auto import from //depot/cupcake/@135843
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..68ea41f
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+# TODO: Remove dependency of application on the test runner (android.test.runner)
+# library.
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
+
+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 0000000..9363849
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,104 @@
+<?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"
+ android:sharedUserLabel="@string/app_label">
+
+ <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" />
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
+
+ <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"
+ android:theme="@android:style/Theme.Light">
+ <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.Light.NoTitleBar" />
+ <activity android:name="WeekActivity" android:label="@string/week_view"
+ android:theme="@android:style/Theme.Light.NoTitleBar" />
+ <activity android:name="DayActivity" android:label="@string/day_view"
+ android:theme="@android:style/Theme.Light.NoTitleBar" />
+ <activity android:name="AgendaActivity" android:label="@string/agenda_view"
+ android:theme="@android:style/Theme.Light.NoTitleBar"
+ android:exported="true" />
+
+ <activity android:name="EditEvent" android:label="@string/event_edit_title"
+ android:theme="@android:style/Theme.Light"
+ 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:theme="@android:style/Theme.Light"
+ 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 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/res/anim/slide_down_in.xml b/res/anim/slide_down_in.xml
new file mode 100644
index 0000000..dd1ca08
--- /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 0000000..188c817
--- /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 0000000..c72fd87
--- /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 0000000..e4308ec
--- /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 0000000..f2f97fc
--- /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 0000000..1807f10
--- /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 0000000..675908f
--- /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 0000000..a1303a5
--- /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 0000000..2f6aa9c
--- /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 0000000..bfed653
--- /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 0000000..cf861b6
--- /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 0000000..da09adc
--- /dev/null
+++ b/res/drawable-land/dna_empty.png
Binary files differ
diff --git a/res/drawable/app_icon.png b/res/drawable/app_icon.png
new file mode 100644
index 0000000..9241090
--- /dev/null
+++ b/res/drawable/app_icon.png
Binary files differ
diff --git a/res/drawable/bg_cal_card.9.png b/res/drawable/bg_cal_card.9.png
new file mode 100644
index 0000000..af7ec44
--- /dev/null
+++ b/res/drawable/bg_cal_card.9.png
Binary files differ
diff --git a/res/drawable/box_appointment.xml b/res/drawable/box_appointment.xml
new file mode 100644
index 0000000..e3d76e7
--- /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 0000000..b3a63ba
--- /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 0000000..b5c5684
--- /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 0000000..d05e886
--- /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 0000000..51808d6
--- /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 0000000..c4b41fb
--- /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 0000000..6520b52
--- /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 0000000..af524bc
--- /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 0000000..b2a22d9
--- /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 0000000..97914c5
--- /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 0000000..257c00a
--- /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 0000000..8e0a0cf
--- /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 0000000..54fa86e
--- /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 0000000..23c6936
--- /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 0000000..debced4
--- /dev/null
+++ b/res/drawable/ic_menu_show_list.png
Binary files differ
diff --git a/res/drawable/ic_recurring_white.png b/res/drawable/ic_recurring_white.png
new file mode 100644
index 0000000..758ec19
--- /dev/null
+++ b/res/drawable/ic_recurring_white.png
Binary files differ
diff --git a/res/drawable/ic_reminder_bell.png b/res/drawable/ic_reminder_bell.png
new file mode 100644
index 0000000..a4e01f8
--- /dev/null
+++ b/res/drawable/ic_reminder_bell.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 0000000..9ad78c8
--- /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 0000000..95bef88
--- /dev/null
+++ b/res/drawable/ic_repeat_white.png
Binary files differ
diff --git a/res/drawable/month_view_background.9.png b/res/drawable/month_view_background.9.png
new file mode 100644
index 0000000..8a8bfcb
--- /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 0000000..e84b4fa
--- /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 0000000..70c0f94
--- /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 0000000..67a6e2e
--- /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 0000000..42e54f0
--- /dev/null
+++ b/res/drawable/month_view_today_background.9.png
Binary files differ
diff --git a/res/drawable/selection.xml b/res/drawable/selection.xml
new file mode 100644
index 0000000..7ca6b55
--- /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 0000000..4433a16
--- /dev/null
+++ b/res/drawable/stat_notify_calendar.png
Binary files differ
diff --git a/res/drawable/strip_cal.9.png b/res/drawable/strip_cal.9.png
new file mode 100644
index 0000000..f4cc88d
--- /dev/null
+++ b/res/drawable/strip_cal.9.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 0000000..43428a1
--- /dev/null
+++ b/res/drawable/sym_calendar_event.png
Binary files differ
diff --git a/res/drawable/top_color_strip.9.png b/res/drawable/top_color_strip.9.png
new file mode 100644
index 0000000..d1353df
--- /dev/null
+++ b/res/drawable/top_color_strip.9.png
Binary files differ
diff --git a/res/layout/agenda_activity.xml b/res/layout/agenda_activity.xml
new file mode 100644
index 0000000..4bf0ee6
--- /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 0000000..faee4a6
--- /dev/null
+++ b/res/layout/agenda_day.xml
@@ -0,0 +1,38 @@
+<?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"
+ android:background="@android:color/darker_gray"
+ android:padding="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>
+
diff --git a/res/layout/agenda_item.xml b/res/layout/agenda_item.xml
new file mode 100644
index 0000000..e700328
--- /dev/null
+++ b/res/layout/agenda_item.xml
@@ -0,0 +1,65 @@
+<?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.
+-->
+
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ >
+
+ <View android:id="@+id/vertical_stripe"
+ android:layout_width="10dip"
+ android:layout_height="fill_parent"
+ android:layout_marginRight="5dip" />
+
+ <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="@android:color/black"
+ style="?android:attr/textAppearanceMediumInverse"
+ />
+
+ <TextView android:id="@+id/when"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ android:textColor="@android:color/black"
+ style="?android:attr/textAppearanceSmallInverse"
+ />
+
+ <TextView android:id="@+id/where"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ android:textColor="@android:color/black"
+ style="?android:attr/textAppearanceSmallInverse"
+ />
+ </LinearLayout>
+ </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/agenda_reminder_item.xml b/res/layout/agenda_reminder_item.xml
new file mode 100644
index 0000000..4038360
--- /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 0000000..bce77d1
--- /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: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 0000000..9e76405
--- /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: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="@android: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 0000000..3187ca7
--- /dev/null
+++ b/res/layout/alert_toast.xml
@@ -0,0 +1,90 @@
+<?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="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:drawable/toast_frame">
+
+ <LinearLayout
+ 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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:drawable/divider_horizontal_dark" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dip">
+
+ <LinearLayout
+ 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: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 0000000..9f988e3
--- /dev/null
+++ b/res/layout/bubble_event.xml
@@ -0,0 +1,64 @@
+<?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:paddingBottom="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="@color/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="@color/panel_text_foreground"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <TextView android:id="@+id/where"
+ android:textColor="@color/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 0000000..9d3ba39
--- /dev/null
+++ b/res/layout/calendar_item.xml
@@ -0,0 +1,40 @@
+<?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: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 0000000..3eb19b1
--- /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 0000000..1335999
--- /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: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 0000000..1ba2d08
--- /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 0000000..93b3ce1
--- /dev/null
+++ b/res/layout/day_activity.xml
@@ -0,0 +1,53 @@
+<?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>
+
+ <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 0000000..68f2001
--- /dev/null
+++ b/res/layout/edit_event.xml
@@ -0,0 +1,339 @@
+<?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: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: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: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: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:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <LinearLayout
+ 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"/>
+ </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: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" />
+ </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:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <LinearLayout
+ 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:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <LinearLayout
+ 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:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <LinearLayout
+ 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 -->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="5dip"
+ android:paddingLeft="4dip"
+ android:paddingRight="4dip"
+ android:paddingBottom="1dip"
+ android:background="@android:drawable/bottom_bar" >
+
+ <Button android:id="@+id/save"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/save_label"
+ />
+
+ <Button android:id="@+id/discard"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:text="@string/discard_label"
+ />
+
+ <Button android:id="@+id/delete"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ 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 0000000..160eb8a
--- /dev/null
+++ b/res/layout/edit_reminder_item.xml
@@ -0,0 +1,37 @@
+<?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="wrap_content"
+ android:layout_marginRight="2dip"
+ android:gravity="center_vertical"
+ />
+</LinearLayout>
diff --git a/res/layout/event_info_activity.xml b/res/layout/event_info_activity.xml
new file mode 100644
index 0000000..a6fbe2d
--- /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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1.0" >
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dip"
+ android:layout_alignParentTop="true" >
+
+
+ <LinearLayout android:id="@+id/event"
+ android:background="@drawable/bg_cal_card"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_weight="1">
+
+
+ <View android:id="@+id/strip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/strip_cal"
+ />
+
+ <!-- WHAT -->
+ <TextView android:id="@+id/title"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="all"
+ android:textStyle="bold"
+ android:padding="8dip"
+ style="?android:attr/textAppearanceMedium"
+ />
+
+ <View android:id="@+id/divider"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <!-- WHEN -->
+ <TextView android:id="@+id/when"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="5dip"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceSmall"
+ />
+
+ <!-- TIMEZONE -->
+ <LinearLayout android:id="@+id/timezone_container"
+ android:orientation="horizontal"
+ android:paddingTop="5dip"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ 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/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/timezone"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceSmall"
+ />
+ </LinearLayout>
+
+ <!-- REPEATS -->
+ <LinearLayout android:id="@+id/repeat_container"
+ android:orientation="horizontal"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ 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/textAppearanceSmall"
+ />
+ </LinearLayout>
+
+ <!-- WHERE -->
+ <TextView android:id="@+id/where"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:autoLink="all"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceSmall"
+ />
+
+ <!-- DESCRIPTION -->
+ <TextView android:id="@+id/description"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:autoLink="all"
+ style="?android:attr/textAppearanceSmall"
+ />
+
+ <!-- CALENDAR -->
+ <LinearLayout android:id="@+id/calendar_container"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:paddingBottom="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/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/calendar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceSmall"
+ />
+ </LinearLayout>
+ </LinearLayout>
+
+ <!-- REMINDERS -->
+ <LinearLayout android:id="@+id/reminders_container"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ 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: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:prompt="@string/view_event_response_label"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/response_labels1"/>
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/res/layout/ics_import_activity.xml b/res/layout/ics_import_activity.xml
new file mode 100644
index 0000000..f994f1c
--- /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="@color/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="@android:string/cancel" />
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/res/layout/month_activity.xml b/res/layout/month_activity.xml
new file mode 100644
index 0000000..fe9c651
--- /dev/null
+++ b/res/layout/month_activity.xml
@@ -0,0 +1,81 @@
+<?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>
+
+ <LinearLayout android:id="@+id/day_names"
+ android:orientation="horizontal"
+ android:background="@color/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 0000000..9cd6c5d
--- /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="@color/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="@color/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="@color/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="@color/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="@color/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="@color/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="@color/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="@color/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="@color/panel_text_foreground"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/res/layout/week_activity.xml b/res/layout/week_activity.xml
new file mode 100644
index 0000000..93b3ce1
--- /dev/null
+++ b/res/layout/week_activity.xml
@@ -0,0 +1,53 @@
+<?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>
+
+ <ViewSwitcher android:id="@+id/switcher"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+</LinearLayout>
diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml
new file mode 100644
index 0000000..f6e58b6
--- /dev/null
+++ b/res/values-cs/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 minut"</item>
+ <item>"10 minut"</item>
+ <item>"15 minut"</item>
+ <item>"20 minut"</item>
+ <item>"25 minut"</item>
+ <item>"30 minut"</item>
+ <item>"45 minut"</item>
+ <item>"1 hodina"</item>
+ <item>"2 hodiny"</item>
+ <item>"3 hodiny"</item>
+ <item>"12 hodin"</item>
+ <item>"24 hodin"</item>
+ <item>"2 dny"</item>
+ <item>"1 týden"</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>"Žádné"</item>
+ <item>"5 minut"</item>
+ <item>"10 minut"</item>
+ <item>"15 minut"</item>
+ <item>"20 minut"</item>
+ <item>"25 minut"</item>
+ <item>"30 minut"</item>
+ <item>"45 minut"</item>
+ <item>"1 hodina"</item>
+ <item>"2 hodiny"</item>
+ <item>"3 hodiny"</item>
+ <item>"12 hodin"</item>
+ <item>"24 hodin"</item>
+ <item>"2 dny"</item>
+ <item>"1 týden"</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>"Upozornění"</item>
+ <item>"Oznámení ve stavovém řádku"</item>
+ <item>"Vypnuto"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Nedostupný"</item>
+ <item>"Dostupný"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Výchozí hodnota"</item>
+ <item>"Soukromá"</item>
+ <item>"Veřejná"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"neděle"</item>
+ <item>"pondělí"</item>
+ <item>"úterý"</item>
+ <item>"středa"</item>
+ <item>"čtvrtek"</item>
+ <item>"pátek"</item>
+ <item>"sobota"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"1."</item>
+ <item>"2."</item>
+ <item>"3."</item>
+ <item>"4."</item>
+ <item>"poslední"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Bez odpovědi)"</item>
+ <item>"Ano"</item>
+ <item>"Možná"</item>
+ <item>"Ne"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Ano"</item>
+ <item>"Možná"</item>
+ <item>"Ne"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Pouze tato událost"</item>
+ <item>"Tato a budoucí události"</item>
+ <item>"Všechny události"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Tato a budoucí události"</item>
+ <item>"Všechny události"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Pouze tato instance"</item>
+ <item>"Všechny instance události"</item>
+ </string-array>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..7da9cfc
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Kalendář"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Vítá vás Kalendář Google!"</font>" "\n" Přístup společnosti Google ke správě vašeho časového plánu je založen na myšlence, že plánování událostí může být intuitivnější, efektivnější a přístupnější."</string>
+ <string name="what_label">"Co"</string>
+ <string name="when_label">"Kdy"</string>
+ <string name="where_label">"Kde"</string>
+ <string name="repeats_label">"Opakování"</string>
+ <string name="no_title_label">"(Bez předmětu)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 minuta"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minut"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 min"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 hodina"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> hod."</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 den"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dnů"</item>
+ </plurals>
+ <string name="show_agenda_view">"Zobrazit agendu"</string>
+ <string name="show_day_view">"Zobrazit den"</string>
+ <string name="agenda_view">"Agenda"</string>
+ <string name="day_view">"Den"</string>
+ <string name="week_view">"Týden"</string>
+ <string name="month_view">"Měsíc"</string>
+ <string name="event_view">"Zobrazit událost"</string>
+ <string name="event_create">"Nová událost"</string>
+ <string name="event_edit">"Upravit událost"</string>
+ <string name="event_delete">"Odstranit událost"</string>
+ <string name="goto_today">"Dnes"</string>
+ <string name="menu_select_calendars">"Moje kalendáře"</string>
+ <string name="menu_preferences">"Nastavení"</string>
+ <string name="plus_N_more">"(plus o <xliff:g id="MORE_COUNT">%d</xliff:g> více …)"</string>
+ <string name="calendars_title">"Moje kalendáře"</string>
+ <string name="add_calendars">"Přidat kalendáře"</string>
+ <string name="remove_calendars">"Odebrat kalendáře"</string>
+ <string name="event_edit_title">"Podrobnosti události"</string>
+ <string name="hint_what">"Název události"</string>
+ <string name="hint_where">"Místo události"</string>
+ <string name="hint_description">"Popis události"</string>
+ <string name="creating_event">"Vytváření události…"</string>
+ <string name="saving_event">"Ukládání události…"</string>
+ <string name="loading_calendars_title">"Načítání kalendářů"</string>
+ <string name="loading_calendars_message">"Načítání kalendářů..."</string>
+ <string name="alert_when_label">"Kdy:"</string>
+ <string name="alert_where_label">"Kde:"</string>
+ <string name="alert_missed_events_single">"(zbývá <xliff:g id="REMINDER_COUNT">%s</xliff:g> připomenutí)"</string>
+ <string name="alert_missed_events_multiple">"(zbývá připomenutí: <xliff:g id="REMINDER_COUNT">%s</xliff:g>)"</string>
+ <string name="event_info_title">"Zobrazit událost"</string>
+ <string name="event_info_title_invite">"Pozvánka na schůzku"</string>
+ <string name="add_new_reminder">"Přidat připomenutí"</string>
+ <string name="edit_event_to_label">"Do"</string>
+ <string name="edit_event_from_label">"Od"</string>
+ <string name="edit_event_all_day_label">"Celý den"</string>
+ <string name="edit_event_calendar_label">"Kalendář"</string>
+ <string name="edit_event_show_extra_options">"Zobrazit další možnosti"</string>
+ <string name="edit_event_hide_extra_options">"Skrýt další možnosti"</string>
+ <string name="description_label">"Popis"</string>
+ <string name="presence_label">"Účast"</string>
+ <string name="privacy_label">"Přístupnost"</string>
+ <string name="reminders_label">"Připomenutí"</string>
+ <string name="no_syncable_calendars">"Žádné kalendáře"</string>
+ <string name="no_calendars_found">"Nemáte žádné kalendáře."</string>
+ <string name="view_event_calendar_label">"Kalendář"</string>
+ <string name="view_event_timezone_label">"Místní časové pásmo"</string>
+ <string name="view_event_response_label">"Zúčastníte se?"</string>
+ <string name="agenda_today">"Dnes"</string>
+ <string name="num_events">"Počet událostí"</string>
+ <string name="edit_event_label">"Upravit událost"</string>
+ <string name="delete_label">"Smazat"</string>
+ <string name="delete_event_label">"Odstranit událost"</string>
+ <string name="save_label">"Uložit"</string>
+ <string name="discard_label">"Zrušit změny"</string>
+ <string name="import_label">"Importovat"</string>
+ <string name="snooze_all_label">"Odložit vše"</string>
+ <string name="dismiss_all_label">"Zavřít vše"</string>
+ <string name="does_not_repeat">"Jednorázová událost"</string>
+ <string name="daily">"Denně"</string>
+ <string name="every_weekday">"Každý pracovní den (po–pá)"</string>
+ <string name="weekly">"Týdně (vždy <xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Měsíčně (vždy <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">"Měsíčně (<xliff:g id="DAY_OF_MONTH">%s</xliff:g>. den)"</string>
+ <string name="yearly">"Ročně (<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Vlastní... (nelze personalizovat v telefonu)"</string>
+ <string name="modify_event">"Změnit pouze tuto událost"</string>
+ <string name="modify_all">"Změnit všechny události série"</string>
+ <string name="modify_all_following">"Změnit tuto událost a všechny budoucí"</string>
+ <string name="delete_this_event_title">"Tato událost bude odstraněna."</string>
+ <string name="delete_title">"Smazat"</string>
+ <string name="change_response_title">"Změnit odpověď"</string>
+ <string name="preferences_title">"Nastavení"</string>
+ <string name="preferences_general_title">"Nastavení zobrazení kalendáře"</string>
+ <string name="preferences_alerts_title">"Nastavení připomenutí"</string>
+ <string name="preferences_hide_declined_title">"Skrýt odmítnuté události"</string>
+ <string name="preferences_alerts_type_title">"Nastavit upozornění a oznámení"</string>
+ <string name="preferences_alerts_type_dialog">"Upozornění a oznámení"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibrace"</string>
+ <string name="preferences_alerts_ringtone_title">"Vybrat zvonění"</string>
+ <string name="preferences_default_reminder_title">"Nastavit výchozí připomenutí"</string>
+ <string name="preferences_default_reminder_dialog">"Výchozí čas připomenutí"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
new file mode 100644
index 0000000..35d3d63
--- /dev/null
+++ b/res/values-de/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 Minuten"</item>
+ <item>"10 Minuten"</item>
+ <item>"15 Minuten"</item>
+ <item>"20 Minuten"</item>
+ <item>"25 Minuten"</item>
+ <item>"30 Minuten"</item>
+ <item>"45 Minuten"</item>
+ <item>"1 Stunde"</item>
+ <item>"2 Stunden"</item>
+ <item>"3 Stunden"</item>
+ <item>"12 Stunden"</item>
+ <item>"24 Stunden"</item>
+ <item>"2 Tage"</item>
+ <item>"1 Woche"</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>"Keine"</item>
+ <item>"5 Minuten"</item>
+ <item>"10 Minuten"</item>
+ <item>"15 Minuten"</item>
+ <item>"20 Minuten"</item>
+ <item>"25 Minuten"</item>
+ <item>"30 Minuten"</item>
+ <item>"45 Minuten"</item>
+ <item>"1 Stunde"</item>
+ <item>"2 Stunden"</item>
+ <item>"3 Stunden"</item>
+ <item>"12 Stunden"</item>
+ <item>"24 Stunden"</item>
+ <item>"2 Tage"</item>
+ <item>"1 Woche"</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>"Benachrichtigung"</item>
+ <item>"Statuszeilenbenachrichtigung"</item>
+ <item>"Aus"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Beschäftigt"</item>
+ <item>"Verfügbar"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Standard"</item>
+ <item>"Privat"</item>
+ <item>"Öffentlich"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"Sonntag"</item>
+ <item>"Montag"</item>
+ <item>"Dienstag"</item>
+ <item>"Mittwoch"</item>
+ <item>"Donnerstag"</item>
+ <item>"Freitag"</item>
+ <item>"Samstag"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"ersten"</item>
+ <item>"zweiten"</item>
+ <item>"dritten"</item>
+ <item>"vierten"</item>
+ <item>"letzten"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(keine Antwort)"</item>
+ <item>"Ja"</item>
+ <item>"Vielleicht"</item>
+ <item>"Nein"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Ja"</item>
+ <item>"Vielleicht"</item>
+ <item>"Nein"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Nur dieser Termin"</item>
+ <item>"Dieser und zukünftige Termine"</item>
+ <item>"Alle Termine"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Dieser und zukünftige Termine"</item>
+ <item>"Alle Termine"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Nur diese Instanz"</item>
+ <item>"Alle Termine dieser Serie"</item>
+ </string-array>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..152047e
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Kalender"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Willkommen bei Google Kalender!"</font>" "\n"Ein Ansatz à la Google beim Verwalten Ihrer Termine, der zeigen soll, dass Terminverwaltung intuitiver, effizienter und einfacher sein kann."</string>
+ <string name="what_label">"Was"</string>
+ <string name="when_label">"Wann"</string>
+ <string name="where_label">"Wo"</string>
+ <string name="repeats_label">"Wiederholung"</string>
+ <string name="no_title_label">"(kein Thema)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 Minute"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Minuten"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 Min."</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Min."</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 Stunde"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 Tag"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Tag(e)"</item>
+ </plurals>
+ <string name="show_agenda_view">"Terminübersicht anzeigen"</string>
+ <string name="show_day_view">"Tag anzeigen"</string>
+ <string name="agenda_view">"Terminübersicht"</string>
+ <string name="day_view">"Tag"</string>
+ <string name="week_view">"Woche"</string>
+ <string name="month_view">"Monat"</string>
+ <string name="event_view">"Termin anzeigen"</string>
+ <string name="event_create">"Neuer Termin"</string>
+ <string name="event_edit">"Termin bearbeiten"</string>
+ <string name="event_delete">"Termin löschen"</string>
+ <string name="goto_today">"Heute"</string>
+ <string name="menu_select_calendars">"Meine Kalender"</string>
+ <string name="menu_preferences">"Einstellungen"</string>
+ <string name="plus_N_more">"(und <xliff:g id="MORE_COUNT">%d</xliff:g> weitere…)"</string>
+ <string name="calendars_title">"Meine Kalender"</string>
+ <string name="add_calendars">"Kalender hinzufügen"</string>
+ <string name="remove_calendars">"Kalender entfernen"</string>
+ <string name="event_edit_title">"Termindetails"</string>
+ <string name="hint_what">"Terminname"</string>
+ <string name="hint_where">"Ort des Termins"</string>
+ <string name="hint_description">"Terminbeschreibung"</string>
+ <string name="creating_event">"Termin wird erstellt…"</string>
+ <string name="saving_event">"Termin wird gespeichert…"</string>
+ <string name="loading_calendars_title">"Kalender werden geladen."</string>
+ <string name="loading_calendars_message">"Kalender werden geladen..."</string>
+ <string name="alert_when_label">"Wann:"</string>
+ <string name="alert_where_label">"Wo:"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> weitere Erinnerung)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> weitere Erinnerungen)"</string>
+ <string name="event_info_title">"Termin anzeigen"</string>
+ <string name="event_info_title_invite">"Einladung zu einer Besprechung"</string>
+ <string name="add_new_reminder">"Erinnerung hinzufügen"</string>
+ <string name="edit_event_to_label">"Bis"</string>
+ <string name="edit_event_from_label">"Von"</string>
+ <string name="edit_event_all_day_label">"Ganztägig"</string>
+ <string name="edit_event_calendar_label">"Kalender"</string>
+ <string name="edit_event_show_extra_options">"Zusätzliche Optionen anzeigen"</string>
+ <string name="edit_event_hide_extra_options">"Zusätzliche Optionen ausblenden"</string>
+ <string name="description_label">"Beschreibung"</string>
+ <string name="presence_label">"Status"</string>
+ <string name="privacy_label">"Datenschutz"</string>
+ <string name="reminders_label">"Erinnerungen"</string>
+ <string name="no_syncable_calendars">"Keine Kalender"</string>
+ <string name="no_calendars_found">"Sie haben keine Kalender."</string>
+ <string name="view_event_calendar_label">"Kalender"</string>
+ <string name="view_event_timezone_label">"Lokalzeit"</string>
+ <string name="view_event_response_label">"Teilnahme?"</string>
+ <string name="agenda_today">"Heute"</string>
+ <string name="num_events">"Anzahl Termine"</string>
+ <string name="edit_event_label">"Termin bearbeiten"</string>
+ <string name="delete_label">"Löschen"</string>
+ <string name="delete_event_label">"Termin löschen"</string>
+ <string name="save_label">"Speichern"</string>
+ <string name="discard_label">"Änderungen verwerfen"</string>
+ <string name="import_label">"Import"</string>
+ <string name="snooze_all_label">"Später erinnern"</string>
+ <string name="dismiss_all_label">"Alle schließen"</string>
+ <string name="does_not_repeat">"Einmaliger Termin"</string>
+ <string name="daily">"Täglich"</string>
+ <string name="every_weekday">"Jeden Werktag (Mo. - Fr.)"</string>
+ <string name="weekly">"Wöchentlich (jeden <xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"Monatlich (am <xliff:g id="DAY_OF_MONTH">%s</xliff:g>.)"</string>
+ <string name="yearly">"Jährlich (am <xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Benutzerdefiniert… (Anpassung über Telefon nicht möglich)"</string>
+ <string name="modify_event">"Nur diesen Termin ändern"</string>
+ <string name="modify_all">"Alle Termine in dieser Serie ändern"</string>
+ <string name="modify_all_following">"Diesen und alle zukünftigen Termine ändern"</string>
+ <string name="delete_this_event_title">"Dieser Termin wird gelöscht."</string>
+ <string name="delete_title">"Löschen"</string>
+ <string name="change_response_title">"Antwort ändern"</string>
+ <string name="preferences_title">"Einstellungen"</string>
+ <string name="preferences_general_title">"Einstellung für Kalenderansicht"</string>
+ <string name="preferences_alerts_title">"Erinnerungseinstellungen"</string>
+ <string name="preferences_hide_declined_title">"Abgelehnte Termine ausblenden"</string>
+ <string name="preferences_alerts_type_title">"Benachrichtigungen und Hinweise festlegen"</string>
+ <string name="preferences_alerts_type_dialog">"Benachrichtigungen und Hinweise"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibration"</string>
+ <string name="preferences_alerts_ringtone_title">"Klingelton auswählen"</string>
+ <string name="preferences_default_reminder_title">"Standarderinnerung festlegen"</string>
+ <string name="preferences_default_reminder_dialog">"Standard-Erinnerungszeit"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml
new file mode 100644
index 0000000..133a831
--- /dev/null
+++ b/res/values-es/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 minutos"</item>
+ <item>"10 minutos"</item>
+ <item>"15 minutos"</item>
+ <item>"20 minutos"</item>
+ <item>"25 minutos"</item>
+ <item>"30 minutos"</item>
+ <item>"45 minutos"</item>
+ <item>"1 hora"</item>
+ <item>"2 horas"</item>
+ <item>"3 horas"</item>
+ <item>"12 horas"</item>
+ <item>"24 horas"</item>
+ <item>"2 días"</item>
+ <item>"1 semana"</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>"Ninguno"</item>
+ <item>"5 minutos"</item>
+ <item>"10 minutos"</item>
+ <item>"15 minutos"</item>
+ <item>"20 minutos"</item>
+ <item>"25 minutos"</item>
+ <item>"30 minutos"</item>
+ <item>"45 minutos"</item>
+ <item>"1 hora"</item>
+ <item>"2 horas"</item>
+ <item>"3 horas"</item>
+ <item>"12 horas"</item>
+ <item>"24 horas"</item>
+ <item>"2 días"</item>
+ <item>"1 semana"</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>"Alertar"</item>
+ <item>"Notificación de la barra de estado"</item>
+ <item>"Desconectado"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Ocupado"</item>
+ <item>"Disponible"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Predeterminado"</item>
+ <item>"Privado"</item>
+ <item>"Público"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"Domingo"</item>
+ <item>"Lunes"</item>
+ <item>"Martes"</item>
+ <item>"Miércoles"</item>
+ <item>"Jueves"</item>
+ <item>"Viernes"</item>
+ <item>"Sábado"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"primero"</item>
+ <item>"segundo"</item>
+ <item>"tercero"</item>
+ <item>"cuarto"</item>
+ <item>"último"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(No responde)"</item>
+ <item>"Sí"</item>
+ <item>"Quizás"</item>
+ <item>"No"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Sí"</item>
+ <item>"Quizás"</item>
+ <item>"No"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Sólo este evento"</item>
+ <item>"Este evento y eventos futuros"</item>
+ <item>"Todos los eventos"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Éste y otros eventos posteriores"</item>
+ <item>"Todos los eventos"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Sólo esta ocasión"</item>
+ <item>"Todos los eventos de la serie"</item>
+ </string-array>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..1755eb7
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Calendario"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Bienvenido a Google Calendar"</font>\n" Una propuesta de Google para gestionar su agenda basándonos en la idea de que la programación de los eventos puede ser más intuitiva, eficaz y accesible."</string>
+ <string name="what_label">"Asunto"</string>
+ <string name="when_label">"Cuándo"</string>
+ <string name="where_label">"Dónde"</string>
+ <string name="repeats_label">"Repetición"</string>
+ <string name="no_title_label">"(Sin asunto)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 minuto"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 minuto"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minutos"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 hora"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> horas"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 día"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> días"</item>
+ </plurals>
+ <string name="show_agenda_view">"Mostrar agenda"</string>
+ <string name="show_day_view">"Mostrar día"</string>
+ <string name="agenda_view">"Agenda"</string>
+ <string name="day_view">"Día"</string>
+ <string name="week_view">"Semana"</string>
+ <string name="month_view">"Mes"</string>
+ <string name="event_view">"Ver evento"</string>
+ <string name="event_create">"Evento nuevo"</string>
+ <string name="event_edit">"Editar evento"</string>
+ <string name="event_delete">"Suprimir evento"</string>
+ <string name="goto_today">"Hoy"</string>
+ <string name="menu_select_calendars">"Mis calendarios"</string>
+ <string name="menu_preferences">"Ajustes"</string>
+ <string name="plus_N_more">"(además de <xliff:g id="MORE_COUNT">%d</xliff:g> más...)"</string>
+ <string name="calendars_title">"Mis calendarios"</string>
+ <string name="add_calendars">"Añadir calendarios"</string>
+ <string name="remove_calendars">"Eliminar calendarios"</string>
+ <string name="event_edit_title">"Información del evento"</string>
+ <string name="hint_what">"Nombre del evento"</string>
+ <string name="hint_where">"Ubicación del evento"</string>
+ <string name="hint_description">"Descripción del evento"</string>
+ <string name="creating_event">"Creando evento..."</string>
+ <string name="saving_event">"Guardando evento..."</string>
+ <string name="loading_calendars_title">"Cargando calendarios"</string>
+ <string name="loading_calendars_message">"Cargando calendarios..."</string>
+ <string name="alert_when_label">"Cuándo:"</string>
+ <string name="alert_where_label">"Dónde:"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> más recordatorios)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> más recordatorios)"</string>
+ <string name="event_info_title">"Ver evento"</string>
+ <string name="event_info_title_invite">"Invitación a un evento"</string>
+ <string name="add_new_reminder">"Añadir recordatorio"</string>
+ <string name="edit_event_to_label">"Hasta"</string>
+ <string name="edit_event_from_label">"Desde"</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_show_extra_options">"Mostrar opciones extra"</string>
+ <string name="edit_event_hide_extra_options">"Ocultar opciones extra"</string>
+ <string name="description_label">"Descripción"</string>
+ <string name="presence_label">"Presencia"</string>
+ <string name="privacy_label">"Privacidad"</string>
+ <string name="reminders_label">"Recordatorios"</string>
+ <string name="no_syncable_calendars">"No hay calendarios."</string>
+ <string name="no_calendars_found">"No tienes ningún calendario."</string>
+ <string name="view_event_calendar_label">"Calendario"</string>
+ <string name="view_event_timezone_label">"Zona horaria local"</string>
+ <string name="view_event_response_label">"¿Asistiendo?"</string>
+ <string name="agenda_today">"Hoy"</string>
+ <string name="num_events">"Número de eventos"</string>
+ <string name="edit_event_label">"Editar evento"</string>
+ <string name="delete_label">"Eliminar"</string>
+ <string name="delete_event_label">"Suprimir evento"</string>
+ <string name="save_label">"Guardar"</string>
+ <string name="discard_label">"Descartar cambios"</string>
+ <string name="import_label">"Importar"</string>
+ <string name="snooze_all_label">"Posponer todos"</string>
+ <string name="dismiss_all_label">"Descartar todos"</string>
+ <string name="does_not_repeat">"Evento único"</string>
+ <string name="daily">"Cada día"</string>
+ <string name="every_weekday">"Cada día laboral (lunes-viernes)"</string>
+ <string name="weekly">"Cada semana (todos los <xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Cada mes (todos los <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">"Cada mes (el día <xliff:g id="DAY_OF_MONTH">%s</xliff:g>)"</string>
+ <string name="yearly">"Cada año (el <xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Personalizado... (no se puede personalizar por teléfono)"</string>
+ <string name="modify_event">"Cambiar sólo este evento"</string>
+ <string name="modify_all">"Cambiar todos los eventos de la serie"</string>
+ <string name="modify_all_following">"Cambiar éste y todos los eventos futuros"</string>
+ <string name="delete_this_event_title">"Este evento se suprimirá."</string>
+ <string name="delete_title">"Eliminar"</string>
+ <string name="change_response_title">"Cambiar respuesta"</string>
+ <string name="preferences_title">"Ajustes"</string>
+ <string name="preferences_general_title">"Configuración de la vista de calendario"</string>
+ <string name="preferences_alerts_title">"Configuración de los recordatorios"</string>
+ <string name="preferences_hide_declined_title">"Ocultar eventos rechazados"</string>
+ <string name="preferences_alerts_type_title">"Configurar notificaciones y alertas"</string>
+ <string name="preferences_alerts_type_dialog">"Alertas y notificaciones"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibrar"</string>
+ <string name="preferences_alerts_ringtone_title">"Seleccionar tono"</string>
+ <string name="preferences_default_reminder_title">"Configurar recordatorio predeterminado"</string>
+ <string name="preferences_default_reminder_dialog">"Duración predeterminada del recordatorio"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
new file mode 100644
index 0000000..62e0448
--- /dev/null
+++ b/res/values-fr/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <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 heure"</item>
+ <item>"2 heures"</item>
+ <item>"3 heures"</item>
+ <item>"12 heures"</item>
+ <item>"24 heures"</item>
+ <item>"2 jours"</item>
+ <item>"1 semaine"</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>"Aucun"</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 heure"</item>
+ <item>"2 heures"</item>
+ <item>"3 heures"</item>
+ <item>"12 heures"</item>
+ <item>"24 heures"</item>
+ <item>"2 jours"</item>
+ <item>"1 semaine"</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>"Alerte"</item>
+ <item>"Notification de barre d\'état"</item>
+ <item>"Désactivé"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Occupé"</item>
+ <item>"Disponible"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Par défaut"</item>
+ <item>"Privé"</item>
+ <item>"Public"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"dimanche"</item>
+ <item>"lundi"</item>
+ <item>"mardi"</item>
+ <item>"mercredi"</item>
+ <item>"jeudi"</item>
+ <item>"vendredi"</item>
+ <item>"samedi"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"1er"</item>
+ <item>"2e"</item>
+ <item>"3e"</item>
+ <item>"4e"</item>
+ <item>"dernier"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Aucune réponse)"</item>
+ <item>"Oui"</item>
+ <item>"Peut-être"</item>
+ <item>"Non"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Oui"</item>
+ <item>"Peut-être"</item>
+ <item>"Non"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Uniquement cet événement"</item>
+ <item>"Cet événement et les suivants"</item>
+ <item>"Tous les événements"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Cet événement et les suivants"</item>
+ <item>"Tous les événements"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Uniquement cette instance"</item>
+ <item>"Tous les événements de la série"</item>
+ </string-array>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..a1b4517
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Google Agenda"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Bienvenue dans Google Agenda !"</font>" "\n"La solution de Google pour gérer votre emploi du temps en planifiant vos événements de manière plus intuitive, efficace et directe."</string>
+ <string name="what_label">"Objet"</string>
+ <string name="when_label">"Date"</string>
+ <string name="where_label">"Lieu"</string>
+ <string name="repeats_label">"Fréquence"</string>
+ <string name="no_title_label">"(Aucun objet)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 minute"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minutes"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 mn"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> mn"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 heure"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> heures"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 jour"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> jours"</item>
+ </plurals>
+ <string name="show_agenda_view">"Afficher l\'agenda"</string>
+ <string name="show_day_view">"Afficher le jour"</string>
+ <string name="agenda_view">"Mon planning"</string>
+ <string name="day_view">"Jour"</string>
+ <string name="week_view">"Semaine"</string>
+ <string name="month_view">"Mois"</string>
+ <string name="event_view">"Afficher l\'événement"</string>
+ <string name="event_create">"Nouvel événement"</string>
+ <string name="event_edit">"Modifier l\'événement"</string>
+ <string name="event_delete">"Supprimer l\'événement"</string>
+ <string name="goto_today">"Aujourd\'hui"</string>
+ <string name="menu_select_calendars">"Mes agendas"</string>
+ <string name="menu_preferences">"Paramètres"</string>
+ <string name="plus_N_more">"(et <xliff:g id="MORE_COUNT">%d</xliff:g> autres…)"</string>
+ <string name="calendars_title">"Mes agendas"</string>
+ <string name="add_calendars">"Ajouter des agendas"</string>
+ <string name="remove_calendars">"Supprimer les agendas"</string>
+ <string name="event_edit_title">"Détails sur 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="hint_description">"Description de l\'événement"</string>
+ <string name="creating_event">"Création de l\'événement…"</string>
+ <string name="saving_event">"Enregistrement de l\'événement…"</string>
+ <string name="loading_calendars_title">"Chargement des agendas"</string>
+ <string name="loading_calendars_message">"Chargement des agendas..."</string>
+ <string name="alert_when_label">"Date :"</string>
+ <string name="alert_where_label">"Lieu :"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> rappel supplémentaire)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> rappels supplémentaires)"</string>
+ <string name="event_info_title">"Afficher l\'événement"</string>
+ <string name="event_info_title_invite">"Invitation à une réunion"</string>
+ <string name="add_new_reminder">"Ajouter un rappel"</string>
+ <string name="edit_event_to_label">"Au"</string>
+ <string name="edit_event_from_label">"Du"</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_show_extra_options">"Afficher les options supplémentaires"</string>
+ <string name="edit_event_hide_extra_options">"Masquer options supplémentaires"</string>
+ <string name="description_label">"Description"</string>
+ <string name="presence_label">"Présence"</string>
+ <string name="privacy_label">"Confidentialité"</string>
+ <string name="reminders_label">"Rappels"</string>
+ <string name="no_syncable_calendars">"Pas d\'agenda"</string>
+ <string name="no_calendars_found">"Vous n\'avez pas d\'agenda."</string>
+ <string name="view_event_calendar_label">"Agenda"</string>
+ <string name="view_event_timezone_label">"Fuseau horaire local"</string>
+ <string name="view_event_response_label">"Participation ?"</string>
+ <string name="agenda_today">"Aujourd\'hui"</string>
+ <string name="num_events">"Nbre d\'événements"</string>
+ <string name="edit_event_label">"Modifier l\'événement"</string>
+ <string name="delete_label">"Supprimer"</string>
+ <string name="delete_event_label">"Supprimer l\'événement"</string>
+ <string name="save_label">"Enregistrer"</string>
+ <string name="discard_label">"Ignorer"</string>
+ <string name="import_label">"Importer"</string>
+ <string name="snooze_all_label">"Tout répéter"</string>
+ <string name="dismiss_all_label">"Tout supprimer"</string>
+ <string name="does_not_repeat">"Événement ponctuel"</string>
+ <string name="daily">"Ignorer"</string>
+ <string name="every_weekday">"Les jours ouvrables (lun–ven)"</string>
+ <string name="weekly">"Hebdomadaire (chaque <xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"Mensuel (le <xliff:g id="DAY_OF_MONTH">%s</xliff:g>)"</string>
+ <string name="yearly">"Annuel (le <xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Personnalisé… (non paramétrable sur téléphone)"</string>
+ <string name="modify_event">"Modifier uniquement cet événement."</string>
+ <string name="modify_all">"Modifier tous les événements de la série."</string>
+ <string name="modify_all_following">"Modifier cet événement et tous les suivants."</string>
+ <string name="delete_this_event_title">"Cet événement va être supprimé."</string>
+ <string name="delete_title">"Supprimer"</string>
+ <string name="change_response_title">"Modifier la réponse"</string>
+ <string name="preferences_title">"Paramètres"</string>
+ <string name="preferences_general_title">"Paramètre d\'affichage de l\'agenda"</string>
+ <string name="preferences_alerts_title">"Paramètres de rappel"</string>
+ <string name="preferences_hide_declined_title">"Masquer évén. refusés"</string>
+ <string name="preferences_alerts_type_title">"Alertes et notifications"</string>
+ <string name="preferences_alerts_type_dialog">"Alertes et notifications"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibreur"</string>
+ <string name="preferences_alerts_ringtone_title">"Sélectionner la sonnerie"</string>
+ <string name="preferences_default_reminder_title">"Définir le rappel par défaut"</string>
+ <string name="preferences_default_reminder_dialog">"Intervalle de rappel par défaut"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml
new file mode 100644
index 0000000..5778f7e
--- /dev/null
+++ b/res/values-it/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 minuti"</item>
+ <item>"10 minuti"</item>
+ <item>"15 minuti"</item>
+ <item>"20 minuti"</item>
+ <item>"25 minuti"</item>
+ <item>"30 minuti"</item>
+ <item>"45 minuti"</item>
+ <item>"1 ora"</item>
+ <item>"2 ore"</item>
+ <item>"3 ore"</item>
+ <item>"12 ore"</item>
+ <item>"24 ore"</item>
+ <item>"2 giorni"</item>
+ <item>"1 settimana"</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>"Nessuno"</item>
+ <item>"5 minuti"</item>
+ <item>"10 minuti"</item>
+ <item>"15 minuti"</item>
+ <item>"20 minuti"</item>
+ <item>"25 minuti"</item>
+ <item>"30 minuti"</item>
+ <item>"45 minuti"</item>
+ <item>"1 ora"</item>
+ <item>"2 ore"</item>
+ <item>"3 ore"</item>
+ <item>"12 ore"</item>
+ <item>"24 ore"</item>
+ <item>"2 giorni"</item>
+ <item>"1 settimana"</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>"Avviso"</item>
+ <item>"Notifica barra di stato"</item>
+ <item>"Disativa"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Occupato"</item>
+ <item>"Disponibile"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Predefinito"</item>
+ <item>"Privato"</item>
+ <item>"Pubblico"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"Domenica"</item>
+ <item>"Lunedì"</item>
+ <item>"Martedì"</item>
+ <item>"Mercoledì"</item>
+ <item>"Giovedì"</item>
+ <item>"Venerdì"</item>
+ <item>"Sabato"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"primo"</item>
+ <item>"secondo"</item>
+ <item>"terzo"</item>
+ <item>"quarto"</item>
+ <item>"ultimo"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Nessuna risposta)"</item>
+ <item>"Sì"</item>
+ <item>"Forse"</item>
+ <item>"No"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Sì"</item>
+ <item>"Forse"</item>
+ <item>"No"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Solo questo evento"</item>
+ <item>"Questo e gli eventi futuri"</item>
+ <item>"Tutti gli eventi"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Questo e gli eventi futuri"</item>
+ <item>"Tutti gli eventi"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Solo questa istanza"</item>
+ <item>"Tutti gli eventi nella serie"</item>
+ </string-array>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..0aaeef5
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Calendario"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Benvenuto in Google Calendar!"</font>" "\n"Un approccio di Google alla gestione della tua pianificazione in base all\'idea che gli eventi di pianificazione possano essere più intuitivi, efficaci e accessibili."</string>
+ <string name="what_label">"Che cosa"</string>
+ <string name="when_label">"Intervallo di tempo"</string>
+ <string name="where_label">"Dove"</string>
+ <string name="repeats_label">"Ripetizione"</string>
+ <string name="no_title_label">"(Nessun oggetto)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 minuto"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minuti"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 min"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 ora"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ore"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 giorno"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> giorni"</item>
+ </plurals>
+ <string name="show_agenda_view">"Mostra agenda"</string>
+ <string name="show_day_view">"Mostra giorno"</string>
+ <string name="agenda_view">"Agenda"</string>
+ <string name="day_view">"Giorno"</string>
+ <string name="week_view">"Settimana"</string>
+ <string name="month_view">"Mese"</string>
+ <string name="event_view">"Visualizza evento"</string>
+ <string name="event_create">"Nuovo evento"</string>
+ <string name="event_edit">"Modifica evento"</string>
+ <string name="event_delete">"Elimina evento"</string>
+ <string name="goto_today">"Oggi"</string>
+ <string name="menu_select_calendars">"I miei calendari"</string>
+ <string name="menu_preferences">"Impostazioni"</string>
+ <string name="plus_N_more">"(più altri <xliff:g id="MORE_COUNT">%d</xliff:g>…)"</string>
+ <string name="calendars_title">"I miei calendari"</string>
+ <string name="add_calendars">"Aggiungi calendari"</string>
+ <string name="remove_calendars">"Rimuovi calendari"</string>
+ <string name="event_edit_title">"Dettagli evento"</string>
+ <string name="hint_what">"Nome evento"</string>
+ <string name="hint_where">"Luogo evento"</string>
+ <string name="hint_description">"Descrizione evento"</string>
+ <string name="creating_event">"Creazione evento in corso…"</string>
+ <string name="saving_event">"Salvataggio evento in corso…"</string>
+ <string name="loading_calendars_title">"Caricamento calendari"</string>
+ <string name="loading_calendars_message">"Caricamento calendari in corso..."</string>
+ <string name="alert_when_label">"Intervallo di tempo:"</string>
+ <string name="alert_where_label">"Dove:"</string>
+ <string name="alert_missed_events_single">"(Altri <xliff:g id="REMINDER_COUNT">%s</xliff:g> promemoria)"</string>
+ <string name="alert_missed_events_multiple">"(Altri <xliff:g id="REMINDER_COUNT">%s</xliff:g> promemoria)"</string>
+ <string name="event_info_title">"Visualizza evento"</string>
+ <string name="event_info_title_invite">"Invito a riunione"</string>
+ <string name="add_new_reminder">"Aggiungi promemoria"</string>
+ <string name="edit_event_to_label">"A"</string>
+ <string name="edit_event_from_label">"Da"</string>
+ <string name="edit_event_all_day_label">"Tutto il giorno"</string>
+ <string name="edit_event_calendar_label">"Calendario"</string>
+ <string name="edit_event_show_extra_options">"Mostra opzioni aggiuntive"</string>
+ <string name="edit_event_hide_extra_options">"Nascondi opzioni aggiuntive"</string>
+ <string name="description_label">"Descrizione"</string>
+ <string name="presence_label">"Presenza"</string>
+ <string name="privacy_label">"Privacy"</string>
+ <string name="reminders_label">"Promemoria"</string>
+ <string name="no_syncable_calendars">"Nessun calendario"</string>
+ <string name="no_calendars_found">"Non sono presenti calendari."</string>
+ <string name="view_event_calendar_label">"Calendario"</string>
+ <string name="view_event_timezone_label">"Fuso orario locale"</string>
+ <string name="view_event_response_label">"Partecipanti?"</string>
+ <string name="agenda_today">"Oggi"</string>
+ <string name="num_events">"N. eventi"</string>
+ <string name="edit_event_label">"Modifica evento"</string>
+ <string name="delete_label">"Elimina"</string>
+ <string name="delete_event_label">"Elimina evento"</string>
+ <string name="save_label">"Salva"</string>
+ <string name="discard_label">"Ignora modifiche"</string>
+ <string name="import_label">"Importa"</string>
+ <string name="snooze_all_label">"Posponi tutto"</string>
+ <string name="dismiss_all_label">"Ignora tutto"</string>
+ <string name="does_not_repeat">"Evento occasionale"</string>
+ <string name="daily">"Ogni giorno"</string>
+ <string name="every_weekday">"Ogni giorno lavorativo (Lun-Ven)"</string>
+ <string name="weekly">"Ogni settimana (ogni <xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Ogni mese (ogni <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">"Ogni mese (il <xliff:g id="DAY_OF_MONTH">%s</xliff:g>)"</string>
+ <string name="yearly">"Ogni anno (il <xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Personalizzato… (impossibile personalizzare sul telefono)"</string>
+ <string name="modify_event">"Modifica solo questo evento."</string>
+ <string name="modify_all">"Modifica tutti gli eventi nelle serie."</string>
+ <string name="modify_all_following">"Modifica questo e tutti gli eventi futuri."</string>
+ <string name="delete_this_event_title">"L\'evento verrà eliminato."</string>
+ <string name="delete_title">"Elimina"</string>
+ <string name="change_response_title">"Cambia risposta"</string>
+ <string name="preferences_title">"Impostazioni"</string>
+ <string name="preferences_general_title">"Impostazione visualizzazione calendario"</string>
+ <string name="preferences_alerts_title">"Impostazioni promemoria"</string>
+ <string name="preferences_hide_declined_title">"Nascondi eventi rifiutati"</string>
+ <string name="preferences_alerts_type_title">"Imposta avvisi e notifiche"</string>
+ <string name="preferences_alerts_type_dialog">"Avvisi e notifiche"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibrazione"</string>
+ <string name="preferences_alerts_ringtone_title">"Seleziona suoneria"</string>
+ <string name="preferences_default_reminder_title">"Promemoria predefinito"</string>
+ <string name="preferences_default_reminder_dialog">"Imposta promemoria predefinito"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
new file mode 100644
index 0000000..8c05d85
--- /dev/null
+++ b/res/values-ja/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5分"</item>
+ <item>"10分"</item>
+ <item>"15分"</item>
+ <item>"20分"</item>
+ <item>"25分"</item>
+ <item>"30分"</item>
+ <item>"45分"</item>
+ <item>"1時間"</item>
+ <item>"2時間"</item>
+ <item>"3時間"</item>
+ <item>"12時間"</item>
+ <item>"24時間"</item>
+ <item>"2日"</item>
+ <item>"1週間"</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>"1,440"</item>
+ <item>"2,880"</item>
+ <item>"10,080"</item>
+ </string-array>
+ <string-array name="preferences_default_reminder_labels">
+ <item>"なし"</item>
+ <item>"5分"</item>
+ <item>"10分"</item>
+ <item>"15分"</item>
+ <item>"20分"</item>
+ <item>"25分"</item>
+ <item>"30分"</item>
+ <item>"45分"</item>
+ <item>"1時間"</item>
+ <item>"2時間"</item>
+ <item>"3時間"</item>
+ <item>"12時間"</item>
+ <item>"24時間"</item>
+ <item>"2日"</item>
+ <item>"1週間"</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>"1,440"</item>
+ <item>"2,880"</item>
+ <item>"10,080"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_labels">
+ <item>"アラート"</item>
+ <item>"ステータスバーの通知"</item>
+ <item>"オフ"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"予定あり"</item>
+ <item>"予定なし"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"既定"</item>
+ <item>"限定公開"</item>
+ <item>"一般公開"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"日曜日"</item>
+ <item>"月曜日"</item>
+ <item>"火曜日"</item>
+ <item>"水曜日"</item>
+ <item>"木曜日"</item>
+ <item>"金曜日"</item>
+ <item>"土曜日"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"第1"</item>
+ <item>"第2"</item>
+ <item>"第3"</item>
+ <item>"第4"</item>
+ <item>"最終"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(返答なし)"</item>
+ <item>"はい"</item>
+ <item>"未定"</item>
+ <item>"いいえ"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"はい"</item>
+ <item>"未定"</item>
+ <item>"いいえ"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"この予定のみ"</item>
+ <item>"これ以降の予定"</item>
+ <item>"すべての予定"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"これ以降の予定"</item>
+ <item>"すべての予定"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"今回のみ"</item>
+ <item>"一連の定期的な予定すべて"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..235f0df
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"カレンダー"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Googleカレンダーにようこそ"</font>" "\n"いつでもどこでも簡単にスケジュールを管理できます。"</string>
+ <string name="what_label">"タイトル"</string>
+ <string name="when_label">"日時"</string>
+ <string name="where_label">"場所"</string>
+ <string name="repeats_label">"繰り返し"</string>
+ <string name="no_title_label">"(無題)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1分"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1分"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>分"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1時間"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>時間"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1日"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>日"</item>
+ </plurals>
+ <string name="show_agenda_view">"予定リストを表示"</string>
+ <string name="show_day_view">"日付を表示"</string>
+ <string name="agenda_view">"予定リスト"</string>
+ <string name="day_view">"日"</string>
+ <string name="week_view">"週"</string>
+ <string name="month_view">"月"</string>
+ <string name="event_view">"予定を表示"</string>
+ <string name="event_create">"新しい予定"</string>
+ <string name="event_edit">"予定を編集"</string>
+ <string name="event_delete">"予定を削除"</string>
+ <string name="goto_today">"今日"</string>
+ <string name="menu_select_calendars">"マイ カレンダー"</string>
+ <string name="menu_preferences">"設定"</string>
+ <string name="plus_N_more">"(あと <xliff:g id="MORE_COUNT">%d</xliff:g> 件...)"</string>
+ <string name="calendars_title">"マイ カレンダー"</string>
+ <string name="add_calendars">"カレンダーを追加"</string>
+ <string name="remove_calendars">"カレンダーを削除"</string>
+ <string name="event_edit_title">"予定の詳細"</string>
+ <string name="hint_what">"予定の名前"</string>
+ <string name="hint_where">"予定の場所"</string>
+ <string name="hint_description">"予定の説明"</string>
+ <string name="creating_event">"予定を作成中..."</string>
+ <string name="saving_event">"予定を保存中..."</string>
+ <string name="loading_calendars_title">"カレンダーを読み込み中"</string>
+ <string name="loading_calendars_message">"カレンダーを読み込み中..."</string>
+ <string name="alert_when_label">"日時:"</string>
+ <string name="alert_where_label">"場所:"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> 件の通知がありました)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> 件の通知がありました)"</string>
+ <string name="event_info_title">"予定を表示"</string>
+ <string name="event_info_title_invite">"会議への招待状"</string>
+ <string name="add_new_reminder">"通知を追加"</string>
+ <string name="edit_event_to_label">"終了"</string>
+ <string name="edit_event_from_label">"開始"</string>
+ <string name="edit_event_all_day_label">"終日"</string>
+ <string name="edit_event_calendar_label">"カレンダー"</string>
+ <string name="edit_event_show_extra_options">"追加のオプションを表示する"</string>
+ <string name="edit_event_hide_extra_options">"追加のオプションを表示しない"</string>
+ <string name="description_label">"説明"</string>
+ <string name="presence_label">"ステータス"</string>
+ <string name="privacy_label">"プライバシー"</string>
+ <string name="reminders_label">"通知"</string>
+ <string name="no_syncable_calendars">"カレンダーがありません"</string>
+ <string name="no_calendars_found">"カレンダーがありません。"</string>
+ <string name="view_event_calendar_label">"カレンダー"</string>
+ <string name="view_event_timezone_label">"ローカルタイムゾーン"</string>
+ <string name="view_event_response_label">"参加しますか?"</string>
+ <string name="agenda_today">"今日"</string>
+ <string name="num_events">"予定数"</string>
+ <string name="edit_event_label">"予定を編集"</string>
+ <string name="delete_label">"削除"</string>
+ <string name="delete_event_label">"予定を削除"</string>
+ <string name="save_label">"保存"</string>
+ <string name="discard_label">"変更取消"</string>
+ <string name="import_label">"インポート"</string>
+ <string name="snooze_all_label">"すべてスヌーズ"</string>
+ <string name="dismiss_all_label">"すべて表示しない"</string>
+ <string name="does_not_repeat">"一度だけの予定"</string>
+ <string name="daily">"毎日"</string>
+ <string name="every_weekday">"平日(月~金)"</string>
+ <string name="weekly">"毎週(<xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"毎月(<xliff:g id="DAY_OF_MONTH">%s</xliff:g>日)"</string>
+ <string name="yearly">"毎年(<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"カスタム…(携帯端末ではカスタマイズ不可)"</string>
+ <string name="modify_event">"この予定のみを変更します。"</string>
+ <string name="modify_all">"一連の定期的な予定すべてを削除します。"</string>
+ <string name="modify_all_following">"これ以降の予定すべてを削除します。"</string>
+ <string name="delete_this_event_title">"この予定を削除します。"</string>
+ <string name="delete_title">"削除"</string>
+ <string name="change_response_title">"返答を変更"</string>
+ <string name="preferences_title">"設定"</string>
+ <string name="preferences_general_title">"カレンダーの表示の設定"</string>
+ <string name="preferences_alerts_title">"通知の設定"</string>
+ <string name="preferences_hide_declined_title">"辞退した予定を非表示"</string>
+ <string name="preferences_alerts_type_title">"アラートと通知を設定"</string>
+ <string name="preferences_alerts_type_dialog">"アラートと通知"</string>
+ <string name="preferences_alerts_vibrate_title">"バイブレーション"</string>
+ <string name="preferences_alerts_ringtone_title">"着信音を選択"</string>
+ <string name="preferences_default_reminder_title">"通知設定"</string>
+ <string name="preferences_default_reminder_dialog">"デフォルトの通知時間"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml
new file mode 100644
index 0000000..926bfdc
--- /dev/null
+++ b/res/values-ko/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5분"</item>
+ <item>"10분"</item>
+ <item>"15분"</item>
+ <item>"20분"</item>
+ <item>"25분"</item>
+ <item>"30분"</item>
+ <item>"45분"</item>
+ <item>"1시간"</item>
+ <item>"2시간"</item>
+ <item>"3시간"</item>
+ <item>"12시간"</item>
+ <item>"24시간"</item>
+ <item>"2일"</item>
+ <item>"1주"</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>"없음"</item>
+ <item>"5분"</item>
+ <item>"10분"</item>
+ <item>"15분"</item>
+ <item>"20분"</item>
+ <item>"25분"</item>
+ <item>"30분"</item>
+ <item>"45분"</item>
+ <item>"1시간"</item>
+ <item>"2시간"</item>
+ <item>"3시간"</item>
+ <item>"12시간"</item>
+ <item>"24시간"</item>
+ <item>"2일"</item>
+ <item>"1주"</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>"알림"</item>
+ <item>"상태 표시줄 알림"</item>
+ <item>"해제"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"다른 용무 중"</item>
+ <item>"대화가능"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"기본값"</item>
+ <item>"비공개"</item>
+ <item>"공개"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"일요일"</item>
+ <item>"월요일"</item>
+ <item>"화요일"</item>
+ <item>"수요일"</item>
+ <item>"목요일"</item>
+ <item>"금요일"</item>
+ <item>"토요일"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"첫 번째"</item>
+ <item>"두 번째"</item>
+ <item>"3번째"</item>
+ <item>"4번째"</item>
+ <item>"마지막"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(응답 없음)"</item>
+ <item>"예"</item>
+ <item>"미정"</item>
+ <item>"아니요"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"예"</item>
+ <item>"미정"</item>
+ <item>"아니요"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"해당 이벤트만"</item>
+ <item>"현재 일정 및 향후 일정"</item>
+ <item>"모든 일정"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"현재 및 향후 일정"</item>
+ <item>"모든 일정"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"이 일정에만"</item>
+ <item>"모든 반복 일정"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..24a2e64
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"캘린더"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Google 캘린더에 오신 것을 환영합니다."</font>" "\n"일정 예약을 보다 간편하고 효율적이며 쉽게 사용할 수 있도록 만들어진 Google의 일정 관리 서비스입니다."</string>
+ <string name="what_label">"내용"</string>
+ <string name="when_label">"일자"</string>
+ <string name="where_label">"위치"</string>
+ <string name="repeats_label">"반복"</string>
+ <string name="no_title_label">"(제목 없음)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1분"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1분"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>분"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1시간"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>시간"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1일"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>일"</item>
+ </plurals>
+ <string name="show_agenda_view">"일정 표시"</string>
+ <string name="show_day_view">"하루 일정 표시"</string>
+ <string name="agenda_view">"일정목록"</string>
+ <string name="day_view">"일"</string>
+ <string name="week_view">"주"</string>
+ <string name="month_view">"월"</string>
+ <string name="event_view">"일정 보기"</string>
+ <string name="event_create">"새 일정"</string>
+ <string name="event_edit">"일정 수정"</string>
+ <string name="event_delete">"일정 삭제"</string>
+ <string name="goto_today">"오늘"</string>
+ <string name="menu_select_calendars">"내 캘린더"</string>
+ <string name="menu_preferences">"설정"</string>
+ <string name="plus_N_more">"(<xliff:g id="MORE_COUNT">%d</xliff:g>개 추가 표시)"</string>
+ <string name="calendars_title">"내 캘린더"</string>
+ <string name="add_calendars">"캘린더 추가"</string>
+ <string name="remove_calendars">"캘린더 삭제"</string>
+ <string name="event_edit_title">"일정 세부정보"</string>
+ <string name="hint_what">"일정 이름"</string>
+ <string name="hint_where">"일정 위치"</string>
+ <string name="hint_description">"일정 설명"</string>
+ <string name="creating_event">"일정을 만드는 중..."</string>
+ <string name="saving_event">"일정 저장 중..."</string>
+ <string name="loading_calendars_title">"캘린더 로드 중"</string>
+ <string name="loading_calendars_message">"캘린더 로드 중..."</string>
+ <string name="alert_when_label">"일자:"</string>
+ <string name="alert_where_label">"위치:"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g>개 이상의 알림)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g>개 이상의 알림)"</string>
+ <string name="event_info_title">"일정 보기"</string>
+ <string name="event_info_title_invite">"회의 초대장"</string>
+ <string name="add_new_reminder">"알림 추가"</string>
+ <string name="edit_event_to_label">"종료일:"</string>
+ <string name="edit_event_from_label">"시작일:"</string>
+ <string name="edit_event_all_day_label">"종일"</string>
+ <string name="edit_event_calendar_label">"캘린더"</string>
+ <string name="edit_event_show_extra_options">"추가 옵션 표시"</string>
+ <string name="edit_event_hide_extra_options">"추가 옵션 숨기기"</string>
+ <string name="description_label">"설명"</string>
+ <string name="presence_label">"상태"</string>
+ <string name="privacy_label">"비공개"</string>
+ <string name="reminders_label">"알림"</string>
+ <string name="no_syncable_calendars">"캘린더 없음"</string>
+ <string name="no_calendars_found">"캘린더가 없습니다."</string>
+ <string name="view_event_calendar_label">"캘린더"</string>
+ <string name="view_event_timezone_label">"현지 시간대"</string>
+ <string name="view_event_response_label">"참석 여부"</string>
+ <string name="agenda_today">"오늘"</string>
+ <string name="num_events">"일정 수"</string>
+ <string name="edit_event_label">"일정 수정"</string>
+ <string name="delete_label">"삭제"</string>
+ <string name="delete_event_label">"일정 삭제"</string>
+ <string name="save_label">"저장"</string>
+ <string name="discard_label">"변경사항 취소"</string>
+ <string name="import_label">"가져오기"</string>
+ <string name="snooze_all_label">"모든 알림 일시중지"</string>
+ <string name="dismiss_all_label">"모두 해제"</string>
+ <string name="does_not_repeat">"일회성 일정"</string>
+ <string name="daily">"매일"</string>
+ <string name="every_weekday">"주중 매일(월-금)"</string>
+ <string name="weekly">"매주(<xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"매월(<xliff:g id="DAY_OF_MONTH">%s</xliff:g>일)"</string>
+ <string name="yearly">"매년(<xliff:g id="DATES">%s</xliff:g>일"</string>
+ <string name="custom">"사용자설정...(휴대전화에서는 불가능)"</string>
+ <string name="modify_event">"해당 일정만 변경합니다."</string>
+ <string name="modify_all">"반복되는 모든 일정을 변경합니다."</string>
+ <string name="modify_all_following">"현재 일정 및 모든 향후 일정을 변경합니다."</string>
+ <string name="delete_this_event_title">"일정이 삭제됩니다."</string>
+ <string name="delete_title">"삭제"</string>
+ <string name="change_response_title">"응답 변경"</string>
+ <string name="preferences_title">"설정"</string>
+ <string name="preferences_general_title">"캘린더 보기 설정"</string>
+ <string name="preferences_alerts_title">"알림 설정"</string>
+ <string name="preferences_hide_declined_title">"거절한 일정 숨기기"</string>
+ <string name="preferences_alerts_type_title">"알리미 및 알림 설정"</string>
+ <string name="preferences_alerts_type_dialog">"알리미 및 알림"</string>
+ <string name="preferences_alerts_vibrate_title">"진동"</string>
+ <string name="preferences_alerts_ringtone_title">"벨소리 선택"</string>
+ <string name="preferences_default_reminder_title">"기본 알림 설정"</string>
+ <string name="preferences_default_reminder_dialog">"기본 알림 시간"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-land/integers.xml b/res/values-land/integers.xml
new file mode 100644
index 0000000..9ece108
--- /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-nb/arrays.xml b/res/values-nb/arrays.xml
new file mode 100644
index 0000000..0155043
--- /dev/null
+++ b/res/values-nb/arrays.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 minutter"</item>
+ <item>"10 minutter"</item>
+ <item>"15 minutter"</item>
+ <item>"20 minutter"</item>
+ <item>"25 minutter"</item>
+ <item>"30 minutter"</item>
+ <item>"45 minutter"</item>
+ <item>"1 time"</item>
+ <item>"2 timer"</item>
+ <item>"3 timer"</item>
+ <item>"12 timer"</item>
+ <item>"24 timer"</item>
+ <item>"2 dager"</item>
+ <item>"1 uke"</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>"Ingen"</item>
+ <item>"5 minutter"</item>
+ <item>"10 minutter"</item>
+ <item>"15 minutter"</item>
+ <item>"20 minutter"</item>
+ <item>"25 minutter"</item>
+ <item>"30 minutter"</item>
+ <item>"45 minutter"</item>
+ <item>"1 time"</item>
+ <item>"2 timer"</item>
+ <item>"3 timer"</item>
+ <item>"12 timer"</item>
+ <item>"24 timer"</item>
+ <item>"2 dager"</item>
+ <item>"1 uke"</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>"Advarsel"</item>
+ <item>"Varsling i statusfeltet"</item>
+ <item>"Av"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Opptatt"</item>
+ <item>"Tilgjengelig"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Standard"</item>
+ <item>"Privat"</item>
+ <item>"Offentlig"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"søndag"</item>
+ <item>"mandag"</item>
+ <item>"tirsdag"</item>
+ <item>"onsdag"</item>
+ <item>"torsdag"</item>
+ <item>"fredag"</item>
+ <item>"lørdag"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"første"</item>
+ <item>"andre"</item>
+ <item>"tredje"</item>
+ <item>"fjerde"</item>
+ <item>"siste"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Ikke svart)"</item>
+ <item>"Ja"</item>
+ <item>"Kanskje"</item>
+ <item>"Nei"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Ja"</item>
+ <item>"Kanskje"</item>
+ <item>"Nei"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Bare denne hendelsen"</item>
+ <item>"Denne og framtidige hendelser"</item>
+ <item>"Alle hendelser"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Denne og framtidige hendelser"</item>
+ <item>"Alle hendelser"</item>
+ </string-array>
+ <!-- no translation found for change_response_labels:0 (2012815396992847028) -->
+ <!-- no translation found for change_response_labels:1 (140670561403965380) -->
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..3da8d23
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Kalender"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Velkommen til Google-kalenderen!"</font>" "\n"En Google-løsning for å vedlikeholde timeplanen din, bygget på ideen om at å planlegge ting skal være intuitivt, effektivt og tilgjengelig."</string>
+ <string name="what_label">"Hva"</string>
+ <string name="when_label">"Når"</string>
+ <string name="where_label">"Hvor"</string>
+ <string name="repeats_label">"Gjentas"</string>
+ <string name="no_title_label">"(Mangler emne)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"Et minutt"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minutter"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 min"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"En time"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> timer"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"En dag"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dager"</item>
+ </plurals>
+ <string name="show_agenda_view">"Vis agenda"</string>
+ <string name="show_day_view">"Vis dag"</string>
+ <string name="agenda_view">"Agenda"</string>
+ <string name="day_view">"Dag"</string>
+ <string name="week_view">"Uke"</string>
+ <string name="month_view">"Måned"</string>
+ <string name="event_view">"Vis hendelse"</string>
+ <string name="event_create">"Ny hendelse"</string>
+ <string name="event_edit">"Rediger hendelse"</string>
+ <string name="event_delete">"Slett hendelse"</string>
+ <string name="goto_today">"I dag"</string>
+ <string name="menu_select_calendars">"Mine kalendre"</string>
+ <string name="menu_preferences">"Innstillinger"</string>
+ <string name="plus_N_more">"(samt <xliff:g id="MORE_COUNT">%d</xliff:g> til …)"</string>
+ <string name="calendars_title">"Mine kalendre"</string>
+ <string name="add_calendars">"Legg til kalendre"</string>
+ <string name="remove_calendars">"Fjern kalendre"</string>
+ <string name="event_edit_title">"Hendelsesdetaljer"</string>
+ <string name="hint_what">"Navn på hendelse"</string>
+ <string name="hint_where">"Sted"</string>
+ <string name="hint_description">"Beskrivelse av hendelsen"</string>
+ <string name="creating_event">"Lager hendelse…"</string>
+ <string name="saving_event">"Lagrer hendelse…"</string>
+ <string name="loading_calendars_title">"Laster kalendre"</string>
+ <string name="loading_calendars_message">"Laster kalendre…"</string>
+ <string name="alert_when_label">"Hva:"</string>
+ <string name="alert_where_label">"Hvor:"</string>
+ <!-- no translation found for alert_missed_events_single (8042891195314876684) -->
+ <skip />
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> hendelser til)"</string>
+ <string name="event_info_title">"Vis hendelse"</string>
+ <string name="event_info_title_invite">"Møteinvitasjon"</string>
+ <string name="add_new_reminder">"Legg til påminnelse"</string>
+ <string name="edit_event_to_label">"Til"</string>
+ <string name="edit_event_from_label">"Fra"</string>
+ <string name="edit_event_all_day_label">"Hele dagen"</string>
+ <string name="edit_event_calendar_label">"Kalender"</string>
+ <string name="edit_event_show_extra_options">"Vis ekstra valg"</string>
+ <string name="edit_event_hide_extra_options">"Skjul ekstra valg"</string>
+ <string name="description_label">"Beskrivelse"</string>
+ <string name="presence_label">"Vis meg som"</string>
+ <string name="privacy_label">"Personvern"</string>
+ <string name="reminders_label">"Påminnelser"</string>
+ <string name="no_syncable_calendars">"Ingen kalendre"</string>
+ <string name="no_calendars_found">"Du har ingen kalendre."</string>
+ <string name="view_event_calendar_label">"Kalender"</string>
+ <string name="view_event_timezone_label">"Lokal tidssone"</string>
+ <string name="view_event_response_label">"Kommer du?"</string>
+ <string name="agenda_today">"I dag"</string>
+ <string name="num_events">"Ant. hendelser"</string>
+ <string name="edit_event_label">"Rediger hendelse"</string>
+ <string name="delete_label">"Slett"</string>
+ <string name="delete_event_label">"Slett hendelse"</string>
+ <string name="save_label">"Lagre"</string>
+ <string name="discard_label">"Forkast hendelse"</string>
+ <string name="import_label">"Importer"</string>
+ <string name="snooze_all_label">"Slumre alle"</string>
+ <string name="dismiss_all_label">"Skjul alle"</string>
+ <string name="does_not_repeat">"Engangshendelse"</string>
+ <string name="daily">"Daglig"</string>
+ <string name="every_weekday">"Hverdager (man–fre)"</string>
+ <string name="weekly">"Ukentlig (hver <xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Hver måned (hver <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">"Hver måned (den <xliff:g id="DAY_OF_MONTH">%s</xliff:g>.)"</string>
+ <string name="yearly">"Hvert år (<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Egendefinert… (kan ikke settes på telefonen)"</string>
+ <string name="modify_event">"Endre bare denne hendelsen."</string>
+ <string name="modify_all">"Endre alle hendelser i denne serien."</string>
+ <string name="modify_all_following">"Endre denne og alle framtidige hendelser."</string>
+ <string name="delete_this_event_title">"Denne hendelsen vil bli slettet."</string>
+ <string name="delete_title">"Slett"</string>
+ <string name="change_response_title">"Endre svar"</string>
+ <string name="preferences_title">"Innstillinger"</string>
+ <string name="preferences_general_title">"Kalender visningsinnstilling"</string>
+ <string name="preferences_alerts_title">"Innstillinger for påminnelser"</string>
+ <string name="preferences_hide_declined_title">"Skjul avslåtte hendelser"</string>
+ <string name="preferences_alerts_type_title">"Sett varsling og påminnelser"</string>
+ <string name="preferences_alerts_type_dialog">"Varsling og påminnelser"</string>
+ <string name="preferences_alerts_vibrate_title">"Vibrer"</string>
+ <string name="preferences_alerts_ringtone_title">"Velg ringetone"</string>
+ <string name="preferences_default_reminder_title">"Sett standard påminnelse"</string>
+ <string name="preferences_default_reminder_dialog">"Standard påminnelsestid"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml
new file mode 100644
index 0000000..2ee530f
--- /dev/null
+++ b/res/values-nl/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 minuten"</item>
+ <item>"10 minuten"</item>
+ <item>"15 minuten"</item>
+ <item>"20 minuten"</item>
+ <item>"25 minuten"</item>
+ <item>"30 minuten"</item>
+ <item>"45 minuten"</item>
+ <item>"1 uur"</item>
+ <item>"2 uur"</item>
+ <item>"3 uur"</item>
+ <item>"12 uur"</item>
+ <item>"24 uur"</item>
+ <item>"2 dagen"</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>"Geen"</item>
+ <item>"5 minuten"</item>
+ <item>"10 minuten"</item>
+ <item>"15 minuten"</item>
+ <item>"20 minuten"</item>
+ <item>"25 minuten"</item>
+ <item>"30 minuten"</item>
+ <item>"45 minuten"</item>
+ <item>"1 uur"</item>
+ <item>"2 uur"</item>
+ <item>"3 uur"</item>
+ <item>"12 uur"</item>
+ <item>"24 uur"</item>
+ <item>"2 dagen"</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>"Melding"</item>
+ <item>"Statusbalkmelding"</item>
+ <item>"Uit"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Bezet"</item>
+ <item>"Beschikbaar"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Standaard"</item>
+ <item>"Privé"</item>
+ <item>"Openbaar"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"Zondag"</item>
+ <item>"Maandag"</item>
+ <item>"Dinsdag"</item>
+ <item>"Woensdag"</item>
+ <item>"Donderdag"</item>
+ <item>"Vrijdag"</item>
+ <item>"Zaterdag"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"eerste"</item>
+ <item>"tweede"</item>
+ <item>"derde"</item>
+ <item>"vierde"</item>
+ <item>"laatste"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Geen antwoord)"</item>
+ <item>"Ja"</item>
+ <item>"Misschien"</item>
+ <item>"Nee"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Ja"</item>
+ <item>"Misschien"</item>
+ <item>"Nee"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Alleen deze afspraak"</item>
+ <item>"Deze en toekomstige afspraken"</item>
+ <item>"Alle afspraken"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Deze en toekomstige afspraken"</item>
+ <item>"Alle afspraken"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Alleen deze keer"</item>
+ <item>"Alle gebeurtenissen in de reeks"</item>
+ </string-array>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..a3448cb
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Agenda"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Welkom bij Google Agenda!"</font>" "\n" Een Google-benadering om uw planning te beheren, gebaseerd op het idee dat het plannen van afspraken intuïtiever, efficiënter en toegankelijker kan."</string>
+ <string name="what_label">"Wat"</string>
+ <string name="when_label">"Wanneer"</string>
+ <string name="where_label">"Waar"</string>
+ <string name="repeats_label">"Herhaling"</string>
+ <string name="no_title_label">"(Geen onderwerp)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 minuut"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minuten"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 min."</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min."</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 uur"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> uur"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 dag"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dagen"</item>
+ </plurals>
+ <string name="show_agenda_view">"Geef agenda weer"</string>
+ <string name="show_day_view">"Geef dag weer"</string>
+ <string name="agenda_view">"Agenda"</string>
+ <string name="day_view">"Dag"</string>
+ <string name="week_view">"Week"</string>
+ <string name="month_view">"Maand"</string>
+ <string name="event_view">"Afspraak weergeven"</string>
+ <string name="event_create">"Nieuwe afspraak"</string>
+ <string name="event_edit">"Afspraak bewerken"</string>
+ <string name="event_delete">"Afspraak verwijderen"</string>
+ <string name="goto_today">"Vandaag"</string>
+ <string name="menu_select_calendars">"Mijn agenda\'s"</string>
+ <string name="menu_preferences">"Instellingen"</string>
+ <string name="plus_N_more">"(plus <xliff:g id="MORE_COUNT">%d</xliff:g> extra …)"</string>
+ <string name="calendars_title">"Mijn agenda\'s"</string>
+ <string name="add_calendars">"Agenda\'s toevoegen"</string>
+ <string name="remove_calendars">"Agenda\'s verwijderen"</string>
+ <string name="event_edit_title">"Afspraakgegevens"</string>
+ <string name="hint_what">"Afspraaknaam"</string>
+ <string name="hint_where">"Locatie van de afspraak"</string>
+ <string name="hint_description">"Beschrijving van afspraak"</string>
+ <string name="creating_event">"Afspraak wordt gemaakt…"</string>
+ <string name="saving_event">"Afspraak wordt opgeslagen..."</string>
+ <string name="loading_calendars_title">"Agenda\'s worden geladen"</string>
+ <string name="loading_calendars_message">"Agenda\'s worden geladen"</string>
+ <string name="alert_when_label">"Wanneer:"</string>
+ <string name="alert_where_label">"Waar:"</string>
+ <string name="alert_missed_events_single">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> extra herinnering)"</string>
+ <string name="alert_missed_events_multiple">"(<xliff:g id="REMINDER_COUNT">%s</xliff:g> extra herinneringen)"</string>
+ <string name="event_info_title">"Afspraak weergeven"</string>
+ <string name="event_info_title_invite">"Uitnodiging voor vergadering"</string>
+ <string name="add_new_reminder">"Herinnering toevoegen"</string>
+ <string name="edit_event_to_label">"Tot"</string>
+ <string name="edit_event_from_label">"Van"</string>
+ <string name="edit_event_all_day_label">"De hele dag"</string>
+ <string name="edit_event_calendar_label">"Agenda"</string>
+ <string name="edit_event_show_extra_options">"Extra opties weergeven"</string>
+ <string name="edit_event_hide_extra_options">"Extra opties verbergen"</string>
+ <string name="description_label">"Beschrijving"</string>
+ <string name="presence_label">"Beschikbaarheid"</string>
+ <string name="privacy_label">"Privacy"</string>
+ <string name="reminders_label">"Herinneringen"</string>
+ <string name="no_syncable_calendars">"Geen agenda\'s"</string>
+ <string name="no_calendars_found">"U heeft geen agenda\'s."</string>
+ <string name="view_event_calendar_label">"Agenda"</string>
+ <string name="view_event_timezone_label">"Lokale tijdzone"</string>
+ <string name="view_event_response_label">"Aanwezig?"</string>
+ <string name="agenda_today">"Vandaag"</string>
+ <string name="num_events">"Aantal afspraken"</string>
+ <string name="edit_event_label">"Afspraak bewerken"</string>
+ <string name="delete_label">"Verwijderen"</string>
+ <string name="delete_event_label">"Afspraak verwijderen"</string>
+ <string name="save_label">"Opslaan"</string>
+ <string name="discard_label">"Wijzigingen annuleren"</string>
+ <string name="import_label">"Importeren"</string>
+ <string name="snooze_all_label">"5 min. uitstellen"</string>
+ <string name="dismiss_all_label">"Allemaal verwijderen"</string>
+ <string name="does_not_repeat">"Eenmalige afspraak"</string>
+ <string name="daily">"Dagelijks"</string>
+ <string name="every_weekday">"Elke werkdag (ma - vr)"</string>
+ <string name="weekly">"Wekelijks (elke <xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Maandelijks (elke <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">"Maandelijks (op dag <xliff:g id="DAY_OF_MONTH">%s</xliff:g>)"</string>
+ <string name="yearly">"Jaarlijks (op <xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Aangepast… (kan niet op de telefoon worden aangepast)"</string>
+ <string name="modify_event">"Alleen deze afspraak wijzigen."</string>
+ <string name="modify_all">"Alle afspraken in de reeks wijzigen."</string>
+ <string name="modify_all_following">"Deze en alle toekomstige afspraken wijzigen."</string>
+ <string name="delete_this_event_title">"Deze afspraak wordt verwijderd."</string>
+ <string name="delete_title">"Verwijderen"</string>
+ <string name="change_response_title">"Reactie wijzigen"</string>
+ <string name="preferences_title">"Instellingen"</string>
+ <string name="preferences_general_title">"Instelling agendaweergave"</string>
+ <string name="preferences_alerts_title">"Instellingen voor herinneringen"</string>
+ <string name="preferences_hide_declined_title">"Geweigerde afspraken verbergen"</string>
+ <string name="preferences_alerts_type_title">"Meldingen instellen"</string>
+ <string name="preferences_alerts_type_dialog">"Meldingen en berichten"</string>
+ <string name="preferences_alerts_vibrate_title">"Trillen"</string>
+ <string name="preferences_alerts_ringtone_title">"Ringtone selecteren"</string>
+ <string name="preferences_default_reminder_title">"Standaardherinnering instellen"</string>
+ <string name="preferences_default_reminder_dialog">"Standaard herinneringstijd"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml
new file mode 100644
index 0000000..d81349d
--- /dev/null
+++ b/res/values-pl/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 min"</item>
+ <item>"10 min"</item>
+ <item>"15 min"</item>
+ <item>"20 min"</item>
+ <item>"25 min"</item>
+ <item>"30 min"</item>
+ <item>"45 min"</item>
+ <item>"1 godzina"</item>
+ <item>"2 godziny"</item>
+ <item>"3 godziny"</item>
+ <item>"12 godzin"</item>
+ <item>"24 godziny"</item>
+ <item>"2 dni"</item>
+ <item>"1 tydzień"</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>"Brak"</item>
+ <item>"5 min"</item>
+ <item>"10 min"</item>
+ <item>"15 min"</item>
+ <item>"20 min"</item>
+ <item>"25 min"</item>
+ <item>"30 min"</item>
+ <item>"45 min"</item>
+ <item>"1 godzina"</item>
+ <item>"2 godziny"</item>
+ <item>"3 godziny"</item>
+ <item>"12 godzin"</item>
+ <item>"24 godziny"</item>
+ <item>"2 dni"</item>
+ <item>"1 tydzień"</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>"Powiadomienie na pasku stanu"</item>
+ <item>"Wyłączone"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Zajęty"</item>
+ <item>"Dostępny"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"Domyślne"</item>
+ <item>"Prywatny"</item>
+ <item>"Publiczny"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"niedziela"</item>
+ <item>"poniedziałek"</item>
+ <item>"wtorek"</item>
+ <item>"środa"</item>
+ <item>"czwartek"</item>
+ <item>"piątek"</item>
+ <item>"sobota"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"1."</item>
+ <item>"2."</item>
+ <item>"3."</item>
+ <item>"4."</item>
+ <item>"ostatni"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(Brak odpowiedzi)"</item>
+ <item>"Tak"</item>
+ <item>"Możliwe"</item>
+ <item>"Nie"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Tak"</item>
+ <item>"Możliwe"</item>
+ <item>"Nie"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Tylko to wydarzenie"</item>
+ <item>"To i przyszłe wydarzenia"</item>
+ <item>"Wszystkie wydarzenia"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"To i przyszłe wydarzenia"</item>
+ <item>"Wszystkie wydarzenia"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Tylko to wystąpienie"</item>
+ <item>"Wszystkie wydarzenia w serii"</item>
+ </string-array>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..168d59b
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Kalendarz"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Witamy w Kalendarzu Google!"</font>" "\n"Intuicyjne, wydajne i dostępne planowanie wydarzeń w celu zarządzania terminami według Google."</string>
+ <string name="what_label">"Co"</string>
+ <string name="when_label">"Kiedy"</string>
+ <string name="where_label">"Gdzie"</string>
+ <string name="repeats_label">"Powtórzenie"</string>
+ <string name="no_title_label">"(Brak tematu)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 min"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 min"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> min"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 godzina"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> godz."</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 dzień"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dni"</item>
+ </plurals>
+ <string name="show_agenda_view">"Pokaż plan dnia"</string>
+ <string name="show_day_view">"Pokaż dzień"</string>
+ <string name="agenda_view">"Plan dnia"</string>
+ <string name="day_view">"Dzień"</string>
+ <string name="week_view">"Tydzień"</string>
+ <string name="month_view">"Miesiąc"</string>
+ <string name="event_view">"Wyświetl wydarzenie"</string>
+ <string name="event_create">"Nowe wydarzenie"</string>
+ <string name="event_edit">"Edytuj wydarzenie"</string>
+ <string name="event_delete">"Usuń wydarzenie"</string>
+ <string name="goto_today">"Dzisiaj"</string>
+ <string name="menu_select_calendars">"Moje kalendarze"</string>
+ <string name="menu_preferences">"Ustawienia"</string>
+ <string name="plus_N_more">"(i <xliff:g id="MORE_COUNT">%d</xliff:g> więcej …)"</string>
+ <string name="calendars_title">"Moje kalendarze"</string>
+ <string name="add_calendars">"Dodaj kalendarze"</string>
+ <string name="remove_calendars">"Usuń kalendarze"</string>
+ <string name="event_edit_title">"Szczegóły wydarzenia"</string>
+ <string name="hint_what">"Nazwa wydarzenia"</string>
+ <string name="hint_where">"Lokalizacja wydarzenia"</string>
+ <string name="hint_description">"Opis wydarzenia"</string>
+ <string name="creating_event">"Tworzenie wydarzenia..."</string>
+ <string name="saving_event">"Zapisywanie wydarzenia..."</string>
+ <string name="loading_calendars_title">"Ładowanie kalendarzy"</string>
+ <string name="loading_calendars_message">"Ładowanie kalendarzy..."</string>
+ <string name="alert_when_label">"Kiedy:"</string>
+ <string name="alert_where_label">"Gdzie:"</string>
+ <string name="alert_missed_events_single">"(więcej przypomnień: <xliff:g id="REMINDER_COUNT">%s</xliff:g>)"</string>
+ <string name="alert_missed_events_multiple">"(więcej przypomnień: <xliff:g id="REMINDER_COUNT">%s</xliff:g>)"</string>
+ <string name="event_info_title">"Wyświetl wydarzenie"</string>
+ <string name="event_info_title_invite">"Zaproszenie na spotkanie"</string>
+ <string name="add_new_reminder">"Dodaj przypomnienie"</string>
+ <string name="edit_event_to_label">"Do"</string>
+ <string name="edit_event_from_label">"Od"</string>
+ <string name="edit_event_all_day_label">"Cały dzień"</string>
+ <string name="edit_event_calendar_label">"Kalendarz"</string>
+ <string name="edit_event_show_extra_options">"Pokaż dodatkowe opcje"</string>
+ <string name="edit_event_hide_extra_options">"Ukryj dodatkowe opcje"</string>
+ <string name="description_label">"Opis"</string>
+ <string name="presence_label">"Obecność"</string>
+ <string name="privacy_label">"Prywatność"</string>
+ <string name="reminders_label">"Przypomnienia"</string>
+ <string name="no_syncable_calendars">"Brak kalendarzy"</string>
+ <string name="no_calendars_found">"Nie masz kalendarzy."</string>
+ <string name="view_event_calendar_label">"Kalendarz"</string>
+ <string name="view_event_timezone_label">"Lokalna strefa czasowa"</string>
+ <string name="view_event_response_label">"Uczestnictwo"</string>
+ <string name="agenda_today">"Dzisiaj"</string>
+ <string name="num_events">"Liczba wydarzeń"</string>
+ <string name="edit_event_label">"Edytuj wydarzenie"</string>
+ <string name="delete_label">"Usuń"</string>
+ <string name="delete_event_label">"Usuń wydarzenie"</string>
+ <string name="save_label">"Zapisz"</string>
+ <string name="discard_label">"Odrzuć zmiany"</string>
+ <string name="import_label">"Importuj"</string>
+ <string name="snooze_all_label">"Odłóż wszystkie"</string>
+ <string name="dismiss_all_label">"Zamknij wszystkie"</string>
+ <string name="does_not_repeat">"Wydarzenie jednorazowe"</string>
+ <string name="daily">"Codziennie"</string>
+ <string name="every_weekday">"Codziennie (pn-pt)"</string>
+ <string name="weekly">"Co tydzień (<xliff:g id="DAYS_OF_WEEK">%s</xliff:g>)"</string>
+ <string name="monthly_on_day_count">"Co miesiąc (co <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">"Co miesiąc (<xliff:g id="DAY_OF_MONTH">%s</xliff:g> każdego miesiąca)"</string>
+ <string name="yearly">"Co rok (<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Niestandardowo… (nie można dostosować na telefonie)"</string>
+ <string name="modify_event">"Zmień tylko to wydarzenie."</string>
+ <string name="modify_all">"Zmień wszystkie wydarzenia w serii."</string>
+ <string name="modify_all_following">"Zmień to i wszystkie przyszłe wydarzenia."</string>
+ <string name="delete_this_event_title">"To wydarzenie zostanie usunięte."</string>
+ <string name="delete_title">"Usuń"</string>
+ <string name="change_response_title">"Zmień odpowiedź"</string>
+ <string name="preferences_title">"Ustawienia"</string>
+ <string name="preferences_general_title">"Widok kalendarza"</string>
+ <string name="preferences_alerts_title">"Ustawienia przypomnień"</string>
+ <string name="preferences_hide_declined_title">"Ukryj odrzucone"</string>
+ <string name="preferences_alerts_type_title">"Alerty i powiadomienia"</string>
+ <string name="preferences_alerts_type_dialog">"Alerty i powiadomienia"</string>
+ <string name="preferences_alerts_vibrate_title">"Wibracje"</string>
+ <string name="preferences_alerts_ringtone_title">"Wybierz dzwonek"</string>
+ <string name="preferences_default_reminder_title">"Domyślne przypomnienie"</string>
+ <string name="preferences_default_reminder_dialog">"Domyślne przypomnienie"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
new file mode 100644
index 0000000..21c026c
--- /dev/null
+++ b/res/values-ru/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 минут"</item>
+ <item>"10 минут"</item>
+ <item>"15 минут"</item>
+ <item>"20 минут"</item>
+ <item>"25 минут"</item>
+ <item>"30 минут"</item>
+ <item>"45 минут"</item>
+ <item>"1 час"</item>
+ <item>"2 часа"</item>
+ <item>"3 часа"</item>
+ <item>"12 часов"</item>
+ <item>"24 часа"</item>
+ <item>"2 дня"</item>
+ <item>"1 неделя"</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>"Нет"</item>
+ <item>"5 минут"</item>
+ <item>"10 минут"</item>
+ <item>"15 минут"</item>
+ <item>"20 минут"</item>
+ <item>"25 минут"</item>
+ <item>"30 минут"</item>
+ <item>"45 минут"</item>
+ <item>"1 час"</item>
+ <item>"2 часа"</item>
+ <item>"3 часа"</item>
+ <item>"12 часов"</item>
+ <item>"24 часа"</item>
+ <item>"2 дня"</item>
+ <item>"1 неделя"</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>"Оповещение"</item>
+ <item>"Уведомление в строке состояния"</item>
+ <item>"Выкл"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"Занято"</item>
+ <item>"Свободно"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"По умолчанию"</item>
+ <item>"Закрыт."</item>
+ <item>"Открыт."</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"Воскресенье"</item>
+ <item>"Понедельник"</item>
+ <item>"Вторник"</item>
+ <item>"Среда"</item>
+ <item>"Четверг"</item>
+ <item>"Пятница"</item>
+ <item>"Суббота"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"перв."</item>
+ <item>"втор."</item>
+ <item>"трет."</item>
+ <item>"четверт."</item>
+ <item>"посл."</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(нет ответа)"</item>
+ <item>"Да"</item>
+ <item>"Может быть"</item>
+ <item>"Нет"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"Да"</item>
+ <item>"Может быть"</item>
+ <item>"Нет"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"Только это мероприятие"</item>
+ <item>"Это и будущие события"</item>
+ <item>"Все мероприятия"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"Это и будущие мероприятия"</item>
+ <item>"Все мероприятия"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"Только этот экземпляр"</item>
+ <item>"Все экземпляры события"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..6eba53a
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Календарь"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"Добро пожаловать в Календарь Google!"</font>" "\n"Средство Google для управления расписаниями, основанное на идее о том, что планирование мероприятий может быть более интуитивным, эффективным и доступным."</string>
+ <string name="what_label">"Что"</string>
+ <string name="when_label">"Когда"</string>
+ <string name="where_label">"Где"</string>
+ <string name="repeats_label">"Повторение"</string>
+ <string name="no_title_label">"(Без темы)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 минут"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> мин."</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 мин."</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> мин."</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 час"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> ч."</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 день"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> дн."</item>
+ </plurals>
+ <string name="show_agenda_view">"Показать в режиме просмотра повестки дня"</string>
+ <string name="show_day_view">"Показать в режиме просмотра дня"</string>
+ <string name="agenda_view">"Повестка дня"</string>
+ <string name="day_view">"День"</string>
+ <string name="week_view">"Неделя"</string>
+ <string name="month_view">"Месяц"</string>
+ <string name="event_view">"Просмотреть мероприятие"</string>
+ <string name="event_create">"Создать мероприятие"</string>
+ <string name="event_edit">"Изменить мероприятие"</string>
+ <string name="event_delete">"Удалить мероприятие"</string>
+ <string name="goto_today">"Сегодня"</string>
+ <string name="menu_select_calendars">"Мои календари"</string>
+ <string name="menu_preferences">"Настройки"</string>
+ <string name="plus_N_more">"(и еще <xliff:g id="MORE_COUNT">%d</xliff:g>)"</string>
+ <string name="calendars_title">"Мои календари"</string>
+ <string name="add_calendars">"Добавить календари"</string>
+ <string name="remove_calendars">"Удаление календарей"</string>
+ <string name="event_edit_title">"Сведения о мероприятии"</string>
+ <string name="hint_what">"Название мероприятия"</string>
+ <string name="hint_where">"Место для мероприятия"</string>
+ <string name="hint_description">"Описание мероприятия"</string>
+ <string name="creating_event">"Идет создание мероприятия..."</string>
+ <string name="saving_event">"Идет сохранение мероприятия..."</string>
+ <string name="loading_calendars_title">"Загрузка календарей"</string>
+ <string name="loading_calendars_message">"Идет загрузка календарей..."</string>
+ <string name="alert_when_label">"Когда:"</string>
+ <string name="alert_where_label">"Где:"</string>
+ <string name="alert_missed_events_single">"(дополнительное уведомление: <xliff:g id="REMINDER_COUNT">%s</xliff:g>)"</string>
+ <string name="alert_missed_events_multiple">"(дополнительные уведомления: <xliff:g id="REMINDER_COUNT">%s</xliff:g>)"</string>
+ <string name="event_info_title">"Просмотр мероприятия"</string>
+ <string name="event_info_title_invite">"Приглашение на встречу"</string>
+ <string name="add_new_reminder">"Добавить напоминание"</string>
+ <string name="edit_event_to_label">"по"</string>
+ <string name="edit_event_from_label">"С"</string>
+ <string name="edit_event_all_day_label">"Весь день"</string>
+ <string name="edit_event_calendar_label">"Календарь"</string>
+ <string name="edit_event_show_extra_options">"Показать дополнительные параметры"</string>
+ <string name="edit_event_hide_extra_options">"Скрыть дополнительные параметры"</string>
+ <string name="description_label">"Описание"</string>
+ <string name="presence_label">"Наличие"</string>
+ <string name="privacy_label">"Конфиденциальность"</string>
+ <string name="reminders_label">"Напоминания"</string>
+ <string name="no_syncable_calendars">"Нет календарей"</string>
+ <string name="no_calendars_found">"Календари отсутствуют."</string>
+ <string name="view_event_calendar_label">"Календарь"</string>
+ <string name="view_event_timezone_label">"Локальный часовой пояс"</string>
+ <string name="view_event_response_label">"Идете?"</string>
+ <string name="agenda_today">"Сегодня"</string>
+ <string name="num_events">"Количество мероприятий"</string>
+ <string name="edit_event_label">"Изменить мероприятие"</string>
+ <string name="delete_label">"Удалить"</string>
+ <string name="delete_event_label">"Удалить мероприятие"</string>
+ <string name="save_label">"Сохранить"</string>
+ <string name="discard_label">"Не сохранять изменения"</string>
+ <string name="import_label">"Импорт"</string>
+ <string name="snooze_all_label">"Отложить все"</string>
+ <string name="dismiss_all_label">"Отклонить все"</string>
+ <string name="does_not_repeat">"Однократное мероприятие"</string>
+ <string name="daily">"Каждый день"</string>
+ <string name="every_weekday">"Каждый рабочий день (понедельник—пятница)"</string>
+ <string name="weekly">"Каждую неделю (день недели: <xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"Ежемесячно (в <xliff:g id="DAY_OF_MONTH">%s</xliff:g> день)"</string>
+ <string name="yearly">"Каждый год (<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"Пользовательский вариант... (настройка на телефоне невозможна)"</string>
+ <string name="modify_event">"Изменить только это мероприятие."</string>
+ <string name="modify_all">"Изменить все мероприятия серии."</string>
+ <string name="modify_all_following">"Изменить это и все будущие мероприятия"</string>
+ <string name="delete_this_event_title">"Это мероприятие будет удалено."</string>
+ <string name="delete_title">"Удалить"</string>
+ <string name="change_response_title">"Изменение ответа"</string>
+ <string name="preferences_title">"Настройки"</string>
+ <string name="preferences_general_title">"Настройка режимов просмотра календаря"</string>
+ <string name="preferences_alerts_title">"Настройки напоминания"</string>
+ <string name="preferences_hide_declined_title">"Скрыть отклоненные мероприятия"</string>
+ <string name="preferences_alerts_type_title">"Настроить оповещения и уведомления"</string>
+ <string name="preferences_alerts_type_dialog">"Оповещения и уведомления"</string>
+ <string name="preferences_alerts_vibrate_title">"Вибрировать"</string>
+ <string name="preferences_alerts_ringtone_title">"Выбор мелодию звонка"</string>
+ <string name="preferences_default_reminder_title">"Установить напоминание по умолчанию"</string>
+ <string name="preferences_default_reminder_dialog">"Время напоминания по умолчанию"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..8470d4e
--- /dev/null
+++ b/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 分钟"</item>
+ <item>"10 分钟"</item>
+ <item>"15 分钟"</item>
+ <item>"20 分钟"</item>
+ <item>"25 分钟"</item>
+ <item>"30 分钟"</item>
+ <item>"45 分钟"</item>
+ <item>"1 小时"</item>
+ <item>"2 小时"</item>
+ <item>"3 小时"</item>
+ <item>"12 小时"</item>
+ <item>"24 小时"</item>
+ <item>"2 天"</item>
+ <item>"1 周"</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>"无"</item>
+ <item>"5 分钟"</item>
+ <item>"10 分钟"</item>
+ <item>"15 分钟"</item>
+ <item>"20 分钟"</item>
+ <item>"25 分钟"</item>
+ <item>"30 分钟"</item>
+ <item>"45 分钟"</item>
+ <item>"1 小时"</item>
+ <item>"2 小时"</item>
+ <item>"3 小时"</item>
+ <item>"12 小时"</item>
+ <item>"24 小时"</item>
+ <item>"2 天"</item>
+ <item>"1 周"</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>"提醒"</item>
+ <item>"状态栏通知"</item>
+ <item>"关闭"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"忙碌"</item>
+ <item>"有空"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"默认"</item>
+ <item>"私人"</item>
+ <item>"公共"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"星期日"</item>
+ <item>"星期一"</item>
+ <item>"星期二"</item>
+ <item>"星期三"</item>
+ <item>"星期四"</item>
+ <item>"星期五"</item>
+ <item>"星期六"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"第一个"</item>
+ <item>"第二个"</item>
+ <item>"第三个"</item>
+ <item>"第四个"</item>
+ <item>"上一"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(未响应)"</item>
+ <item>"是"</item>
+ <item>"不确定"</item>
+ <item>"否"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"是"</item>
+ <item>"不确定"</item>
+ <item>"否"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"仅此活动"</item>
+ <item>"此活动和将来的活动"</item>
+ <item>"所有活动"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"此活动和将来的活动"</item>
+ <item>"所有活动"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"仅此例"</item>
+ <item>"所有此类活动"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..9645f48
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"日历"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"欢迎使用 Google 日历!"</font>" "\n"Google 日历是一种用于管理您的日程安排的工具,它独到的设计理念能让您更加直接、高效和方便地安排活动。"</string>
+ <string name="what_label">"活动"</string>
+ <string name="when_label">"时间"</string>
+ <string name="where_label">"地点"</string>
+ <string name="repeats_label">"重复频率"</string>
+ <string name="no_title_label">"(无主题)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 分钟"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 分钟"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分钟"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 小时"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小时"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 天"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天"</item>
+ </plurals>
+ <string name="show_agenda_view">"显示日程"</string>
+ <string name="show_day_view">"显示日期"</string>
+ <string name="agenda_view">"日程"</string>
+ <string name="day_view">"天"</string>
+ <string name="week_view">"周"</string>
+ <string name="month_view">"月"</string>
+ <string name="event_view">"查看活动"</string>
+ <string name="event_create">"新建活动"</string>
+ <string name="event_edit">"编辑活动"</string>
+ <string name="event_delete">"删除活动"</string>
+ <string name="goto_today">"今天"</string>
+ <string name="menu_select_calendars">"我的日历"</string>
+ <string name="menu_preferences">"设置"</string>
+ <string name="plus_N_more">"(还有 <xliff:g id="MORE_COUNT">%d</xliff:g> 个未显示...)"</string>
+ <string name="calendars_title">"我的日历"</string>
+ <string name="add_calendars">"添加日历"</string>
+ <string name="remove_calendars">"删除日历"</string>
+ <string name="event_edit_title">"活动详情"</string>
+ <string name="hint_what">"活动名称"</string>
+ <string name="hint_where">"活动地点"</string>
+ <string name="hint_description">"活动描述"</string>
+ <string name="creating_event">"正在创建活动..."</string>
+ <string name="saving_event">"正在保存活动..."</string>
+ <string name="loading_calendars_title">"载入日历"</string>
+ <string name="loading_calendars_message">"正在载入日历..."</string>
+ <string name="alert_when_label">"时间:"</string>
+ <string name="alert_where_label">"地点:"</string>
+ <string name="alert_missed_events_single">"(还有 <xliff:g id="REMINDER_COUNT">%s</xliff:g> 个提醒)"</string>
+ <string name="alert_missed_events_multiple">"(还有 <xliff:g id="REMINDER_COUNT">%s</xliff:g> 个提醒)"</string>
+ <string name="event_info_title">"查看活动"</string>
+ <string name="event_info_title_invite">"会议邀请"</string>
+ <string name="add_new_reminder">"添加提醒"</string>
+ <string name="edit_event_to_label">"结束日期"</string>
+ <string name="edit_event_from_label">"开始日期"</string>
+ <string name="edit_event_all_day_label">"全天"</string>
+ <string name="edit_event_calendar_label">"日历"</string>
+ <string name="edit_event_show_extra_options">"显示其他选项"</string>
+ <string name="edit_event_hide_extra_options">"隐藏其他选项"</string>
+ <string name="description_label">"描述"</string>
+ <string name="presence_label">"现状"</string>
+ <string name="privacy_label">"隐私性"</string>
+ <string name="reminders_label">"提醒"</string>
+ <string name="no_syncable_calendars">"无日历"</string>
+ <string name="no_calendars_found">"您没有日历。"</string>
+ <string name="view_event_calendar_label">"日历"</string>
+ <string name="view_event_timezone_label">"本地时区"</string>
+ <string name="view_event_response_label">"是否参加?"</string>
+ <string name="agenda_today">"今天"</string>
+ <string name="num_events">"活动数"</string>
+ <string name="edit_event_label">"编辑活动"</string>
+ <string name="delete_label">"删除"</string>
+ <string name="delete_event_label">"删除活动"</string>
+ <string name="save_label">"保存"</string>
+ <string name="discard_label">"放弃更改"</string>
+ <string name="import_label">"导入"</string>
+ <string name="snooze_all_label">"全部暂停"</string>
+ <string name="dismiss_all_label">"全部解除"</string>
+ <string name="does_not_repeat">"一次性活动"</string>
+ <string name="daily">"每天"</string>
+ <string name="every_weekday">"每个工作日(周一至周五)"</string>
+ <string name="weekly">"每周(每个<xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"每月(<xliff:g id="DAY_OF_MONTH">%s</xliff:g> 日)"</string>
+ <string name="yearly">"每年(<xliff:g id="DATES">%s</xliff:g>)"</string>
+ <string name="custom">"自定义...(无法通过电话自定义)"</string>
+ <string name="modify_event">"仅更改此活动。"</string>
+ <string name="modify_all">"更改此系列的所有活动。"</string>
+ <string name="modify_all_following">"更改此活动和将来的所有活动。"</string>
+ <string name="delete_this_event_title">"系统将删除此活动。"</string>
+ <string name="delete_title">"删除"</string>
+ <string name="change_response_title">"更改响应"</string>
+ <string name="preferences_title">"设置"</string>
+ <string name="preferences_general_title">"日历视图设置"</string>
+ <string name="preferences_alerts_title">"提醒设置"</string>
+ <string name="preferences_hide_declined_title">"隐藏拒绝的活动"</string>
+ <string name="preferences_alerts_type_title">"设置提醒和通知"</string>
+ <string name="preferences_alerts_type_dialog">"警报和通知"</string>
+ <string name="preferences_alerts_vibrate_title">"振动"</string>
+ <string name="preferences_alerts_ringtone_title">"选择铃声"</string>
+ <string name="preferences_default_reminder_title">"设置默认提醒"</string>
+ <string name="preferences_default_reminder_dialog">"默认提醒时间"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
new file mode 100644
index 0000000..2138eef
--- /dev/null
+++ b/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="reminder_minutes_labels">
+ <item>"5 分鐘"</item>
+ <item>"10 分鐘"</item>
+ <item>"15 分鐘"</item>
+ <item>"20 分鐘"</item>
+ <item>"25 分鐘"</item>
+ <item>"30 分鐘"</item>
+ <item>"45 分鐘"</item>
+ <item>"1 小時"</item>
+ <item>"2 小時"</item>
+ <item>"3 小時"</item>
+ <item>"12 小時"</item>
+ <item>"24 小時"</item>
+ <item>"2 天"</item>
+ <item>"1 週"</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>"無"</item>
+ <item>"5 分鐘"</item>
+ <item>"10 分鐘"</item>
+ <item>"15 分鐘"</item>
+ <item>"20 分鐘"</item>
+ <item>"25 分鐘"</item>
+ <item>"30 分鐘"</item>
+ <item>"45 分鐘"</item>
+ <item>"1 小時"</item>
+ <item>"2 小時"</item>
+ <item>"3 小時"</item>
+ <item>"12 小時"</item>
+ <item>"24 小時"</item>
+ <item>"2 天"</item>
+ <item>"1 週"</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>"警示"</item>
+ <item>"狀態列通知"</item>
+ <item>"關閉"</item>
+ </string-array>
+ <string-array name="preferences_alert_type_values">
+ <item>"0"</item>
+ <item>"1"</item>
+ <item>"2"</item>
+ </string-array>
+ <string-array name="availability">
+ <item>"忙碌"</item>
+ <item>"有空"</item>
+ </string-array>
+ <string-array name="visibility">
+ <item>"預設"</item>
+ <item>"私人"</item>
+ <item>"公開"</item>
+ </string-array>
+ <string-array name="day_labels">
+ <item>"星期日"</item>
+ <item>"星期一"</item>
+ <item>"星期二"</item>
+ <item>"星期三"</item>
+ <item>"星期四"</item>
+ <item>"星期五"</item>
+ <item>"星期六"</item>
+ </string-array>
+ <string-array name="ordinal_labels">
+ <item>"第一個"</item>
+ <item>"第二個"</item>
+ <item>"第三個"</item>
+ <item>"第四個"</item>
+ <item>"最後一個"</item>
+ </string-array>
+ <string-array name="response_labels1">
+ <item>"(無回應)"</item>
+ <item>"是"</item>
+ <item>"不確定"</item>
+ <item>"否"</item>
+ </string-array>
+ <string-array name="response_labels2">
+ <item>"是"</item>
+ <item>"不確定"</item>
+ <item>"否"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels">
+ <item>"僅限此活動"</item>
+ <item>"此活動及未來活動"</item>
+ <item>"所有活動"</item>
+ </string-array>
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>"此活動及未來活動"</item>
+ <item>"所有活動"</item>
+ </string-array>
+ <string-array name="change_response_labels">
+ <item>"僅適用於這一次"</item>
+ <item>"所有後續活動"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..e7ce72f
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">"Google 日曆"</string>
+ <string name="calendar_plug"><font fgcolor="#ffffffff">"歡迎使用 Google 日曆!"</font>\n"Google 提供您管理每日行程的方法,讓您憑直覺快速安排活動,輕鬆存取所有行程。"</string>
+ <string name="what_label">"主題"</string>
+ <string name="when_label">"時間"</string>
+ <string name="where_label">"地點"</string>
+ <string name="repeats_label">"重複頻率"</string>
+ <string name="no_title_label">"(無主題)"</string>
+ <plurals name="Nminutes">
+ <item quantity="one">"1 分鐘"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分鐘"</item>
+ </plurals>
+ <plurals name="Nmins">
+ <item quantity="one">"1 分鐘"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 分鐘"</item>
+ </plurals>
+ <plurals name="Nhours">
+ <item quantity="one">"1 小時"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 小時"</item>
+ </plurals>
+ <plurals name="Ndays">
+ <item quantity="one">"1 天"</item>
+ <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 天"</item>
+ </plurals>
+ <string name="show_agenda_view">"顯示待辦事項"</string>
+ <string name="show_day_view">"顯示天"</string>
+ <string name="agenda_view">"待辦事項"</string>
+ <string name="day_view">"日"</string>
+ <string name="week_view">"週"</string>
+ <string name="month_view">"月"</string>
+ <string name="event_view">"檢視活動"</string>
+ <string name="event_create">"新活動"</string>
+ <string name="event_edit">"編輯活動"</string>
+ <string name="event_delete">"刪除活動"</string>
+ <string name="goto_today">"今日"</string>
+ <string name="menu_select_calendars">"我的日曆"</string>
+ <string name="menu_preferences">"設定"</string>
+ <string name="plus_N_more">"(還有 <xliff:g id="MORE_COUNT">%d</xliff:g> 個...)"</string>
+ <string name="calendars_title">"我的日曆"</string>
+ <string name="add_calendars">"新增日曆"</string>
+ <string name="remove_calendars">"移除日曆"</string>
+ <string name="event_edit_title">"活動詳細資訊"</string>
+ <string name="hint_what">"活動名稱"</string>
+ <string name="hint_where">"活動地點"</string>
+ <string name="hint_description">"活動說明"</string>
+ <string name="creating_event">"正在建立活動..."</string>
+ <string name="saving_event">"正在儲存活動..."</string>
+ <string name="loading_calendars_title">"正在載入日曆"</string>
+ <string name="loading_calendars_message">"正在載入日曆..."</string>
+ <string name="alert_when_label">"時間:"</string>
+ <string name="alert_where_label">"地點:"</string>
+ <string name="alert_missed_events_single">"(還有 <xliff:g id="REMINDER_COUNT">%s</xliff:g> 則提醒)"</string>
+ <string name="alert_missed_events_multiple">"(還有 <xliff:g id="REMINDER_COUNT">%s</xliff:g> 則提醒)"</string>
+ <string name="event_info_title">"檢視活動"</string>
+ <string name="event_info_title_invite">"會議邀請"</string>
+ <string name="add_new_reminder">"新增提醒"</string>
+ <string name="edit_event_to_label">"到"</string>
+ <string name="edit_event_from_label">"從"</string>
+ <string name="edit_event_all_day_label">"全天"</string>
+ <string name="edit_event_calendar_label">"日曆"</string>
+ <string name="edit_event_show_extra_options">"顯示少用選項"</string>
+ <string name="edit_event_hide_extra_options">"隱藏少用選項"</string>
+ <string name="description_label">"說明"</string>
+ <string name="presence_label">"狀態"</string>
+ <string name="privacy_label">"隱私權"</string>
+ <string name="reminders_label">"提醒"</string>
+ <string name="no_syncable_calendars">"無日曆"</string>
+ <string name="no_calendars_found">"您沒有日曆。"</string>
+ <string name="view_event_calendar_label">"日曆"</string>
+ <string name="view_event_timezone_label">"當地時區"</string>
+ <string name="view_event_response_label">"是否參加?"</string>
+ <string name="agenda_today">"今日"</string>
+ <string name="num_events">"活動數"</string>
+ <string name="edit_event_label">"編輯活動"</string>
+ <string name="delete_label">"刪除"</string>
+ <string name="delete_event_label">"刪除活動"</string>
+ <string name="save_label">"儲存"</string>
+ <string name="discard_label">"捨棄變更"</string>
+ <string name="import_label">"匯入"</string>
+ <string name="snooze_all_label">"全部關閉"</string>
+ <string name="dismiss_all_label">"全部關閉"</string>
+ <string name="does_not_repeat">"單次活動"</string>
+ <string name="daily">"每日"</string>
+ <string name="every_weekday">"平日 (週一至週五)"</string>
+ <string name="weekly">"每週 (每<xliff:g id="DAYS_OF_WEEK">%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="monthly_on_day">"每月 (<xliff:g id="DAY_OF_MONTH">%s</xliff:g> 日)"</string>
+ <string name="yearly">"每年 (<xliff:g id="DATES">%s</xliff:g> 日)"</string>
+ <string name="custom">"自訂... (無法在手機上自訂)"</string>
+ <string name="modify_event">"僅變更此活動。"</string>
+ <string name="modify_all">"變更所有重複的活動。"</string>
+ <string name="modify_all_following">"變更此活動及所有未來活動。"</string>
+ <string name="delete_this_event_title">"系統會刪除此活動。"</string>
+ <string name="delete_title">"刪除"</string>
+ <string name="change_response_title">"變更回應"</string>
+ <string name="preferences_title">"設定"</string>
+ <string name="preferences_general_title">"日曆檢視設定"</string>
+ <string name="preferences_alerts_title">"提醒設定"</string>
+ <string name="preferences_hide_declined_title">"隱藏已拒絕的活動"</string>
+ <string name="preferences_alerts_type_title">"設定警示及通知"</string>
+ <string name="preferences_alerts_type_dialog">"警示及通知"</string>
+ <string name="preferences_alerts_vibrate_title">"振動"</string>
+ <string name="preferences_alerts_ringtone_title">"選取鈴聲"</string>
+ <string name="preferences_default_reminder_title">"設定預設提醒"</string>
+ <string name="preferences_default_reminder_dialog">"預設提醒時間"</string>
+ <string name="preferences_default_reminder_default">"10"</string>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..ccdfe78
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,178 @@
+<?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.
+ -->
+ <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_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>
+
+ <string-array name="availability">
+ <item>Busy</item>
+ <item>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 -->
+ <skip />
+ <string-array name="delete_repeating_labels">
+ <item>Only this event</item>
+ <item>This & future events</item>
+ <item>All events</item>
+ </string-array>
+
+ <!-- The corresponding indices are defined in DeleteEventHelper.java -->
+ <!-- This is the same array as above (the "delete_repeating_labels" array,
+ except that the first element "Only this event" is removed. This
+ array exists to work-around a bug in the CalendarProvider and sync
+ code where you can't delete one instance of a repeating event that
+ was created on the phone until that event has been synced to the server.
+ -->
+ <skip />
+ <string-array name="delete_repeating_labels_no_selected">
+ <item>This & future events</item>
+ <item>All events</item>
+ </string-array>
+
+ <!-- The corresponding array indices are defined in EventInfoActivity.java -->
+ <skip />
+ <!-- The list of choices displayed in a dialog box when the user wants to
+ respond to an invitation that repeats. -->
+ <string-array name="change_response_labels">
+ <!-- The choice for responding to only one instance of a repeating event -->
+ <item>Only this instance</item>
+ <!-- The choice for responding to all instances of a repeating event -->
+ <item>All events in the series</item>
+ </string-array>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..32cfc28
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,56 @@
+<?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>
+ <color name="event_background">#ffc3d9ff</color>
+ <color name="panel_text_foreground">#ffffffff</color>
+ <color name="daynames_background">#ffc3d9ff</color>
+ <color name="daynames_text_color">#ff112abb</color>
+ <color name="calendar_event_selected_text_color">#ff000000</color>
+ <color name="calendar_event_text_color">#ffffffff</color>
+
+ <color name="calendar_all_day_background">#ffe8eef7</color>
+ <color name="week_weekend">#ff446688</color>
+ <color name="calendar_hour_label">#ff446688</color>
+ <color name="calendar_ampm_label">#ff446688</color>
+ <color name="calendar_hour_background">#ffe8eef7</color>
+ <color name="calendar_hour_selected">#ffB0B0B0</color>
+ <color name="calendar_date_banner_background">#ffc3d9ff</color>
+ <color name="calendar_date_selected">#ffB0B0B0</color>
+ <color name="calendar_date_banner_text_color">#ff446688</color>
+ <color name="calendar_grid_area_background">#ffffffff</color>
+ <color name="calendar_grid_area_selected">#ffffffcc</color>
+ <color name="calendar_grid_line_horizontal_color">#ffdddddd</color>
+ <color name="calendar_grid_line_vertical_color">#ff5a5a5a</color>
+ <color name="calendar_grid_line_highlight_color">#ff707070</color>
+ <color name="selection">#ffffaa00</color>
+ <color name="pressed">#fffab400</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_banner">#80595959</color>
+ <color name="month_week_banner">#20595959</color>
+
+ <color name="event_center">#ff6bd697</color>
+
+ <drawable name="agenda_item_declined">#88ffffff</drawable>
+</resources>
+
diff --git a/res/values/integers.xml b/res/values/integers.xml
new file mode 100644
index 0000000..ac44790
--- /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 0000000..8ee9a03
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,350 @@
+<?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">
+ <!-- This is the label underneath the icon for Calendar -->
+ <string name="app_label">Calendar</string>
+ <!-- This is the welcome message when a user starts Calendar for the first time -->
+ <string name="calendar_plug"><font fgcolor="#ffffffff">Welcome to Google Calendar!</font>
+ \nA Google approach to managing your schedule built on the idea that scheduling events can be more intuitive, efficient and accessible.
+ </string>
+
+ <!-- Shared Labels. These labels are shared among the activities. -->
+ <skip />
+ <!-- This is the label for the title or name of an event -->
+ <string name="what_label">What</string>
+ <!-- This is the label for the date and time of an event -->
+ <string name="when_label">When</string>
+ <!-- This is the label for the location of an event -->
+ <string name="where_label">Where</string>
+ <!-- Some events repeat daily, weekly, monthly, or yearly. This is the label
+ for all the choices about how often an event repeats (including the choice
+ of not repeating). -->
+ <string name="repeats_label">Repetition</string>
+ <!-- Title of event when no explicit title is specified by the user -->
+ <string name="no_title_label">(No subject)</string>
+
+ <!-- Reminder format strings -->
+ <plurals name="Nminutes">
+ <!-- This is the label for a 1-minute reminder. -->
+ <item quantity="one">1 minute</item>
+ <!-- This is the label for 2 or more minutes. The actual number of
+ minutes is a parameter. -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes</item>
+ </plurals>
+ <!-- We use the abbreviation "mins" instead of "minutes" to keep the string short.
+ This is the number of minutes displayed for a calendar reminder. For example,
+ a reminder of 10 minutes would be displayed as '10 mins'. The translation
+ should use the shortest acceptable abbreviation to save space. -->
+ <plurals name="Nmins">
+ <!-- This is the label for a 1-minute reminder. -->
+ <item quantity="one">1 min</item>
+ <!-- This is the label for 2 or more minutes. The actual number of
+ minutes is a parameter. -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> mins</item>
+ </plurals>
+ <!-- This is the number of hours displayed for a calendar reminder. For example,
+ a reminder of 1 hour would be displayed as '1 hour'. The translation
+ should use the shortest acceptable abbreviation of 'hour' to save space. -->
+ <plurals name="Nhours">
+ <!-- This is the label for a 1-hour reminder. -->
+ <item quantity="one">1 hour</item>
+ <!-- This is the label for a reminder of 2 or more hours. The actual number of
+ hours is a parameter. -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item>
+ </plurals>
+ <!-- This is the number of days displayed for a calendar reminder. For example,
+ a reminder of 2 days would be displayed as '2 days'. The translation
+ should use the shortest acceptable abbreviation of 'day' to save space. -->
+ <plurals name="Ndays">
+ <!-- This is the label for a reminder of 1 day. -->
+ <item quantity="one">1 day</item>
+ <!-- This is the label for a reminder of 2 or more days. The actual number of
+ days is a parameter. -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> days</item>
+ </plurals>
+
+ <!-- Menu items: -->
+ <skip />
+ <!-- This is a label on a context menu item. Pressing this menu item displays the
+ Agenda view. -->
+ <string name="show_agenda_view">"Show agenda"</string>
+ <!-- This is a label on a context menu item. Pressing this menu item displays the
+ Day view. -->
+ <string name="show_day_view">"Show day"</string>
+ <!-- This is a label on a menu item. Pressing this menu item displays the
+ Agenda view. -->
+ <string name="agenda_view">"Agenda"</string>
+ <!-- This is a label on a menu item. Pressing this menu item displays the
+ Day view. -->
+ <string name="day_view">"Day"</string>
+ <!-- This is a label on a menu item. Pressing this menu item displays the
+ Week view. -->
+ <string name="week_view">"Week"</string>
+ <!-- This is a label on a menu item. Pressing this menu item displays the
+ Month view. -->
+ <string name="month_view">"Month"</string>
+ <!-- This is a label on a menu item. Pressing this menu item displays the
+ details of an event. -->
+ <string name="event_view">"View event"</string>
+ <!-- This is a label on a menu item. Pressing this menu item allows the
+ user to create a new event. -->
+ <string name="event_create">"New event"</string>
+ <!-- This is a label on a menu item in a popup window. Pressing this menu
+ item allows the user to edit or change an existing event. -->
+ <string name="event_edit">"Edit event"</string>
+ <!-- This is a label on a menu item in a popup window. Pressing this menu
+ item allows the user to delete an existing event. -->
+ <string name="event_delete">"Delete event"</string>
+ <!-- This is a label on a menu item. Pressing this menu
+ item takes the user to the current day (today) in whatever view
+ (Day view, Week view, Agenda view, Month view) they are currently in. -->
+ <string name="goto_today">"Today"</string>
+ <!-- This is a label on a menu item. Pressing this menu item allows the
+ user to see the list of calendars that he is viewing and to then add
+ or remove selected calendars. -->
+ <string name="menu_select_calendars">"My calendars"</string>
+ <!-- This is a label on a menu item. Pressing this menu item allows the
+ user to view and edit his Settings (or Preferences) -->
+ <string name="menu_preferences">"Settings"</string>
+
+ <!-- Month view -->
+ <skip />
+ <!-- This string is used in a popup window when there are more events than
+ can be displayed so it displays the events that it can and then displays
+ this line so that the user knows how many more events there are that
+ could not be displayed. For example, if there are 6 events but we can
+ only display 4 events then we will display the first 4 events and then
+ show 'plus 2 more' to indicate that there are two more events that we
+ could not display because of lack of room. -->
+ <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> more \u2026)"</string>
+
+ <!-- Select Calendars activity -->
+ <skip />
+ <!-- Title of "My calendars" screen -->
+ <string name="calendars_title">"My calendars"</string>
+ <!-- Menu option for selecting calendars to add -->
+ <string name="add_calendars">"Add calendars"</string>
+ <!-- Menu option for selecting calendars to remove -->
+ <string name="remove_calendars">"Remove calendars"</string>
+
+ <!-- Event edit activity -->
+ <skip />
+ <!-- The title of the screen where the user edits an event -->
+ <string name="event_edit_title">"Event details"</string>
+ <!-- Default value of What field (as a hint to the user) -->
+ <string name="hint_what">"Event name"</string>
+ <!-- Default value of Where field (as a hint to the user) -->
+ <string name="hint_where">"Event location"</string>
+ <!-- Default value of Description field (as a hint to the user) -->
+ <string name="hint_description">"Event description"</string>
+ <string name="creating_event">"Creating event\u2026"</string>
+ <string name="saving_event">"Saving event\u2026"</string>
+ <string name="loading_calendars_title">"Loading calendars"</string>
+ <string name="loading_calendars_message">"Loading calendars\u2026"</string>
+
+ <!-- The alert toast is not actually being displayed for some reason. -->
+ <skip />
+ <!-- The label for the date/time of an event in a popup window -->
+ <string name="alert_when_label">When:</string>
+ <!-- The label for the location of an event in a popup window -->
+ <string name="alert_where_label">Where:</string>
+ <!-- Notification window messages: -->
+ <skip />
+ <!-- This shows the number of additional reminders that were missed. This
+ is the singular case so it displays '1 more reminder' -->
+ <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> more reminder)</string>
+ <!-- This shows the number of additional reminders that were missed. For example,
+ if there were two additional reminders then this would show
+ '2 more reminders' -->
+ <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> more reminders)</string>
+
+ <!-- Event info/edit screen labels:-->
+ <skip />
+ <!-- This is the title of the screen used to view the details of an event -->
+ <string name="event_info_title">View event</string>
+ <!-- This is the title of the screen used to view the details of an invitation
+ to a meeting or event -->
+ <string name="event_info_title_invite">Meeting invitation</string>
+ <!-- Menu item to allow the user to add a reminder to an event -->
+ <string name="add_new_reminder">Add reminder</string>
+
+ <!-- EditEventActivity specific strings: -->
+ <skip />
+ <!-- Events have a start date/time and an end date/time. The display shows
+ the start and end times like 'From start date To end date'. This is the
+ label before the 'end date'. -->
+ <string name="edit_event_to_label">To</string>
+ <!-- Events have a start date/time and an end date/time. The display shows
+ the start and end times like 'From start date To end date'. This is the
+ label before the 'start date'. -->
+ <string name="edit_event_from_label">From</string>
+ <!-- Check box label that specifies if this is an all-day event -->
+ <string name="edit_event_all_day_label">All day</string>
+ <!-- Label for choosing one of the calendars -->
+ <string name="edit_event_calendar_label">Calendar</string>
+ <!-- Menu item to show extra options that are used less often -->
+ <string name="edit_event_show_extra_options">Show extra options</string>
+ <!-- Menu item to hide extra options that are used less often -->
+ <string name="edit_event_hide_extra_options">Hide extra options</string>
+ <!-- Label for the event description -->
+ <string name="description_label">Description</string>
+ <!-- Label for the 'Presence' of an event, which can be either 'busy' (the default)
+ or 'available' -->
+ <string name="presence_label">Presence</string>
+ <!-- Label for the 'Privacy' of an event, which can be either 'private'
+ or 'public' -->
+ <string name="privacy_label">Privacy</string>
+ <!-- Label for the list of reminders for an event -->
+ <string name="reminders_label">Reminders</string>
+ <string name="no_syncable_calendars">No calendars</string>
+ <string name="no_calendars_found">You have no calendars.</string>
+
+ <!-- View Event -->
+ <skip />
+ <!-- Label for which calendar an event is part of -->
+ <string name="view_event_calendar_label">Calendar</string>
+ <!-- Label for the local timezone -->
+ <string name="view_event_timezone_label">Local time zone</string>
+ <!-- Label for whether the user is attending this event. This is shown when
+ a user is invited to a meeting or event. The possible answers are
+ 'yes', 'no', and 'maybe' (and, initially, 'no response'). -->
+ <string name="view_event_response_label">Attending?</string>
+
+ <!-- Agenda View strings -->
+ <skip />
+ <!-- This is shown as part of the heading at the top of a list of today's events. -->
+ <string name="agenda_today">Today</string>
+
+ <!-- ICS Import activity -->
+ <skip />
+ <!-- This is a abbreviation for 'Number of events' and is a label next to
+ an input for the user to enter the number of events to import. -->
+ <string name="num_events">"Num events"</string>
+
+ <!-- Button labels: -->
+ <skip />
+ <!-- This is the title of a popup window that is displayed when the user
+ edits a repeating event. -->
+ <string name="edit_event_label">"Edit event"</string>
+ <!-- The button label for deleting an event -->
+ <string name="delete_label">"Delete"</string>
+ <!-- A menu item for deleting an event -->
+ <string name="delete_event_label">"Delete event"</string>
+ <!-- The button label for saving an event -->
+ <string name="save_label">Save</string>
+ <!-- The button label for discarding changes to an event -->
+ <string name="discard_label">Discard changes</string>
+ <!-- The button label for importing events from another source to the phone -->
+ <string name="import_label">"Import"</string>
+
+ <!-- Button labels on expanded notification reminders: -->
+ <skip />
+ <!-- The button label for making the reminder alarms go away temporarily.
+ The reminder alarms will fire off again in 5 minutes. -->
+ <string name="snooze_all_label">"Snooze all"</string>
+ <!-- The button label for dismissing all the current reminder alarms. This
+ causes them to disappear from the notification screen. -->
+ <string name="dismiss_all_label">"Dismiss all"</string>
+
+ <!-- Repetition dialog options: -->
+ <skip />
+ <!-- A label in a multiple-choice list for the specifying that an event does
+ not repeat -->
+ <string name="does_not_repeat">One-time event</string>
+ <!-- A label in a multiple-choice list for the specifying that an event
+ repeats daily -->
+ <string name="daily">Daily</string>
+ <!-- A label in a multiple-choice list for the specifying that an event
+ repeats every weekday (Monday through Friday) -->
+ <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+ <!-- A label in a multiple-choice list for the specifying that an event
+ repeats weekly on some day of the week. For example,
+ 'Weekly (every Tuesday)'. -->
+ <string name="weekly">"Weekly (every <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+
+ <!-- Example: 'Monthly (every first Sunday)' -->
+ <!-- 1st parameter is an ordinal number, like 'first' -->
+ <!-- 2nd parameter is a day of the week, like '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>
+
+ <!-- Example: "Monthly (on day 15)" -->
+ <string name="monthly_on_day">"Monthly (on day <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+ <!-- Example: "Yearly (on April 15)" -->
+ <string name="yearly">"Yearly (on <xliff:g id="dates">%s</xliff:g>)"</string>
+ <!-- This is a label for telling the user that this event repeats in
+ custom (non-standard) way from the usual repeat rates (such as daily,
+ weekly, etc.) and that the user cannot change this on the phone
+ (but only on the web). -->
+ <string name="custom">"Custom\u2026 (cannot customize on phone)"</string>
+
+ <!-- This is a choice in a list that the user sees when he tries to modify
+ a repeating event. This choice says to change just this one instance
+ of this repeating event. -->
+ <string name="modify_event">Change only this event.</string>
+ <!-- This is a choice in a list that the user sees when he tries to modify
+ a repeating event. This choice says to change all occurrences of
+ this repeating event. -->
+ <string name="modify_all">Change all events in the series.</string>
+ <!-- This is a choice in a list that the user sees when he tries to modify
+ a repeating event. This choice says to change this instance and all
+ future occurrences of this repeating event. -->
+ <string name="modify_all_following">Change this and all future events.</string>
+
+ <!-- Dialogs -->
+ <!-- Confirmation dialog message -->
+ <skip />
+ <!-- This is a confirmation message in a popup dialog that appears when the
+ user has asked to delete an event. The user still has a chance to cancel
+ this operation. -->
+ <string name="delete_this_event_title">This event will be deleted.</string>
+ <!-- This is the title of a popup dialog that asks for confirmation before
+ deleting an event. -->
+ <string name="delete_title">Delete</string>
+ <!-- This is the title of a popup dialog that asks for confirmation before
+ changing the response to an invitation. -->
+ <string name="change_response_title">Change response</string>
+
+ <!-- This is the title of the screen for setting user preferences. -->
+ <string name="preferences_title">"Settings"</string>
+ <!-- This is the title of a section in the Settings screen for settings
+ related to viewing calendar events -->
+ <string name="preferences_general_title">Calendar view setting</string>
+ <!-- This is the title of a section in the Settings screen for settings
+ related to reminders for calendar events -->
+ <string name="preferences_alerts_title">Reminder settings</string>
+ <!-- Settings check box label to hide events that the user has declined to attend -->
+ <string name="preferences_hide_declined_title">Hide declined events</string>
+ <!-- Settings option to bring up a dialog that lets the user choose how
+ reminder notifications should occur. -->
+ <string name="preferences_alerts_type_title">Set alerts & notifications</string>
+ <!-- The title of a popup dialog that lets the user choose how
+ reminder notifications should occur. -->
+ <string name="preferences_alerts_type_dialog">Alerts & notifications</string>
+ <!-- DO NOT TRANSLATE -->
+ <string name="preferences_alerts_type_default">1</string>
+ <!-- Settings check box label that specifies whether the phone should vibrate -->
+ <string name="preferences_alerts_vibrate_title">Vibrate</string>
+ <!-- Title of ringtone selector dialog -->
+ <string name="preferences_alerts_ringtone_title">Select ringtone</string>
+ <!-- List item label for setting the default number of reminder minutes -->
+ <string name="preferences_default_reminder_title">Set default reminder</string>
+ <!-- Title of dialog for setting the default number of reminder minutes -->
+ <string name="preferences_default_reminder_dialog">Default reminder time</string>
+ <!-- Default value for the number of reminder minutes -->
+ <string name="preferences_default_reminder_default">10</string>
+
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..bcc04c2
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,71 @@
+<?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="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">@color/daynames_text_color</item>
+ </style>
+
+ <style name="TextAppearance.AgendaView_ValueLabel">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+
+ <style name="TextAppearance.EditEvent_Label">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+ <style name="TextAppearance.Alert_Title">
+ <item name="android:textSize">18sp</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+ <style name="TextAppearance.Alert_Label">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+ <style name="TextAppearance.Alert_Value">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
new file mode 100644
index 0000000..061170e
--- /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 0000000..fd159e6
--- /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.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.text.format.Time;
+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";
+
+ 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
+ Instances.SELF_ATTENDEE_STATUS, // 12
+ };
+
+ 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 int INDEX_SELF_ATTENDEE_STATUS = 12;
+
+ 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);
+ 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 0000000..6aac218
--- /dev/null
+++ b/src/com/android/calendar/AgendaAdapter.java
@@ -0,0 +1,158 @@
+/*
+ * 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.res.Resources;
+import android.database.Cursor;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Reminders;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class AgendaAdapter extends ResourceCursorAdapter {
+
+ static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders._ID, // 0
+ Reminders.MINUTES, // 1
+ };
+ static final int REMINDERS_INDEX_MINUTES = 1;
+ 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) {
+ // Fade text if event was declined.
+ int selfAttendeeStatus = cursor.getInt(AgendaActivity.INDEX_SELF_ATTENDEE_STATUS);
+ boolean declined = (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED);
+
+ View stripe = view.findViewById(R.id.vertical_stripe);
+ int color = cursor.getInt(AgendaActivity.INDEX_COLOR);
+ ((FrameLayout) view).setForeground(declined ?
+ mResources.getDrawable(R.drawable.agenda_item_declined) : null);
+
+ stripe.setBackgroundColor(color);
+
+ // 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);
+ title.setTextColor(color);
+
+ // 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(context, begin, end, flags);
+ when.setText(whenString);
+
+ String rrule = cursor.getString(AgendaActivity.INDEX_RRULE);
+ if (rrule != null) {
+ when.setCompoundDrawablesWithIntrinsicBounds(null, null,
+ context.getResources().getDrawable(R.drawable.ic_repeat_dark), null);
+ when.setCompoundDrawablePadding(5);
+ } else {
+ when.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+
+ /*
+ // 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 0000000..d774efb
--- /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.text.format.DateUtils;
+import android.text.format.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(mContext, 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 0000000..913bd85
--- /dev/null
+++ b/src/com/android/calendar/AlertActivity.java
@@ -0,0 +1,284 @@
+/*
+ * 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 = getWindow().getAttributes();
+ lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+
+ // 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);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+ 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 0000000..b182ccd
--- /dev/null
+++ b/src/com/android/calendar/AlertAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * 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.text.format.DateFormat;
+import android.text.format.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(context, 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 0000000..20f065c
--- /dev/null
+++ b/src/com/android/calendar/AlertReceiver.java
@@ -0,0 +1,234 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+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());
+
+ // Record the received time in the CalendarAlerts table.
+ // This is useful for finding bugs that cause alarms to be
+ // missed or delayed.
+ ContentResolver cr = context.getContentResolver();
+ ContentValues values = new ContentValues();
+ long currentTime = System.currentTimeMillis();
+ values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
+ cr.update(uri, values, null /* where */, null /* args */);
+ }
+ 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;
+ }
+
+ // Check the settings to see if alerts are disabled
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String reminderType = prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_TYPE,
+ CalendarPreferenceActivity.ALERT_TYPE_STATUS_BAR);
+ if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_OFF)) {
+ 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 0000000..eb70504
--- /dev/null
+++ b/src/com/android/calendar/AlertService.java
@@ -0,0 +1,437 @@
+/*
+ * 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.ContentValues;
+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.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.text.format.DateUtils;
+import android.text.format.Time;
+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 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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "multiple alarms for this event");
+ }
+ numReminders -= 1;
+ }
+ }
+ } finally {
+ alertCursor.close();
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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 (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(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);
+ }
+
+ // Record the notify time in the CalendarAlerts table.
+ // This is used for debugging missed alarms.
+ ContentValues values = new ContentValues();
+ long currentTime = System.currentTimeMillis();
+ values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
+ cr.update(alertUri, values, null /* where */, null /* args */);
+
+ // The notification time should be pretty close to the reminder time
+ // that the user set for this event. If the notification is late, then
+ // that's a bug and we should log an error.
+ if (currentTime > alarmTime + DateUtils.MINUTE_IN_MILLIS) {
+ long minutesLate = (currentTime - alarmTime) / DateUtils.MINUTE_IN_MILLIS;
+ int flags = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_TIME;
+ String alarmTimeStr = DateUtils.formatDateTime(this, alarmTime, flags);
+ String currentTimeStr = DateUtils.formatDateTime(this, currentTime, flags);
+ Log.w(TAG, "Calendar reminder alarm for event id " + eventId
+ + " is " + minutesLate + " minute(s) late;"
+ + " expected alarm at: " + alarmTimeStr
+ + " but got it at: " + currentTimeStr);
+ }
+
+ 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 0000000..639d59c
--- /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.provider.Calendar;
+import android.text.format.Time;
+import android.view.GestureDetector;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.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(this, new CalendarGestureListener());
+ mEventLoader = new EventLoader(this);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
+ Time time = new Time();
+ 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 0000000..a3dad95
--- /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 0000000..49f3056
--- /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 0000000..78d6065
--- /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 0000000..a50c44b
--- /dev/null
+++ b/src/com/android/calendar/CalendarView.java
@@ -0,0 +1,2970 @@
+/*
+ * 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.net.Uri;
+import android.os.Handler;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.TextUtils;
+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.ViewGroup;
+import android.view.WindowManager;
+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 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 mEventTextPaint = new Paint();
+ private Paint mSelectionPaint = new Paint();
+ private Path mPath = new Path();
+
+ protected boolean mDrawTextInEventRect;
+ private int mStartDay;
+
+ private PopupWindow mPopup;
+ private View mPopupView;
+
+ // The number of milliseconds to show the popup window
+ private static final int POPUP_DISMISS_DELAY = 3000;
+ private DismissPopup mDismissPopup = new DismissPopup();
+
+ // For drawing to an off-screen Canvas
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private boolean mRedrawScreen = true;
+ private boolean mRemeasure = true;
+
+ private final EventLoader mEventLoader;
+ protected final EventGeometry mEventGeometry;
+
+ private static final int DAY_GAP = 1;
+ private static final int HOUR_GAP = 1;
+ private static 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 int mSelectionColor;
+ private static int mPressedColor;
+ private static int mSelectedEventTextColor;
+ private static int mEventTextColor;
+
+ private int mViewStartX;
+ private int mViewStartY;
+ private int mMaxViewStartY;
+ private int mBitmapHeight;
+ private int mViewHeight;
+ private int mViewWidth;
+ private int mGridAreaHeight;
+ private int mCellHeight;
+ private int mScrollStartY;
+ private int mPreviousDirection;
+ private int mPreviousDistanceX;
+
+ private int mHoursTextHeight;
+ private int mEventTextAscent;
+ private int mEventTextHeight;
+ private int mAllDayHeight;
+ private int mBannerPlusMargin;
+ private int mMaxAllDayEvents;
+
+ protected int mNumDays = 7;
+ private int mNumHours = 10;
+ private int mHoursWidth;
+ private int mDateStrWidth;
+ private int mFirstCell;
+ private int mFirstHour = -1;
+ private int mFirstHourOffset;
+ private String[] mHourStrs;
+ private String[] mDayStrs;
+ private String[] mDayStrs2Letter;
+ private boolean mIs24HourFormat;
+
+ private float[] mCharWidths = new float[MAX_EVENT_TEXT_LEN];
+ private ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
+ private boolean mComputeSelectedEvents;
+ private Event mSelectedEvent;
+ private Event mPrevSelectedEvent;
+ private Rect mPrevBox = new Rect();
+ protected final Resources mResources;
+ private String mAmString;
+ private String mPmString;
+ private DeleteEventHelper mDeleteEventHelper;
+
+ private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
+
+ /**
+ * The initial state of the touch mode when we enter this view.
+ */
+ private static final int TOUCH_MODE_INITIAL_STATE = 0;
+
+ /**
+ * Indicates we just received the touch event and we are waiting to see if
+ * it is a tap or a scroll gesture.
+ */
+ private static final int TOUCH_MODE_DOWN = 1;
+
+ /**
+ * Indicates the touch gesture is a vertical scroll
+ */
+ private static final int TOUCH_MODE_VSCROLL = 0x20;
+
+ /**
+ * Indicates the touch gesture is a horizontal scroll
+ */
+ private static final int TOUCH_MODE_HSCROLL = 0x40;
+
+ private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
+
+ /**
+ * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+ */
+ private static final int SELECTION_HIDDEN = 0;
+ private static final int SELECTION_PRESSED = 1;
+ private static final int SELECTION_SELECTED = 2;
+ private static final int SELECTION_LONGPRESS = 3;
+
+ private int mSelectionMode = SELECTION_HIDDEN;
+
+ private boolean mScrolling = false;
+
+ private String mDateRange;
+ private TextView mTitleTextView;
+
+ 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);
+ mPressedColor = mResources.getColor(R.color.pressed);
+ mSelectedEventTextColor = mResources.getColor(R.color.calendar_event_selected_text_color);
+ mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
+ mEventTextPaint.setColor(mEventTextColor);
+ mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
+ mEventTextPaint.setTextAlign(Paint.Align.LEFT);
+ mEventTextPaint.setAntiAlias(true);
+
+ int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
+ Paint p = mSelectionPaint;
+ p.setColor(gridLineColor);
+ p.setStyle(Style.STROKE);
+ p.setStrokeWidth(2.0f);
+ p.setAntiAlias(false);
+
+ p = mPaint;
+ p.setAntiAlias(true);
+
+ // 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;
+
+ LayoutInflater inflater;
+ inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPopupView = inflater.inflate(R.layout.bubble_event, null);
+ mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mPopup = new PopupWindow(context);
+ mPopup.setContentView(mPopupView);
+ Resources.Theme dialogTheme = getResources().newTheme();
+ dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
+ TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
+ android.R.attr.windowBackground });
+ mPopup.setBackgroundDrawable(ta.getDrawable(0));
+ ta.recycle();
+
+ // Enable touching the popup window
+ mPopupView.setOnClickListener(this);
+
+ mBaseDate = new Time();
+ 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(mParentActivity, 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;
+ int gridAreaWidth = width - mHoursWidth;
+ mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
+
+ Paint p = new Paint();
+ p.setTextSize(NORMAL_FONT_SIZE);
+ int bannerTextHeight = (int) Math.abs(p.ascent());
+
+ p.setTextSize(HOURS_FONT_SIZE);
+ mHoursTextHeight = (int) Math.abs(p.ascent());
+
+ p.setTextSize(EVENT_TEXT_FONT_SIZE);
+ float ascent = -p.ascent();
+ mEventTextAscent = (int) Math.ceil(ascent);
+ float totalHeight = ascent + p.descent();
+ mEventTextHeight = (int) Math.ceil(totalHeight);
+
+ if (mNumDays > 1) {
+ mBannerPlusMargin = bannerTextHeight + 14;
+ } else {
+ mBannerPlusMargin = 0;
+ }
+
+ remeasure(width, height);
+ }
+
+ // Measures the space needed for various parts of the view after
+ // loading new events. This can change if there are all-day events.
+ private void remeasure(int width, int height) {
+
+ // First, clear the array of earliest start times, and the array
+ // indicating presence of an all-day event.
+ for (int day = 0; day < mNumDays; day++) {
+ mEarliestStartHour[day] = 25; // some big number
+ mHasAllDayEvent[day] = false;
+ }
+
+ // Compute the space needed for the all-day events, if any.
+ // Make a pass over all the events, and keep track of the maximum
+ // number of all-day events in any one day. Also, keep track of
+ // the earliest event in each day.
+ int maxAllDayEvents = 0;
+ ArrayList<Event> events = mEvents;
+ int len = events.size();
+ for (int ii = 0; ii < len; ii++) {
+ Event event = events.get(ii);
+ if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay)
+ continue;
+ if (event.allDay) {
+ int max = event.getColumn() + 1;
+ if (maxAllDayEvents < max) {
+ maxAllDayEvents = max;
+ }
+ int daynum = event.startDay - mFirstJulianDay;
+ int durationDays = event.endDay - event.startDay + 1;
+ if (daynum < 0) {
+ durationDays += daynum;
+ daynum = 0;
+ }
+ if (daynum + durationDays > mNumDays) {
+ durationDays = mNumDays - daynum;
+ }
+ for (int day = daynum; durationDays > 0; day++, durationDays--) {
+ mHasAllDayEvent[day] = true;
+ }
+ } else {
+ int daynum = event.startDay - mFirstJulianDay;
+ int hour = event.startTime / 60;
+ if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
+ mEarliestStartHour[daynum] = hour;
+ }
+
+ // Also check the end hour in case the event spans more than
+ // one day.
+ daynum = event.endDay - mFirstJulianDay;
+ hour = event.endTime / 60;
+ if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
+ mEarliestStartHour[daynum] = hour;
+ }
+ }
+ }
+ mMaxAllDayEvents = maxAllDayEvents;
+
+ mFirstCell = mBannerPlusMargin;
+ int allDayHeight = 0;
+ if (maxAllDayEvents > 0) {
+ // If there is at most one all-day event per day, then use less
+ // space (but more than the space for a single event).
+ if (maxAllDayEvents == 1) {
+ allDayHeight = SINGLE_ALLDAY_HEIGHT;
+ } else {
+ // Allow the all-day area to grow in height depending on the
+ // number of all-day events we need to show, up to a limit.
+ allDayHeight = maxAllDayEvents * MAX_ALLDAY_EVENT_HEIGHT;
+ if (allDayHeight > MAX_ALLDAY_HEIGHT) {
+ allDayHeight = MAX_ALLDAY_HEIGHT;
+ }
+ }
+ mFirstCell = mBannerPlusMargin + allDayHeight + ALLDAY_TOP_MARGIN;
+ } else {
+ mSelectionAllDay = false;
+ }
+ mAllDayHeight = allDayHeight;
+
+ mGridAreaHeight = height - mFirstCell;
+ mCellHeight = (mGridAreaHeight - ((mNumHours + 1) * HOUR_GAP)) / mNumHours;
+ int usedGridAreaHeight = (mCellHeight + HOUR_GAP) * mNumHours + HOUR_GAP;
+ int bottomSpace = mGridAreaHeight - usedGridAreaHeight;
+ mEventGeometry.setHourHeight(mCellHeight);
+
+ // Create an off-screen bitmap that we can draw into.
+ mBitmapHeight = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) + bottomSpace;
+ if ((mBitmap == null || mBitmap.getHeight() < mBitmapHeight) && width > 0 &&
+ mBitmapHeight > 0) {
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = Bitmap.createBitmap(width, mBitmapHeight, Bitmap.Config.RGB_565);
+ mCanvas = new Canvas(mBitmap);
+ }
+ mMaxViewStartY = mBitmapHeight - mGridAreaHeight;
+
+ if (mFirstHour == -1) {
+ initFirstHour();
+ mFirstHourOffset = 0;
+ }
+
+ // When we change the base date, the number of all-day events may
+ // change and that changes the cell height. When we switch dates,
+ // we use the mFirstHourOffset from the previous view, but that may
+ // be too large for the new view if the cell height is smaller.
+ if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
+ mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
+ }
+ mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
+
+ int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
+ mPopup.dismiss();
+ mPopup.setWidth(eventAreaWidth - 20);
+ mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Initialize the state for another view. The given view is one that has
+ * its own bitmap and will use an animation to replace the current view.
+ * The current view and new view are either both Week views or both Day
+ * views. They differ in their base date.
+ *
+ * @param view the view to initialize.
+ */
+ private void initView(CalendarView view) {
+ view.mSelectionHour = mSelectionHour;
+ view.mSelectedEvents.clear();
+ view.mComputeSelectedEvents = true;
+ view.mFirstHour = mFirstHour;
+ view.mFirstHourOffset = mFirstHourOffset;
+ view.remeasure(getWidth(), getHeight());
+
+ view.mSelectedEvent = null;
+ view.mPrevSelectedEvent = null;
+ view.mStartDay = mStartDay;
+ if (view.mEvents.size() > 0) {
+ view.mSelectionAllDay = mSelectionAllDay;
+ } else {
+ view.mSelectionAllDay = false;
+ }
+
+ // Redraw the screen so that the selection box will be redrawn. We may
+ // have scrolled to a different part of the day in some other view
+ // so the selection box in this view may no longer be visible.
+ view.mRedrawScreen = true;
+ view.recalc();
+ }
+
+ /**
+ * Switch to another view based on what was selected (an event or a free
+ * slot) and how it was selected (by touch or by trackball).
+ *
+ * @param trackBallSelection true if the selection was made using the
+ * trackball.
+ */
+ private void switchViews(boolean trackBallSelection) {
+ Event selectedEvent = mSelectedEvent;
+
+ mPopup.dismiss();
+ 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;
+ 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 eventTextPaint = mEventTextPaint;
+
+ // Draw the background for the all-day events area
+ r.top = mBannerPlusMargin;
+ r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
+ r.left = mHoursWidth;
+ r.right = r.left + mNumDays * (mCellWidth + DAY_GAP);
+ p.setColor(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, eventTextPaint);
+ drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
+
+ // Check if this all-day event intersects the selected day
+ if (mSelectionAllDay && mComputeSelectedEvents) {
+ if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
+ mSelectedEvents.add(event);
+ }
+ }
+ }
+
+ if (mSelectionAllDay) {
+ // Compute the neighbors for the list of all-day events that
+ // intersect the selected day.
+ computeAllDayNeighbors();
+ if (mSelectedEvent != null) {
+ Event event = mSelectedEvent;
+ RectF rf = drawAllDayEventRect(event, canvas, p, eventTextPaint);
+ drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
+ }
+
+ // Draw the highlight on the selected all-day area
+ float top = mBannerPlusMargin + 1;
+ float bottom = top + mAllDayHeight + ALLDAY_TOP_MARGIN - 1;
+ int daynum = mSelectionDay - mFirstJulianDay;
+ left = mHoursWidth + daynum * (mCellWidth + DAY_GAP) + 1;
+ float right = left + mCellWidth + DAY_GAP - 1;
+ if (mNumDays == 1) {
+ // The Day view doesn't have a vertical line on the right.
+ right -= 1;
+ }
+ Path path = mPath;
+ path.reset();
+ path.addRect(left, top, right, bottom, Direction.CW);
+ canvas.drawPath(path, mSelectionPaint);
+
+ // Set the selection position to zero so that when we move down
+ // to the normal event area, we will highlight the topmost event.
+ saveSelectionPosition(0f, 0f, 0f, 0f);
+ }
+ }
+
+ private void computeAllDayNeighbors() {
+ int len = mSelectedEvents.size();
+ if (len == 0 || mSelectedEvent != null) {
+ return;
+ }
+
+ // First, clear all the links
+ for (int ii = 0; ii < len; ii++) {
+ Event ev = mSelectedEvents.get(ii);
+ ev.nextUp = null;
+ ev.nextDown = null;
+ ev.nextLeft = null;
+ ev.nextRight = null;
+ }
+
+ // For each event in the selected event list "mSelectedEvents", find
+ // its neighbors in the up and down directions. This could be done
+ // more efficiently by sorting on the Event.getColumn() field, but
+ // the list is expected to be very small.
+
+ // Find the event in the same row as the previously selected all-day
+ // event, if any.
+ int startPosition = -1;
+ if (mPrevSelectedEvent != null && mPrevSelectedEvent.allDay) {
+ startPosition = mPrevSelectedEvent.getColumn();
+ }
+ int maxPosition = -1;
+ Event startEvent = null;
+ Event maxPositionEvent = null;
+ for (int ii = 0; ii < len; ii++) {
+ Event ev = mSelectedEvents.get(ii);
+ int position = ev.getColumn();
+ if (position == startPosition) {
+ startEvent = ev;
+ } else if (position > maxPosition) {
+ maxPositionEvent = ev;
+ maxPosition = position;
+ }
+ for (int jj = 0; jj < len; jj++) {
+ if (jj == ii) {
+ continue;
+ }
+ Event neighbor = mSelectedEvents.get(jj);
+ int neighborPosition = neighbor.getColumn();
+ if (neighborPosition == position - 1) {
+ ev.nextUp = neighbor;
+ } else if (neighborPosition == position + 1) {
+ ev.nextDown = neighbor;
+ }
+ }
+ }
+ if (startEvent != null) {
+ mSelectedEvent = startEvent;
+ } else {
+ mSelectedEvent = maxPositionEvent;
+ }
+ }
+
+ RectF drawAllDayEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
+ // If this event is selected, then use the selection color
+ if (mSelectedEvent == event) {
+ // Also, remember the last selected event that we drew
+ mPrevSelectedEvent = event;
+ p.setColor(mSelectionColor);
+ eventTextPaint.setColor(mSelectedEventTextColor);
+ } else {
+ // Use the normal color for all-day events
+ p.setColor(event.color);
+ eventTextPaint.setColor(mEventTextColor);
+ }
+
+ RectF rf = mRectF;
+ rf.top = event.top;
+ rf.bottom = event.bottom;
+ rf.left = event.left;
+ rf.right = event.right;
+ canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+ rf.left += 2;
+ rf.right -= 2;
+ return rf;
+ }
+
+ private void drawEvents(int date, int left, int top, Canvas canvas, Paint p) {
+ Paint eventTextPaint = mEventTextPaint;
+ int cellWidth = mCellWidth;
+ int cellHeight = mCellHeight;
+
+ // Use the selected hour as the selection region
+ Rect selectionArea = mRect;
+ selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
+ selectionArea.bottom = selectionArea.top + cellHeight;
+ selectionArea.left = left;
+ selectionArea.right = selectionArea.left + cellWidth;
+
+ ArrayList<Event> events = mEvents;
+ int numEvents = events.size();
+ EventGeometry geometry = mEventGeometry;
+
+ for (int i = 0; i < numEvents; i++) {
+ Event event = events.get(i);
+ if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+ continue;
+ }
+
+ if (date == mSelectionDay && !mSelectionAllDay && mComputeSelectedEvents
+ && geometry.eventIntersectsSelection(event, selectionArea)) {
+ mSelectedEvents.add(event);
+ }
+
+ RectF rf = drawEventRect(event, canvas, p, eventTextPaint);
+ drawEventText(event, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
+ }
+
+ if (date == mSelectionDay && !mSelectionAllDay && isFocused()
+ && mSelectionMode != SELECTION_HIDDEN) {
+ computeNeighbors();
+ if (mSelectedEvent != null) {
+ RectF rf = drawEventRect(mSelectedEvent, canvas, p, eventTextPaint);
+ drawEventText(mSelectedEvent, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
+ }
+ }
+ }
+
+ // Computes the "nearest" neighbor event in four directions (left, right,
+ // up, down) for each of the events in the mSelectedEvents array.
+ private void computeNeighbors() {
+ int len = mSelectedEvents.size();
+ if (len == 0 || mSelectedEvent != null) {
+ return;
+ }
+
+ // First, clear all the links
+ for (int ii = 0; ii < len; ii++) {
+ Event ev = mSelectedEvents.get(ii);
+ ev.nextUp = null;
+ ev.nextDown = null;
+ ev.nextLeft = null;
+ ev.nextRight = null;
+ }
+
+ Event startEvent = mSelectedEvents.get(0);
+ int startEventDistance1 = 100000; // any large number
+ int startEventDistance2 = 100000; // any large number
+ int prevLocation = FROM_NONE;
+ int prevTop;
+ int prevBottom;
+ int prevLeft;
+ int prevRight;
+ int prevCenter = 0;
+ Rect box = getCurrentSelectionPosition();
+ if (mPrevSelectedEvent != null) {
+ prevTop = (int) mPrevSelectedEvent.top;
+ prevBottom = (int) mPrevSelectedEvent.bottom;
+ prevLeft = (int) mPrevSelectedEvent.left;
+ prevRight = (int) mPrevSelectedEvent.right;
+ // Check if the previously selected event intersects the previous
+ // selection box. (The previously selected event may be from a
+ // much older selection box.)
+ if (prevTop >= mPrevBox.bottom || prevBottom <= mPrevBox.top
+ || prevRight <= mPrevBox.left || prevLeft >= mPrevBox.right) {
+ mPrevSelectedEvent = null;
+ prevTop = mPrevBox.top;
+ prevBottom = mPrevBox.bottom;
+ prevLeft = mPrevBox.left;
+ prevRight = mPrevBox.right;
+ } else {
+ // Clip the top and bottom to the previous selection box.
+ if (prevTop < mPrevBox.top) {
+ prevTop = mPrevBox.top;
+ }
+ if (prevBottom > mPrevBox.bottom) {
+ prevBottom = mPrevBox.bottom;
+ }
+ }
+ } else {
+ // Just use the previously drawn selection box
+ prevTop = mPrevBox.top;
+ prevBottom = mPrevBox.bottom;
+ prevLeft = mPrevBox.left;
+ prevRight = mPrevBox.right;
+ }
+
+ // Figure out where we came from and compute the center of that area.
+ if (prevLeft >= box.right) {
+ // The previously selected event was to the right of us.
+ prevLocation = FROM_RIGHT;
+ prevCenter = (prevTop + prevBottom) / 2;
+ } else if (prevRight <= box.left) {
+ // The previously selected event was to the left of us.
+ prevLocation = FROM_LEFT;
+ prevCenter = (prevTop + prevBottom) / 2;
+ } else if (prevBottom <= box.top) {
+ // The previously selected event was above us.
+ prevLocation = FROM_ABOVE;
+ prevCenter = (prevLeft + prevRight) / 2;
+ } else if (prevTop >= box.bottom) {
+ // The previously selected event was below us.
+ prevLocation = FROM_BELOW;
+ prevCenter = (prevLeft + prevRight) / 2;
+ }
+
+ // For each event in the selected event list "mSelectedEvents", search
+ // all the other events in that list for the nearest neighbor in 4
+ // directions.
+ for (int ii = 0; ii < len; ii++) {
+ Event ev = mSelectedEvents.get(ii);
+
+ int startTime = ev.startTime;
+ int endTime = ev.endTime;
+ int left = (int) ev.left;
+ int right = (int) ev.right;
+ int top = (int) ev.top;
+ if (top < box.top) {
+ top = box.top;
+ }
+ int bottom = (int) ev.bottom;
+ if (bottom > box.bottom) {
+ bottom = box.bottom;
+ }
+ if (false) {
+ int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
+ | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+ if (DateFormat.is24HourFormat(mContext)) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ String timeRange = DateUtils.formatDateRange(mParentActivity,
+ ev.startMillis, ev.endMillis, flags);
+ Log.i("Cal", "left: " + left + " right: " + right + " top: " + top
+ + " bottom: " + bottom + " ev: " + timeRange + " " + ev.title);
+ }
+ int upDistanceMin = 10000; // any large number
+ int downDistanceMin = 10000; // any large number
+ int leftDistanceMin = 10000; // any large number
+ int rightDistanceMin = 10000; // any large number
+ Event upEvent = null;
+ Event downEvent = null;
+ Event leftEvent = null;
+ Event rightEvent = null;
+
+ // Pick the starting event closest to the previously selected event,
+ // if any. distance1 takes precedence over distance2.
+ int distance1 = 0;
+ int distance2 = 0;
+ if (prevLocation == FROM_ABOVE) {
+ if (left >= prevCenter) {
+ distance1 = left - prevCenter;
+ } else if (right <= prevCenter) {
+ distance1 = prevCenter - right;
+ }
+ distance2 = top - prevBottom;
+ } else if (prevLocation == FROM_BELOW) {
+ if (left >= prevCenter) {
+ distance1 = left - prevCenter;
+ } else if (right <= prevCenter) {
+ distance1 = prevCenter - right;
+ }
+ distance2 = prevTop - bottom;
+ } else if (prevLocation == FROM_LEFT) {
+ if (bottom <= prevCenter) {
+ distance1 = prevCenter - bottom;
+ } else if (top >= prevCenter) {
+ distance1 = top - prevCenter;
+ }
+ distance2 = left - prevRight;
+ } else if (prevLocation == FROM_RIGHT) {
+ if (bottom <= prevCenter) {
+ distance1 = prevCenter - bottom;
+ } else if (top >= prevCenter) {
+ distance1 = top - prevCenter;
+ }
+ distance2 = prevLeft - right;
+ }
+ if (distance1 < startEventDistance1
+ || (distance1 == startEventDistance1 && distance2 < startEventDistance2)) {
+ startEvent = ev;
+ startEventDistance1 = distance1;
+ startEventDistance2 = distance2;
+ }
+
+ // For each neighbor, figure out if it is above or below or left
+ // or right of me and compute the distance.
+ for (int jj = 0; jj < len; jj++) {
+ if (jj == ii) {
+ continue;
+ }
+ Event neighbor = mSelectedEvents.get(jj);
+ int neighborLeft = (int) neighbor.left;
+ int neighborRight = (int) neighbor.right;
+ if (neighbor.endTime <= startTime) {
+ // This neighbor is entirely above me.
+ // If we overlap the same column, then compute the distance.
+ if (neighborLeft < right && neighborRight > left) {
+ int distance = startTime - neighbor.endTime;
+ if (distance < upDistanceMin) {
+ upDistanceMin = distance;
+ upEvent = neighbor;
+ } else if (distance == upDistanceMin) {
+ int center = (left + right) / 2;
+ int currentDistance = 0;
+ int currentLeft = (int) upEvent.left;
+ int currentRight = (int) upEvent.right;
+ if (currentRight <= center) {
+ currentDistance = center - currentRight;
+ } else if (currentLeft >= center) {
+ currentDistance = currentLeft - center;
+ }
+
+ int neighborDistance = 0;
+ if (neighborRight <= center) {
+ neighborDistance = center - neighborRight;
+ } else if (neighborLeft >= center) {
+ neighborDistance = neighborLeft - center;
+ }
+ if (neighborDistance < currentDistance) {
+ upDistanceMin = distance;
+ upEvent = neighbor;
+ }
+ }
+ }
+ } else if (neighbor.startTime >= endTime) {
+ // This neighbor is entirely below me.
+ // If we overlap the same column, then compute the distance.
+ if (neighborLeft < right && neighborRight > left) {
+ int distance = neighbor.startTime - endTime;
+ if (distance < downDistanceMin) {
+ downDistanceMin = distance;
+ downEvent = neighbor;
+ } else if (distance == downDistanceMin) {
+ int center = (left + right) / 2;
+ int currentDistance = 0;
+ int currentLeft = (int) downEvent.left;
+ int currentRight = (int) downEvent.right;
+ if (currentRight <= center) {
+ currentDistance = center - currentRight;
+ } else if (currentLeft >= center) {
+ currentDistance = currentLeft - center;
+ }
+
+ int neighborDistance = 0;
+ if (neighborRight <= center) {
+ neighborDistance = center - neighborRight;
+ } else if (neighborLeft >= center) {
+ neighborDistance = neighborLeft - center;
+ }
+ if (neighborDistance < currentDistance) {
+ downDistanceMin = distance;
+ downEvent = neighbor;
+ }
+ }
+ }
+ }
+
+ if (neighborLeft >= right) {
+ // This neighbor is entirely to the right of me.
+ // Take the closest neighbor in the y direction.
+ int center = (top + bottom) / 2;
+ int distance = 0;
+ int neighborBottom = (int) neighbor.bottom;
+ int neighborTop = (int) neighbor.top;
+ if (neighborBottom <= center) {
+ distance = center - neighborBottom;
+ } else if (neighborTop >= center) {
+ distance = neighborTop - center;
+ }
+ if (distance < rightDistanceMin) {
+ rightDistanceMin = distance;
+ rightEvent = neighbor;
+ } else if (distance == rightDistanceMin) {
+ // Pick the closest in the x direction
+ int neighborDistance = neighborLeft - right;
+ int currentDistance = (int) rightEvent.left - right;
+ if (neighborDistance < currentDistance) {
+ rightDistanceMin = distance;
+ rightEvent = neighbor;
+ }
+ }
+ } else if (neighborRight <= left) {
+ // This neighbor is entirely to the left of me.
+ // Take the closest neighbor in the y direction.
+ int center = (top + bottom) / 2;
+ int distance = 0;
+ int neighborBottom = (int) neighbor.bottom;
+ int neighborTop = (int) neighbor.top;
+ if (neighborBottom <= center) {
+ distance = center - neighborBottom;
+ } else if (neighborTop >= center) {
+ distance = neighborTop - center;
+ }
+ if (distance < leftDistanceMin) {
+ leftDistanceMin = distance;
+ leftEvent = neighbor;
+ } else if (distance == leftDistanceMin) {
+ // Pick the closest in the x direction
+ int neighborDistance = left - neighborRight;
+ int currentDistance = left - (int) leftEvent.right;
+ if (neighborDistance < currentDistance) {
+ leftDistanceMin = distance;
+ leftEvent = neighbor;
+ }
+ }
+ }
+ }
+ ev.nextUp = upEvent;
+ ev.nextDown = downEvent;
+ ev.nextLeft = leftEvent;
+ ev.nextRight = rightEvent;
+ }
+ mSelectedEvent = startEvent;
+ }
+
+
+ private RectF drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
+
+ int color = event.color;
+
+ // Fade visible boxes if event was declined.
+ boolean declined = (event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED);
+ if (declined) {
+ int alpha = color & 0xff000000;
+ color &= 0x00ffffff;
+ int red = (color & 0x00ff0000) >> 16;
+ int green = (color & 0x0000ff00) >> 8;
+ int blue = (color & 0x0000ff);
+ color = ((red >> 1) << 16) | ((green >> 1) << 8) | (blue >> 1);
+ color += 0x7F7F7F + alpha;
+ }
+
+ // If this event is selected, then use the selection color
+ if (mSelectedEvent == event) {
+ if (mSelectionMode == SELECTION_PRESSED) {
+ // Also, remember the last selected event that we drew
+ mPrevSelectedEvent = event;
+ // box = mBoxPressed;
+ p.setColor(mPressedColor); // FIXME:pressed
+ eventTextPaint.setColor(mSelectedEventTextColor);
+ } else if (mSelectionMode == SELECTION_SELECTED) {
+ // Also, remember the last selected event that we drew
+ mPrevSelectedEvent = event;
+ // box = mBoxSelected;
+ p.setColor(mSelectionColor);
+ eventTextPaint.setColor(mSelectedEventTextColor);
+ } else if (mSelectionMode == SELECTION_LONGPRESS) {
+ // box = mBoxLongPressed;
+ p.setColor(mPressedColor); // FIXME: longpressed (maybe -- this doesn't seem to work)
+ eventTextPaint.setColor(mSelectedEventTextColor);
+ } else {
+ p.setColor(color);
+ eventTextPaint.setColor(mEventTextColor);
+ }
+ } else {
+ p.setColor(color);
+ eventTextPaint.setColor(mEventTextColor);
+ }
+
+
+ RectF rf = mRectF;
+ rf.top = event.top;
+ rf.bottom = event.bottom;
+ rf.left = event.left;
+ rf.right = event.right - 1;
+
+ canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+ rf.left += 2;
+ rf.right -= 2;
+
+ return rf;
+ }
+
+ private void drawEventText(Event event, RectF rf, Canvas canvas, Paint p, int topMargin) {
+ if (!mDrawTextInEventRect) {
+ return;
+ }
+
+ float width = rf.right - rf.left;
+ float height = rf.bottom - rf.top;
+
+ // Leave one pixel extra space between lines
+ int lineHeight = mEventTextHeight + 1;
+
+ // If the rectangle is too small for text, then return
+ if (width < MIN_CELL_WIDTH_FOR_TEXT || height <= lineHeight) {
+ return;
+ }
+
+ // Truncate the event title to a known (large enough) limit
+ String text = event.getTitleAndLocation();
+ 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(mParentActivity,
+ event.startMillis, event.endMillis, flags);
+ TextView timeView = (TextView) mPopupView.findViewById(R.id.time);
+ timeView.setText(timeRange);
+
+ TextView whereView = (TextView) mPopupView.findViewById(R.id.where);
+ final boolean empty = TextUtils.isEmpty(event.location);
+ whereView.setVisibility(empty ? View.GONE : View.VISIBLE);
+ if (!empty) whereView.setText(event.location);
+
+ mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, mHoursWidth, 5);
+ postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
+ }
+
+ // 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);
+ 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();
+ }
+
+ final long startMillis = getSelectedTimeInMillis();
+ int flags = DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
+ | DateUtils.FORMAT_SHOW_WEEKDAY;
+ final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
+ menu.setHeaderTitle(title);
+
+ int numSelectedEvents = mSelectedEvents.size();
+ if (mNumDays == 1) {
+ // Day view.
+
+ // If there is a selected event, then allow it to be viewed and
+ // edited.
+ if (numSelectedEvents >= 1) {
+ item = menu.add(0, MenuHelper.MENU_EVENT_VIEW, 0, R.string.event_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_info_details);
+
+ 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.show_day_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_day);
+ item.setAlphabeticShortcut('d');
+
+ item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_agenda);
+ item.setAlphabeticShortcut('a');
+ } else {
+ // No events are selected
+ item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_add);
+ item.setAlphabeticShortcut('n');
+
+ item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_day);
+ item.setAlphabeticShortcut('d');
+
+ item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_agenda);
+ item.setAlphabeticShortcut('a');
+ }
+ }
+
+ mPopup.dismiss();
+ }
+
+ private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MenuHelper.MENU_EVENT_VIEW: {
+ if (mSelectedEvent != null) {
+ long id = mSelectedEvent.id;
+ Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, id);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(eventUri);
+ intent.setClassName(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();
+ }
+
+ return visibility >= Calendars.CONTRIBUTOR_ACCESS &&
+ relationship >= Attendees.RELATIONSHIP_ORGANIZER;
+ }
+
+ /**
+ * 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(mParentActivity,
+// ev.startMillis, ev.endMillis, flags);
+//
+// Log.i("Cal", " " + timeRange + " " + ev.title);
+// }
+// }
+ return true;
+ }
+
+ private void findSelectedEvent(int x, int y) {
+ int date = mSelectionDay;
+ int cellWidth = mCellWidth;
+ ArrayList<Event> events = mEvents;
+ int numEvents = events.size();
+ int left = mHoursWidth + (mSelectionDay - mFirstJulianDay) * (cellWidth + DAY_GAP);
+ int top = 0;
+ mSelectedEvent = null;
+
+ mSelectedEvents.clear();
+ if (mSelectionAllDay) {
+ float yDistance;
+ float minYdistance = 10000.0f; // any large number
+ Event closestEvent = null;
+ float drawHeight = mAllDayHeight;
+ int yOffset = mBannerPlusMargin + ALLDAY_TOP_MARGIN;
+ for (int i = 0; i < numEvents; i++) {
+ Event event = events.get(i);
+ if (!event.allDay) {
+ continue;
+ }
+
+ if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
+ float numRectangles = event.getMaxColumns();
+ float height = drawHeight / numRectangles;
+ if (height > MAX_ALLDAY_EVENT_HEIGHT) {
+ height = MAX_ALLDAY_EVENT_HEIGHT;
+ }
+ float eventTop = yOffset + height * event.getColumn();
+ float eventBottom = eventTop + height;
+ if (eventTop < y && eventBottom > y) {
+ // If the touch is inside the event rectangle, then
+ // add the event.
+ mSelectedEvents.add(event);
+ closestEvent = event;
+ break;
+ } else {
+ // Find the closest event
+ if (eventTop >= y) {
+ yDistance = eventTop - y;
+ } else {
+ yDistance = y - eventBottom;
+ }
+ if (yDistance < minYdistance) {
+ minYdistance = yDistance;
+ closestEvent = event;
+ }
+ }
+ }
+ }
+ mSelectedEvent = closestEvent;
+ return;
+ }
+
+ // Adjust y for the scrollable bitmap
+ y += mViewStartY - mFirstCell;
+
+ // Use a region around (x,y) for the selection region
+ Rect region = mRect;
+ region.left = x - 10;
+ region.right = x + 10;
+ region.top = y - 10;
+ region.bottom = y + 10;
+
+ EventGeometry geometry = mEventGeometry;
+
+ for (int i = 0; i < numEvents; i++) {
+ Event event = events.get(i);
+ // Compute the event rectangle.
+ if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+ continue;
+ }
+
+ // If the event intersects the selection region, then add it to
+ // mSelectedEvents.
+ if (geometry.eventIntersectsSelection(event, region)) {
+ mSelectedEvents.add(event);
+ }
+ }
+
+ // If there are any events in the selected region, then assign the
+ // closest one to mSelectedEvent.
+ if (mSelectedEvents.size() > 0) {
+ int len = mSelectedEvents.size();
+ Event closestEvent = null;
+ float minDist = mViewWidth + mViewHeight; // some large distance
+ for (int index = 0; index < len; index++) {
+ Event ev = mSelectedEvents.get(index);
+ float dist = geometry.pointToEvent(x, y, ev);
+ if (dist < minDist) {
+ minDist = dist;
+ closestEvent = ev;
+ }
+ }
+ mSelectedEvent = closestEvent;
+
+ // Keep the selected hour and day consistent with the selected
+ // event. They could be different if we touched on an empty hour
+ // slot very close to an event in the previous hour slot. In
+ // that case we will select the nearby event.
+ int startDay = mSelectedEvent.startDay;
+ int endDay = mSelectedEvent.endDay;
+ if (mSelectionDay < startDay) {
+ mSelectionDay = startDay;
+ } else if (mSelectionDay > endDay) {
+ mSelectionDay = endDay;
+ }
+
+ int startHour = mSelectedEvent.startTime / 60;
+ int endHour;
+ if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
+ endHour = (mSelectedEvent.endTime - 1) / 60;
+ } else {
+ endHour = mSelectedEvent.endTime / 60;
+ }
+
+ if (mSelectionHour < startHour) {
+ mSelectionHour = startHour;
+ } else if (mSelectionHour > endHour) {
+ mSelectionHour = endHour;
+ }
+ }
+ }
+
+ // Encapsulates the code to continue the scrolling after the
+ // finger is lifted. Instead of stopping the scroll immediately,
+ // the scroll continues to "free spin" and gradually slows down.
+ private class ContinueScroll implements Runnable {
+ int mSignDeltaY;
+ int mAbsDeltaY;
+ float mFloatDeltaY;
+ long mFreeSpinTime;
+ private static final float FRICTION_COEF = 0.7F;
+ private static final long FREE_SPIN_MILLIS = 180;
+ private static final int MAX_DELTA = 60;
+ private static final int SCROLL_REPEAT_INTERVAL = 30;
+
+ public void init(int deltaY) {
+ mSignDeltaY = 0;
+ if (deltaY > 0) {
+ mSignDeltaY = 1;
+ } else if (deltaY < 0) {
+ mSignDeltaY = -1;
+ }
+ mAbsDeltaY = Math.abs(deltaY);
+
+ // Limit the maximum speed
+ if (mAbsDeltaY > MAX_DELTA) {
+ mAbsDeltaY = MAX_DELTA;
+ }
+ mFloatDeltaY = mAbsDeltaY;
+ mFreeSpinTime = System.currentTimeMillis() + FREE_SPIN_MILLIS;
+// Log.i("Cal", "init scroll: mAbsDeltaY: " + mAbsDeltaY
+// + " mViewStartY: " + mViewStartY);
+ }
+
+ public void run() {
+ long time = System.currentTimeMillis();
+
+ // Start out with a frictionless "free spin"
+ if (time > mFreeSpinTime) {
+ // If the delta is small, then apply a fixed deceleration.
+ // Otherwise
+ if (mAbsDeltaY <= 10) {
+ mAbsDeltaY -= 2;
+ } else {
+ mFloatDeltaY *= FRICTION_COEF;
+ mAbsDeltaY = (int) mFloatDeltaY;
+ }
+
+ if (mAbsDeltaY < 0) {
+ mAbsDeltaY = 0;
+ }
+ }
+
+ if (mSignDeltaY == 1) {
+ mViewStartY -= mAbsDeltaY;
+ } else {
+ mViewStartY += mAbsDeltaY;
+ }
+// Log.i("Cal", " scroll: mAbsDeltaY: " + mAbsDeltaY
+// + " mViewStartY: " + mViewStartY);
+
+ if (mViewStartY < 0) {
+ mViewStartY = 0;
+ mAbsDeltaY = 0;
+ } else if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY;
+ mAbsDeltaY = 0;
+ }
+
+ computeFirstHour();
+
+ if (mAbsDeltaY > 0) {
+ postDelayed(this, SCROLL_REPEAT_INTERVAL);
+ } else {
+ // Done scrolling.
+ mScrolling = false;
+ resetSelectedHour();
+ mRedrawScreen = true;
+ }
+
+ invalidate();
+ }
+ }
+
+ /**
+ * Cleanup the pop-up.
+ */
+ 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/DayActivity.java b/src/com/android/calendar/DayActivity.java
new file mode 100644
index 0000000..51f965b
--- /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 0000000..a24b51b
--- /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 0000000..90cd3ff
--- /dev/null
+++ b/src/com/android/calendar/DeleteEventHelper.java
@@ -0,0 +1,344 @@
+/*
+ * 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.provider.Calendar;
+import android.provider.Calendar.Events;
+import android.text.format.Time;
+import android.widget.Button;
+
+/**
+ * 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 static final String TAG = "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 AlertDialog mAlertDialog;
+
+ private static final String[] EVENT_PROJECTION = new String[] {
+ Events._ID,
+ Events.TITLE,
+ Events.ALL_DAY,
+ Events.CALENDAR_ID,
+ Events.RRULE,
+ Events.DTSTART,
+ Events._SYNC_ID,
+ Events.EVENT_TIMEZONE,
+ };
+
+ private int mEventIndexId;
+ private int mEventIndexRrule;
+ private String mSyncId;
+
+ public DeleteEventHelper(Activity parent, boolean exitWhenDone) {
+ mParent = parent;
+ mContentResolver = mParent.getContentResolver();
+ 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;
+
+ // Enable the "ok" button now that the user has selected which
+ // events in the series to delete.
+ Button ok = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ ok.setEnabled(true);
+ }
+ };
+
+ /**
+ * 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);
+ int eventIndexSyncId = mCursor.getColumnIndexOrThrow(Events._SYNC_ID);
+ mSyncId = mCursor.getString(eventIndexSyncId);
+
+ // 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(android.R.string.ok, mDeleteNormalDialogListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ } else {
+ // This is a repeating event. Pop up a dialog asking which events
+ // to delete.
+ int labelsArrayId = R.array.delete_repeating_labels;
+ if (mSyncId == null) {
+ labelsArrayId = R.array.delete_repeating_labels_no_selected;
+ }
+ AlertDialog dialog = new AlertDialog.Builder(mParent)
+ .setTitle(R.string.delete_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setSingleChoiceItems(labelsArrayId, which, mDeleteListListener)
+ .setPositiveButton(android.R.string.ok, mDeleteRepeatingDialogListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ mAlertDialog = dialog;
+
+ // Disable the "Ok" button until the user selects which events to
+ // delete.
+ Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ ok.setEnabled(false);
+ }
+ }
+
+ private void deleteRepeatingEvent(int which) {
+ int indexDtstart = mCursor.getColumnIndexOrThrow(Events.DTSTART);
+ int indexAllDay = mCursor.getColumnIndexOrThrow(Events.ALL_DAY);
+ int indexTitle = mCursor.getColumnIndexOrThrow(Events.TITLE);
+ int indexTimezone = mCursor.getColumnIndexOrThrow(Events.EVENT_TIMEZONE);
+ int indexCalendarId = mCursor.getColumnIndexOrThrow(Events.CALENDAR_ID);
+
+ String rRule = mCursor.getString(mEventIndexRrule);
+ boolean allDay = mCursor.getInt(indexAllDay) != 0;
+ long dtstart = mCursor.getLong(indexDtstart);
+ long id = mCursor.getInt(mEventIndexId);
+
+ // If the repeating event has not been given a sync id from the server
+ // yet, then we can't delete a single instance of this event. (This is
+ // a deficiency in the CalendarProvider and sync code.) We checked for
+ // that when creating the list of items in the dialog and we removed
+ // the first element ("DELETE_SELECTED") from the dialog in that case.
+ // The "which" value is a 0-based index into the list of items, where
+ // the "DELETE_SELECTED" item is at index 0.
+ if (mSyncId == null) {
+ which += 1;
+ }
+
+ switch (which) {
+ case DELETE_SELECTED:
+ {
+ // If we are deleting the first event in the series, then
+ // instead of creating a recurrence exception, just change
+ // the start time of the recurrence.
+ if (dtstart == mStartMillis) {
+ // TODO
+ }
+
+ // 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 timezone = mCursor.getString(indexTimezone);
+ int calendarId = mCursor.getInt(indexCalendarId);
+ values.put(Events.EVENT_TIMEZONE, timezone);
+ values.put(Events.ALL_DAY, allDay ? 1 : 0);
+ values.put(Events.CALENDAR_ID, calendarId);
+ values.put(Events.DTSTART, mStartMillis);
+ values.put(Events.DTEND, mEndMillis);
+ values.put(Events.ORIGINAL_EVENT, mSyncId);
+ 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 0000000..f020a7c
--- /dev/null
+++ b/src/com/android/calendar/EditEvent.java
@@ -0,0 +1,1690 @@
+/*
+ * 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.ProgressDialog;
+import android.app.TimePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.TimePickerDialog.OnTimeSetListener;
+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.content.Intent;
+import android.content.SharedPreferences;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.EventRecurrence;
+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.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.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 android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+public class EditEvent extends Activity implements View.OnClickListener,
+ DialogInterface.OnCancelListener, DialogInterface.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 + " AND " + Calendars.SYNC_EVENTS + "=1";
+
+ private static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders._ID, // 0
+ Reminders.MINUTES, // 1
+ };
+ private static final int REMINDERS_INDEX_MINUTES = 1;
+ private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
+ Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
+ Reminders.METHOD_DEFAULT + ")";
+
+ private static final int DOES_NOT_REPEAT = 0;
+ private static final int REPEATS_DAILY = 1;
+ private static final int REPEATS_EVERY_WEEKDAY = 2;
+ private static final int REPEATS_WEEKLY_ON_DAY = 3;
+ private static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
+ private static final int REPEATS_MONTHLY_ON_DAY = 5;
+ private static final int REPEATS_YEARLY = 6;
+ private static final int REPEATS_CUSTOM = 7;
+
+ private static final int MODIFY_UNINITIALIZED = 0;
+ private static final int MODIFY_SELECTED = 1;
+ private static final int MODIFY_ALL = 2;
+ private static final int MODIFY_ALL_FOLLOWING = 3;
+
+ private static final int DAY_IN_SECONDS = 24 * 60 * 60;
+
+ private int mFirstDayOfWeek; // cached in onCreate
+ private Uri mUri;
+ private Cursor mEventCursor;
+ private Cursor mCalendarsCursor;
+
+ private Button mStartDateButton;
+ private Button mEndDateButton;
+ private Button mStartTimeButton;
+ private Button mEndTimeButton;
+ private Button mSaveButton;
+ private Button mDeleteButton;
+ private Button mDiscardButton;
+ private 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 boolean mCalendarsQueryComplete;
+ private boolean mSaveAfterQueryComplete;
+ private ProgressDialog mLoadingCalendarsDialog;
+ private AlertDialog mNoCalendarsDialog;
+ 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;
+ private QueryHandler mQueryHandler;
+
+ /* 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) {
+ if (save()) {
+ finish();
+ }
+ return;
+ }
+
+ if (v == mDeleteButton) {
+ long begin = mStartTime.toMillis(false /* use isDst */);
+ long end = mEndTime.toMillis(false /* use isDst */);
+ int which = -1;
+ switch (mModification) {
+ case MODIFY_SELECTED:
+ which = DeleteEventHelper.DELETE_SELECTED;
+ break;
+ case MODIFY_ALL_FOLLOWING:
+ which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
+ break;
+ case MODIFY_ALL:
+ which = DeleteEventHelper.DELETE_ALL;
+ break;
+ }
+ mDeleteEventHelper.delete(begin, end, mEventCursor, which);
+ return;
+ }
+
+ if (v == mDiscardButton) {
+ finish();
+ return;
+ }
+
+ // This must be a click on one of the "remove reminder" buttons
+ LinearLayout reminderItem = (LinearLayout) v.getParent();
+ LinearLayout parent = (LinearLayout) reminderItem.getParent();
+ parent.removeView(reminderItem);
+ mReminderItems.remove(reminderItem);
+ updateRemindersVisibility();
+ }
+
+ // This is called if the user cancels a popup dialog. There are two
+ // dialogs: the "Loading calendars" dialog, and the "No calendars"
+ // dialog. The "Loading calendars" dialog is shown if there is a delay
+ // in loading the calendars (needed when creating an event) and the user
+ // tries to save the event before the calendars have finished loading.
+ // The "No calendars" dialog is shown if there are no syncable calendars.
+ public void onCancel(DialogInterface dialog) {
+ if (dialog == mLoadingCalendarsDialog) {
+ mSaveAfterQueryComplete = false;
+ } else if (dialog == mNoCalendarsDialog) {
+ finish();
+ }
+ }
+
+ // This is called if the user clicks on a dialog button.
+ public void onClick(DialogInterface dialog, int which) {
+ if (dialog == mNoCalendarsDialog) {
+ finish();
+ }
+ }
+
+ private class QueryHandler extends AsyncQueryHandler {
+ public QueryHandler(ContentResolver cr) {
+ super(cr);
+ }
+
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ // If the Activity is finishing, then close the cursor.
+ // Otherwise, use the new cursor in the adapter.
+ if (isFinishing()) {
+ stopManagingCursor(cursor);
+ cursor.close();
+ } else {
+ mCalendarsCursor = cursor;
+ startManagingCursor(cursor);
+
+ // Stop the spinner
+ getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_OFF);
+
+ // If there are no syncable calendars, then we cannot allow
+ // creating a new event.
+ if (cursor.getCount() == 0) {
+ // Cancel the "loading calendars" dialog if it exists
+ if (mSaveAfterQueryComplete) {
+ mLoadingCalendarsDialog.cancel();
+ }
+
+ // Create an error message for the user that, when clicked,
+ // will exit this activity without saving the event.
+ AlertDialog.Builder builder = new AlertDialog.Builder(EditEvent.this);
+ builder.setTitle(R.string.no_syncable_calendars)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_calendars_found)
+ .setPositiveButton(android.R.string.ok, EditEvent.this)
+ .setOnCancelListener(EditEvent.this);
+ mNoCalendarsDialog = builder.show();
+ return;
+ }
+
+ // populate the calendars spinner
+ CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor);
+ mCalendarsSpinner.setAdapter(adapter);
+ mCalendarsQueryComplete = true;
+ if (mSaveAfterQueryComplete) {
+ mLoadingCalendarsDialog.cancel();
+ save();
+ finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ 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);
+ if (mEventCursor == null || mEventCursor.getCount() == 0) {
+ // The cursor is empty. This can happen if the event was deleted.
+ finish();
+ return;
+ }
+ }
+
+ long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
+ long end = intent.getLongExtra(EVENT_END_TIME, 0);
+
+ 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);
+ long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
+
+ // Remember the initial values
+ mInitialValues = new ContentValues();
+ mInitialValues.put(EVENT_BEGIN_TIME, begin);
+ mInitialValues.put(EVENT_END_TIME, end);
+ mInitialValues.put(Events.ALL_DAY, allDay ? 1 : 0);
+ mInitialValues.put(Events.RRULE, rrule);
+ mInitialValues.put(Events.EVENT_TIMEZONE, timezone);
+ mInitialValues.put(Events.CALENDAR_ID, calendarId);
+ } else {
+ // We are creating a new event, so set the default from the
+ // intent (if specified).
+ allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
+
+ // Start the spinner
+ getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_ON);
+
+ // Start a query in the background to read the list of calendars
+ mQueryHandler = new QueryHandler(getContentResolver());
+ mQueryHandler.startQuery(0, null, Calendars.CONTENT_URI, CALENDARS_PROJECTION,
+ CALENDARS_WHERE, null /* selection args */, null /* sort order */);
+ }
+
+ // 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);
+ }
+ }
+
+ // 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 */);
+
+ if (mEventCursor == null) {
+ // Allow the intent to specify the fields in the event.
+ // This will allow other apps to create events easily.
+ initFromIntent(intent);
+ }
+ }
+
+ private void initFromIntent(Intent intent) {
+ String title = intent.getStringExtra(Events.TITLE);
+ if (title != null) {
+ mTitleTextView.setText(title);
+ }
+
+ String location = intent.getStringExtra(Events.EVENT_LOCATION);
+ if (location != null) {
+ mLocationTextView.setText(location);
+ }
+
+ String description = intent.getStringExtra(Events.DESCRIPTION);
+ if (description != null) {
+ mDescriptionTextView.setText(description);
+ }
+
+ int availability = intent.getIntExtra(Events.TRANSPARENCY, -1);
+ if (availability != -1) {
+ mAvailabilitySpinner.setSelection(availability);
+ }
+
+ int visibility = intent.getIntExtra(Events.VISIBILITY, -1);
+ if (visibility != -1) {
+ mVisibilitySpinner.setSelection(visibility);
+ }
+
+ String rrule = intent.getStringExtra(Events.RRULE);
+ if (rrule != null) {
+ mRrule = rrule;
+ mEventRecurrence.parse(rrule);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mUri != null) {
+ if (mEventCursor == null || mEventCursor.getCount() == 0) {
+ // The cursor is empty. This can happen if the event was deleted.
+ finish();
+ return;
+ }
+ }
+
+ if (mEventCursor != null) {
+ Cursor cursor = mEventCursor;
+ cursor.moveToFirst();
+
+ mRrule = cursor.getString(EVENT_INDEX_RRULE);
+ String title = cursor.getString(EVENT_INDEX_TITLE);
+ String description = cursor.getString(EVENT_INDEX_DESCRIPTION);
+ String location = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
+ int availability = cursor.getInt(EVENT_INDEX_TRANSPARENCY);
+ int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
+ if (visibility > 0) {
+ // For now we the array contains the values 0, 2, and 3. We subtract one to match.
+ visibility--;
+ }
+
+ if (!TextUtils.isEmpty(mRrule) && mModification == MODIFY_UNINITIALIZED) {
+ // If this event has not been synced, then don't allow deleting
+ // or changing a single instance.
+ mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
+ mEventRecurrence.parse(mRrule);
+
+ // If we haven't synced this repeating event yet, then don't
+ // allow the user to change just one instance.
+ int itemIndex = 0;
+ CharSequence[] items;
+ if (mSyncId == null) {
+ 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);
+
+ // This is an existing event so hide the calendar spinner
+ // since we can't change the calendar.
+ 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()) {
+ if (!save()) {
+ // We cannot exit this activity because the calendars
+ // are still loading.
+ return true;
+ }
+ }
+ 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.formatDateTime(this, when, flags)));
+ recurrenceIndexes.add(REPEATS_YEARLY);
+
+ if (isCustomRecurrence) {
+ repeatArray.add(r.getString(R.string.custom));
+ recurrenceIndexes.add(REPEATS_CUSTOM);
+ }
+ mRecurrenceIndexes = recurrenceIndexes;
+
+ int position = recurrenceIndexes.indexOf(DOES_NOT_REPEAT);
+ if (mRrule != null) {
+ if (isCustomRecurrence) {
+ position = recurrenceIndexes.indexOf(REPEATS_CUSTOM);
+ } else {
+ switch (mEventRecurrence.freq) {
+ case EventRecurrence.DAILY:
+ position = recurrenceIndexes.indexOf(REPEATS_DAILY);
+ break;
+ case EventRecurrence.WEEKLY:
+ if (mEventRecurrence.repeatsOnEveryWeekDay()) {
+ position = recurrenceIndexes.indexOf(REPEATS_EVERY_WEEKDAY);
+ } else {
+ position = recurrenceIndexes.indexOf(REPEATS_WEEKLY_ON_DAY);
+ }
+ break;
+ case EventRecurrence.MONTHLY:
+ if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
+ position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY_COUNT);
+ } else {
+ position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY);
+ }
+ break;
+ case EventRecurrence.YEARLY:
+ position = recurrenceIndexes.indexOf(REPEATS_YEARLY);
+ break;
+ }
+ }
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, resource, repeatArray);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mRepeatsSpinner.setAdapter(adapter);
+ mRepeatsSpinner.setSelection(position);
+ }
+
+ // Adds a reminder to the displayed list of reminders.
+ // Returns true if successfully added reminder, false if no reminders can
+ // be added.
+ static boolean addReminder(Activity activity, View.OnClickListener listener,
+ ArrayList<LinearLayout> items, ArrayList<Integer> values,
+ ArrayList<String> labels, int minutes) {
+
+ if (items.size() >= MAX_REMINDERS) {
+ return false;
+ }
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ LinearLayout parent = (LinearLayout) activity.findViewById(R.id.reminder_items_container);
+ LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, null);
+ parent.addView(reminderItem);
+
+ Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value);
+ Resources res = activity.getResources();
+ spinner.setPrompt(res.getString(R.string.reminders_label));
+ int resource = android.R.layout.simple_spinner_item;
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, resource, labels);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ ImageButton reminderRemoveButton;
+ reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove);
+ reminderRemoveButton.setOnClickListener(listener);
+
+ int index = findMinutesInReminderList(values, minutes);
+ spinner.setSelection(index);
+ items.add(reminderItem);
+
+ return true;
+ }
+
+ static void addMinutesToList(Context context, ArrayList<Integer> values,
+ ArrayList<String> labels, int minutes) {
+ int index = values.indexOf(minutes);
+ if (index != -1) {
+ return;
+ }
+
+ // The requested "minutes" does not exist in the list, so insert it
+ // into the list.
+
+ String label = constructReminderLabel(context, minutes, false);
+ int len = values.size();
+ for (int i = 0; i < len; i++) {
+ if (minutes < values.get(i)) {
+ values.add(i, minutes);
+ labels.add(i, label);
+ return;
+ }
+ }
+
+ values.add(minutes);
+ labels.add(len, label);
+ }
+
+ /**
+ * Finds the index of the given "minutes" in the "values" list.
+ *
+ * @param values the list of minutes corresponding to the spinner choices
+ * @param minutes the minutes to search for in the values list
+ * @return the index of "minutes" in the "values" list
+ */
+ private static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) {
+ int index = values.indexOf(minutes);
+ if (index == -1) {
+ // This should never happen.
+ Log.e("Cal", "Cannot find minutes (" + minutes + ") in list");
+ return 0;
+ }
+ return index;
+ }
+
+ // Constructs a label given an arbitrary number of minutes. For example,
+ // if the given minutes is 63, then this returns the string "63 minutes".
+ // As another example, if the given minutes is 120, then this returns
+ // "2 hours".
+ static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
+ Resources resources = context.getResources();
+ int value, resId;
+
+ if (minutes % 60 != 0) {
+ value = minutes;
+ if (abbrev) {
+ resId = R.plurals.Nmins;
+ } else {
+ resId = R.plurals.Nminutes;
+ }
+ } else if (minutes % (24 * 60) != 0) {
+ value = minutes / 60;
+ resId = R.plurals.Nhours;
+ } else {
+ value = minutes / ( 24 * 60);
+ resId = R.plurals.Ndays;
+ }
+
+ String format = resources.getQuantityString(resId, value);
+ return String.format(format, value);
+ }
+
+ private void updateRemindersVisibility() {
+ if (mReminderItems.size() == 0) {
+ mRemindersSeparator.setVisibility(View.GONE);
+ mRemindersContainer.setVisibility(View.GONE);
+ } else {
+ mRemindersSeparator.setVisibility(View.VISIBLE);
+ mRemindersContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void setDate(TextView view, long millis) {
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH |
+ DateUtils.FORMAT_ABBREV_WEEKDAY;
+ view.setText(DateUtils.formatDateTime(this, 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.formatDateTime(this, millis, flags));
+ }
+
+ // Saves the event. Returns true if it is okay to exit this activity.
+ private boolean save() {
+ // If we are creating a new event, then make sure we wait until the
+ // query to fetch the list of calendars has finished.
+ if (mEventCursor == null) {
+ if (!mCalendarsQueryComplete) {
+ // Wait for the calendars query to finish.
+ if (mLoadingCalendarsDialog == null) {
+ // Create the progress dialog
+ mLoadingCalendarsDialog = ProgressDialog.show(this,
+ getText(R.string.loading_calendars_title),
+ getText(R.string.loading_calendars_message),
+ true, true, this);
+ mSaveAfterQueryComplete = true;
+ }
+ return false;
+ }
+
+ // Avoid creating a new event if the calendars cursor is empty. 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 true;
+ }
+ Toast.makeText(this, R.string.creating_event, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
+ }
+
+ 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);
+ boolean allDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
+ values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
+
+ uri = cr.insert(Events.CONTENT_URI, values);
+
+ } else if (mModification == MODIFY_ALL_FOLLOWING) {
+ // Modify this instance and all future instances of repeating event
+ addRecurrenceRule(values);
+
+ if (mRrule == null) {
+ // We've changed a recurring event to a non-recurring event.
+ // If the event we are editing is the first in the series,
+ // then delete the whole series. Otherwise, update the series
+ // to end at the new start time.
+ if (isFirstEventInSeries()) {
+ cr.delete(uri, null, null);
+ } else {
+ // Update the current repeating event to end at the new
+ // start time.
+ updatePastEvents(cr, uri);
+ }
+ uri = cr.insert(Events.CONTENT_URI, values);
+ } else {
+ if (isFirstEventInSeries()) {
+ checkTimeDependentFields(values);
+ values.remove(Events.DTEND);
+ cr.update(uri, values, null, null);
+ } else {
+ // Update the current repeating event to end at the new
+ // start time.
+ updatePastEvents(cr, uri);
+
+ // Create a new event with the user-modified fields
+ 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 a non-recurring event.
+ // Delete the whole series and replace it with a new
+ // non-recurring event.
+ cr.delete(uri, null, null);
+ 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);
+ }
+ return true;
+ }
+
+ 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);
+ boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+ String oldRrule = mEventCursor.getString(EVENT_INDEX_RRULE);
+ mEventRecurrence.parse(oldRrule);
+
+ Time untilTime = new Time();
+ long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+ ContentValues oldValues = new ContentValues();
+
+ // The "until" time must be in UTC time in order for Google calendar
+ // to display it properly. For all-day events, the "until" time string
+ // must include just the date field, and not the time field. The
+ // repeating events repeat up to and including the "until" time.
+ untilTime.timezone = Time.TIMEZONE_UTC;
+
+ // Subtract one second from the old begin time to get the new
+ // "until" time.
+ untilTime.set(begin - 1000); // subtract one second (1000 millis)
+ if (allDay) {
+ untilTime.hour = 0;
+ untilTime.minute = 0;
+ untilTime.second = 0;
+ untilTime.allDay = true;
+ untilTime.normalize(false);
+
+ // For all-day events, the duration must be in days, not seconds.
+ // Otherwise, Google Calendar will (mistakenly) change this event
+ // into a non-all-day event.
+ int len = oldDuration.length();
+ if (oldDuration.charAt(0) == 'P' && oldDuration.charAt(len - 1) == 'S') {
+ int seconds = Integer.parseInt(oldDuration.substring(1, len - 1));
+ int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
+ oldDuration = "P" + days + "D";
+ }
+ }
+ mEventRecurrence.until = untilTime.format2445();
+
+ oldValues.put(Events.DTSTART, oldStartMillis);
+ oldValues.put(Events.DURATION, oldDuration);
+ oldValues.put(Events.RRULE, mEventRecurrence.toString());
+ 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.getAsInteger(Events.ALL_DAY) != 0;
+ String oldRrule = mInitialValues.getAsString(Events.RRULE);
+ String oldTimezone = mInitialValues.getAsString(Events.EVENT_TIMEZONE);
+
+ long newBegin = values.getAsLong(Events.DTSTART);
+ long newEnd = values.getAsLong(Events.DTEND);
+ boolean newAllDay = values.getAsInteger(Events.ALL_DAY) != 0;
+ String newRrule = values.getAsString(Events.RRULE);
+ String newTimezone = values.getAsString(Events.EVENT_TIMEZONE);
+
+ // If none of the time-dependent fields changed, then remove them.
+ if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
+ && TextUtils.equals(oldRrule, newRrule)
+ && TextUtils.equals(oldTimezone, newTimezone)) {
+ values.remove(Events.DTSTART);
+ values.remove(Events.DTEND);
+ values.remove(Events.DURATION);
+ values.remove(Events.ALL_DAY);
+ values.remove(Events.RRULE);
+ values.remove(Events.EVENT_TIMEZONE);
+ return;
+ }
+
+ if (oldRrule == null || newRrule == null) {
+ return;
+ }
+
+ // If we are modifying all events then we need to set DTSTART to the
+ // start time of the first event in the series, not the current
+ // date and time. If the start time of the event was changed
+ // (from, say, 3pm to 4pm), then we want to add the time difference
+ // to the start time of the first event in the series (the DTSTART
+ // value). If we are modifying one instance or all following instances,
+ // then we leave the DTSTART field alone.
+ if (mModification == MODIFY_ALL) {
+ long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
+ if (oldBegin != newBegin) {
+ // The user changed the start time of this event
+ long offset = newBegin - oldBegin;
+ oldStartMillis += offset;
+ }
+ values.put(Events.DTSTART, oldStartMillis);
+ }
+ }
+
+ static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
+ ArrayList<Integer> reminderValues) {
+ int len = reminderItems.size();
+ ArrayList<Integer> reminderMinutes = new ArrayList<Integer>(len);
+ for (int index = 0; index < len; index++) {
+ LinearLayout layout = reminderItems.get(index);
+ Spinner spinner = (Spinner) layout.findViewById(R.id.reminder_value);
+ int minutes = reminderValues.get(spinner.getSelectedItemPosition());
+ reminderMinutes.add(minutes);
+ }
+ return reminderMinutes;
+ }
+
+ /**
+ * Saves the reminders, if they changed. Returns true if the database
+ * was updated.
+ *
+ * @param cr the ContentResolver
+ * @param eventId the id of the event whose reminders are being updated
+ * @param reminderMinutes the array of reminders set by the user
+ * @param originalMinutes the original array of reminders
+ * @return true if the database was updated
+ */
+ static boolean 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 false;
+ }
+
+ // 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);
+ }
+ return true;
+ }
+
+ private void addRecurrenceRule(ContentValues values) {
+ updateRecurrenceRule();
+
+ if (mRrule == null) {
+ return;
+ }
+
+ values.put(Events.RRULE, mRrule);
+ long end = mEndTime.toMillis(true /* ignore dst */);
+ long start = mStartTime.toMillis(true /* ignore dst */);
+ String duration;
+
+ boolean isAllDay = mAllDayCheckBox.isChecked();
+ if (isAllDay) {
+ long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) / DateUtils.DAY_IN_MILLIS;
+ duration = "P" + days + "D";
+ } else {
+ long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
+ duration = "P" + seconds + "S";
+ }
+ values.put(Events.DURATION, duration);
+ }
+
+ private void 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();
+
+ ContentValues values = new ContentValues();
+
+ String timezone = null;
+ long startMillis;
+ long endMillis;
+ long calendarId;
+ if (isAllDay) {
+ // Reset start and end time, increment the monthDay by 1, and set
+ // the timezone to UTC, as required for all-day events.
+ 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);
+
+ if (mEventCursor == null) {
+ // This is a new event
+ calendarId = mCalendarsSpinner.getSelectedItemId();
+ } else {
+ calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
+ }
+ } else {
+ startMillis = mStartTime.toMillis(true);
+ endMillis = mEndTime.toMillis(true);
+ if (mEventCursor != null) {
+ // This is an existing event
+ timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
+
+ // The timezone might be null if we are changing an existing
+ // all-day event to a non-all-day event. We need to assign
+ // a timezone to the non-all-day event.
+ if (TextUtils.isEmpty(timezone)) {
+ timezone = TimeZone.getDefault().getID();
+ }
+ calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
+ } else {
+ // This is a new event
+ calendarId = mCalendarsSpinner.getSelectedItemId();
+
+ // The timezone for a new event is the currently displayed
+ // timezone, NOT the timezone of the containing calendar.
+ timezone = TimeZone.getDefault().getID();
+ }
+ }
+
+ values.put(Events.CALENDAR_ID, calendarId);
+ values.put(Events.EVENT_TIMEZONE, timezone);
+ values.put(Events.TITLE, title);
+ values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
+ values.put(Events.DTSTART, startMillis);
+ values.put(Events.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/EditResponseHelper.java b/src/com/android/calendar/EditResponseHelper.java
new file mode 100644
index 0000000..378ef45
--- /dev/null
+++ b/src/com/android/calendar/EditResponseHelper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.widget.Button;
+
+/**
+ * A helper class for editing the response to an invitation when the invitation
+ * is a repeating event.
+ */
+public class EditResponseHelper implements DialogInterface.OnClickListener {
+ private final Activity mParent;
+ private int mWhichEvents = -1;
+ private AlertDialog mAlertDialog;
+
+ /**
+ * This callback is passed in to this object when this object is created
+ * and is invoked when the "Ok" button is selected.
+ */
+ private DialogInterface.OnClickListener mDialogListener;
+
+ public EditResponseHelper(Activity parent) {
+ mParent = parent;
+ }
+
+ public void setOnClickListener(DialogInterface.OnClickListener listener) {
+ mDialogListener = listener;
+ }
+
+ public int getWhichEvents() {
+ return mWhichEvents;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ }
+
+ /**
+ * This callback is used when a list item is selected
+ */
+ private DialogInterface.OnClickListener mListListener =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mWhichEvents = which;
+
+ // Enable the "ok" button now that the user has selected which
+ // events in the series to delete.
+ Button ok = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ ok.setEnabled(true);
+ }
+ };
+
+ public void showDialog(int whichEvents) {
+ // We need to have a non-null listener, otherwise we get null when
+ // we try to fetch the "Ok" button.
+ if (mDialogListener == null) {
+ mDialogListener = this;
+ }
+ AlertDialog dialog = new AlertDialog.Builder(mParent)
+ .setTitle(R.string.change_response_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setSingleChoiceItems(R.array.change_response_labels, whichEvents,
+ mListListener)
+ .setPositiveButton(android.R.string.ok, mDialogListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ mAlertDialog = dialog;
+
+ if (whichEvents == -1) {
+ // Disable the "Ok" button until the user selects which events to
+ // delete.
+ Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ ok.setEnabled(false);
+ }
+ }
+}
diff --git a/src/com/android/calendar/Event.java b/src/com/android/calendar/Event.java
new file mode 100644
index 0000000..c15e5b5
--- /dev/null
+++ b/src/com/android/calendar/Event.java
@@ -0,0 +1,625 @@
+/*
+ * 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.preference.PreferenceManager;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Instances;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+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
+ Instances.SELF_ATTENDEE_STATUS, // 16
+ };
+
+ // 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;
+ private static final int PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16;
+
+ 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;
+
+ public int selfAttendeeStatus;
+
+ // 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;
+ e.selfAttendeeStatus = selfAttendeeStatus;
+
+ 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;
+ dest.selfAttendeeStatus = selfAttendeeStatus;
+ }
+
+ 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;
+ e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
+
+ 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;
+ }
+
+ e.selfAttendeeStatus = c.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX);
+
+ 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);
+ }
+
+ 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 0000000..ebfa6a3
--- /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 0000000..0b31b01
--- /dev/null
+++ b/src/com/android/calendar/EventInfoActivity.java
@@ -0,0 +1,724 @@
+/*
+ * 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.ContentValues;
+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.EventRecurrence;
+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.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class EventInfoActivity extends Activity implements View.OnClickListener,
+ AdapterView.OnItemSelectedListener {
+ private static final int MAX_REMINDERS = 5;
+
+ /**
+ * These are the corresponding indices into the array of strings
+ * "R.array.change_response_labels" in the resource file.
+ */
+ static final int UPDATE_SINGLE = 0;
+ static final int UPDATE_ALL = 1;
+
+ private static final String[] EVENT_PROJECTION = new String[] {
+ Events._ID, // 0 do not remove; used in DeleteEventHelper
+ Events.TITLE, // 1 do not remove; used in DeleteEventHelper
+ Events.RRULE, // 2 do not remove; used in DeleteEventHelper
+ Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper
+ Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper
+ Events.DTSTART, // 5 do not remove; used in DeleteEventHelper
+ Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper
+ Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper
+ Events.DESCRIPTION, // 8
+ Events.EVENT_LOCATION, // 9
+ Events.HAS_ALARM, // 10
+ Events.ACCESS_LEVEL, // 11
+ Events.COLOR, // 12
+ };
+ private static final int EVENT_INDEX_ID = 0;
+ private static final int EVENT_INDEX_TITLE = 1;
+ private static final int EVENT_INDEX_RRULE = 2;
+ private static final int EVENT_INDEX_ALL_DAY = 3;
+ private static final int EVENT_INDEX_CALENDAR_ID = 4;
+ private static final int EVENT_INDEX_SYNC_ID = 6;
+ private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
+ private static final int EVENT_INDEX_DESCRIPTION = 8;
+ private static final int EVENT_INDEX_EVENT_LOCATION = 9;
+ private static final int EVENT_INDEX_HAS_ALARM = 10;
+ private static final int EVENT_INDEX_ACCESS_LEVEL = 11;
+ private static final int EVENT_INDEX_COLOR = 12;
+
+ private static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees._ID, // 0
+ Attendees.ATTENDEE_RELATIONSHIP, // 1
+ Attendees.ATTENDEE_STATUS, // 2
+ };
+ private static final int ATTENDEES_INDEX_ID = 0;
+ 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";
+
+ static final String[] CALENDARS_PROJECTION = new String[] {
+ Calendars._ID, // 0
+ Calendars.DISPLAY_NAME, // 1
+ };
+ static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+ 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 EditResponseHelper mEditResponseHelper;
+
+ private int mResponseOffset;
+ private int mOriginalAttendeeResponse;
+ private boolean mIsRepeating;
+
+ // This is called when one of the "remove reminder" buttons is selected.
+ public void onClick(View v) {
+ LinearLayout reminderItem = (LinearLayout) v.getParent();
+ LinearLayout parent = (LinearLayout) reminderItem.getParent();
+ parent.removeView(reminderItem);
+ mReminderItems.remove(reminderItem);
+ updateRemindersVisibility();
+ }
+
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ // If they selected the "No response" option, then don't display the
+ // dialog asking which events to change.
+ if (id == 0 && mResponseOffset == 0) {
+ return;
+ }
+
+ // If this is not a repeating event, then don't display the dialog
+ // asking which events to change.
+ if (!mIsRepeating) {
+ return;
+ }
+
+ // If the selection is the same as the original, then don't display the
+ // dialog asking which events to change.
+ int index = findResponseIndexFor(mOriginalAttendeeResponse);
+ if (position == index + mResponseOffset) {
+ return;
+ }
+
+ // This is a repeating event. We need to ask the user if they mean to
+ // change just this one instance or all instances.
+ mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ }
+
+ @Override
+ 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);
+ if (initEventCursor()) {
+ // The cursor is empty. This can happen if the event was deleted.
+ finish();
+ return;
+ }
+
+ 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 */);
+ mEditResponseHelper = new EditResponseHelper(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (initEventCursor()) {
+ // The cursor is empty. This can happen if the event was deleted.
+ finish();
+ return;
+ }
+ initAttendeesCursor();
+ initCalendarsCursor();
+ }
+
+ /**
+ * Initializes the event cursor, which is expected to point to the first
+ * (and only) result from a query.
+ * @return true if the cursor is empty.
+ */
+ private boolean initEventCursor() {
+ if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
+ return true;
+ }
+ mEventCursor.moveToFirst();
+ mVisibility = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL);
+ mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
+ String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
+ mIsRepeating = (rRule != null);
+ return false;
+ }
+
+ 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();
+ if (!isFinishing()) {
+ return;
+ }
+ ContentResolver cr = getContentResolver();
+ ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems,
+ mReminderValues);
+ boolean changed = EditEvent.saveReminders(cr, mEventId, reminderMinutes, mOriginalMinutes);
+ changed |= saveResponse(cr);
+ if (changed) {
+ Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItem item;
+ item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
+ R.string.add_new_reminder);
+ item.setIcon(R.drawable.ic_menu_reminder);
+ item.setAlphabeticShortcut('r');
+
+ item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label);
+ item.setIcon(android.R.drawable.ic_menu_edit);
+ item.setAlphabeticShortcut('e');
+
+ item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label);
+ item.setIcon(android.R.drawable.ic_menu_delete);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // 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);
+ }
+ }
+
+ /**
+ * Saves the response to an invitation if the user changed the response.
+ * Returns true if the database was updated.
+ *
+ * @param cr the ContentResolver
+ * @return true if the database was changed
+ */
+ private boolean saveResponse(ContentResolver cr) {
+ if (mAttendeesCursor == null || mEventCursor == null) {
+ return false;
+ }
+ Spinner spinner = (Spinner) findViewById(R.id.response_value);
+ int position = spinner.getSelectedItemPosition() - mResponseOffset;
+ if (position <= 0) {
+ return false;
+ }
+
+ int status = ATTENDEE_VALUES[position];
+
+ // If the status has not changed, then don't update the database
+ if (status == mOriginalAttendeeResponse) {
+ return false;
+ }
+
+ long attendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
+ if (!mIsRepeating) {
+ // This is a non-repeating event
+ updateResponse(cr, mEventId, attendeeId, status);
+ return true;
+ }
+
+ // This is a repeating event
+ int whichEvents = mEditResponseHelper.getWhichEvents();
+ switch (whichEvents) {
+ case -1:
+ return false;
+ case UPDATE_SINGLE:
+ createExceptionResponse(cr, mEventId, attendeeId, status);
+ return true;
+ case UPDATE_ALL:
+ updateResponse(cr, mEventId, attendeeId, status);
+ return true;
+ default:
+ Log.e("Calendar", "Unexpected choice for updating invitation response");
+ break;
+ }
+ return false;
+ }
+
+ private void updateResponse(ContentResolver cr, long eventId, long attendeeId, int status) {
+ // Update the "selfAttendeeStatus" field for the event
+ ContentValues values = new ContentValues();
+
+ // Will need to add email when MULTIPLE_ATTENDEES_PER_EVENT supported.
+ values.put(Attendees.ATTENDEE_STATUS, status);
+ values.put(Attendees.EVENT_ID, eventId);
+
+ Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
+ cr.update(uri, values, null /* where */, null /* selection args */);
+ }
+
+ private void createExceptionResponse(ContentResolver cr, long eventId,
+ long attendeeId, int status) {
+ // Fetch information about the repeating event.
+ Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+ Cursor cursor = cr.query(uri, EVENT_PROJECTION, null, null, null);
+ if (cursor == null) {
+ return;
+ }
+
+ try {
+ cursor.moveToFirst();
+ ContentValues values = new ContentValues();
+
+ String title = cursor.getString(EVENT_INDEX_TITLE);
+ String timezone = cursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
+ int calendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID);
+ boolean allDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+ String syncId = cursor.getString(EVENT_INDEX_SYNC_ID);
+
+ values.put(Events.TITLE, title);
+ values.put(Events.EVENT_TIMEZONE, timezone);
+ values.put(Events.ALL_DAY, allDay ? 1 : 0);
+ values.put(Events.CALENDAR_ID, calendarId);
+ values.put(Events.DTSTART, mStartMillis);
+ values.put(Events.DTEND, mEndMillis);
+ values.put(Events.ORIGINAL_EVENT, syncId);
+ values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
+ values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
+ values.put(Events.STATUS, Events.STATUS_CONFIRMED);
+ values.put(Events.SELF_ATTENDEE_STATUS, status);
+
+ // Create a recurrence exception
+ Uri newUri = cr.insert(Events.CONTENT_URI, values);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private int findResponseIndexFor(int response) {
+ int size = ATTENDEE_VALUES.length;
+ for (int index = 0; index < size; index++) {
+ if (ATTENDEE_VALUES[index] == response) {
+ return index;
+ }
+ }
+ 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;
+
+ View strip = (View) findViewById(R.id.strip);
+ strip.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
+
+ TextView title = (TextView) findViewById(R.id.title);
+ title.setTextColor(color);
+
+ View divider = (View) findViewById(R.id.divider);
+ divider.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(this, 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);
+
+ mOriginalAttendeeResponse = ATTENDEE_NO_RESPONSE;
+ if (mAttendeesCursor != null) {
+ mOriginalAttendeeResponse = 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 ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED)
+ && (mOriginalAttendeeResponse != ATTENDEE_NO_RESPONSE)
+ && (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) {
+ CharSequence[] entries;
+ entries = getResources().getTextArray(R.array.response_labels2);
+ mResponseOffset = -1;
+ ArrayAdapter<CharSequence> adapter =
+ new ArrayAdapter<CharSequence>(this,
+ android.R.layout.simple_spinner_item, entries);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+ }
+
+ int index = findResponseIndexFor(mOriginalAttendeeResponse);
+ spinner.setSelection(index + mResponseOffset);
+ spinner.setOnItemSelectedListener(this);
+ }
+
+ private void setTextCommon(int id, CharSequence text) {
+ TextView textView = (TextView) findViewById(id);
+ if (textView == null)
+ return;
+ textView.setText(text);
+ }
+
+ private void setVisibilityCommon(int id, int visibility) {
+ View v = findViewById(id);
+ if (v != null) {
+ v.setVisibility(visibility);
+ }
+ return;
+ }
+}
diff --git a/src/com/android/calendar/EventLoader.java b/src/com/android/calendar/EventLoader.java
new file mode 100644
index 0000000..eb8a82d
--- /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 0000000..c4f5fde
--- /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 0000000..4dabc5c
--- /dev/null
+++ b/src/com/android/calendar/LaunchActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
+import com.google.android.googlelogin.GoogleLoginServiceHelper;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Gmail;
+
+public class LaunchActivity extends Activity {
+
+ // An arbitrary constant to pass to the GoogleLoginHelperService
+ private static final int GET_ACCOUNT_REQUEST = 1;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Our UI is not something intended for the user to see. We just
+ // stick around until we can figure out what to do next based on
+ // the current state of the system.
+ setVisible(false);
+
+ // Only try looking for an account if this is the first launch.
+ if (icicle == null) {
+ // This will request a Gmail account and if none are present, it will
+ // invoke SetupWizard to login or create one. The result is returned
+ // through onActivityResult().
+ Bundle bundle = new Bundle();
+ bundle.putCharSequence("optional_message", getText(R.string.calendar_plug));
+ GoogleLoginServiceHelper.getCredentials(
+ this,
+ GET_ACCOUNT_REQUEST,
+ bundle,
+ GoogleLoginServiceConstants.PREFER_HOSTED,
+ Gmail.GMAIL_AUTH_SERVICE,
+ true);
+ }
+ }
+
+ private void onAccountsLoaded(String account) {
+ 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();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ if (requestCode == GET_ACCOUNT_REQUEST) {
+ if (resultCode == RESULT_OK) {
+ if (intent != null) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final String account;
+ account = extras.getString(GoogleLoginServiceConstants.AUTH_ACCOUNT_KEY);
+ onAccountsLoaded(account);
+ }
+ }
+ } else {
+ finish();
+ }
+ }
+ }
+}
diff --git a/src/com/android/calendar/MenuHelper.java b/src/com/android/calendar/MenuHelper.java
new file mode 100644
index 0000000..f23cbf9
--- /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.text.format.DateUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class MenuHelper {
+ private static final int MENU_GROUP_AGENDA = 1;
+ private static final int MENU_GROUP_DAY = 2;
+ private static final int MENU_GROUP_WEEK = 3;
+ private static final int MENU_GROUP_MONTH = 4;
+ private static final int MENU_GROUP_EVENT_CREATE = 5;
+ private static final int MENU_GROUP_TODAY = 6;
+ private static final int MENU_GROUP_SELECT_CALENDARS = 7;
+ private static final int MENU_GROUP_PREFERENCES = 8;
+
+ public static final int MENU_GOTO_TODAY = 1;
+ public static final int MENU_AGENDA = 2;
+ public static final int MENU_DAY = 3;
+ public static final int MENU_WEEK = 4;
+ public static final int MENU_EVENT_VIEW = 5;
+ public static final int MENU_EVENT_CREATE = 6;
+ public static final int MENU_EVENT_EDIT = 7;
+ public static final int MENU_EVENT_DELETE = 8;
+ public static final int MENU_MONTH = 9;
+ public static final int MENU_SELECT_CALENDARS = 10;
+ public static final int MENU_PREFERENCES = 11;
+
+ public static void onPrepareOptionsMenu(Activity activity, Menu menu) {
+
+ if (activity instanceof AgendaActivity) {
+ menu.setGroupVisible(MENU_GROUP_AGENDA, 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 0000000..89b07ec
--- /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.preference.PreferenceManager;
+import android.provider.Calendar.Events;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+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 0000000..f6db3b8
--- /dev/null
+++ b/src/com/android/calendar/MonthView.java
@@ -0,0 +1,1366 @@
+/*
+ * 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.provider.Calendar.BusyBits;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+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 = 0;
+ 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(getContext(), new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ // The user might do a slow "fling" after touching the screen
+ // and we don't want the long-press to pop up a context menu.
+ // Setting mLaunchDayView to false prevents the long-press.
+ mLaunchDayView = false;
+ mSelectionMode = SELECTION_HIDDEN;
+
+ int distanceX = Math.abs((int) e2.getX() - (int) e1.getX());
+ int distanceY = Math.abs((int) e2.getY() - (int) e1.getY());
+ if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) {
+ return false;
+ }
+
+ // Switch to a different month
+ Time time = mOtherViewCalendar;
+ time.set(mViewCalendar);
+ if (velocityY < 0) {
+ time.month += 1;
+ } else {
+ time.month -= 1;
+ }
+ time.normalize(true);
+ mParentActivity.goTo(time);
+
+ 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;
+
+ final long startMillis = getSelectedTimeInMillis();
+ final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_ABBREV_MONTH;
+
+ final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
+ menu.setHeaderTitle(title);
+
+ item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_day);
+ item.setAlphabeticShortcut('d');
+
+ item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_agenda);
+ item.setAlphabeticShortcut('a');
+
+ item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_add);
+ item.setAlphabeticShortcut('n');
+ }
+
+ private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MenuHelper.MENU_DAY: {
+ long startMillis = getSelectedTimeInMillis();
+ 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);
+ }
+ }
+ }
+
+ drawGrid(canvas, p);
+ }
+
+ @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 the grid lines for the calendar
+ * @param canvas The canvas to draw on.
+ * @param p The paint used for drawing.
+ */
+ private void drawGrid(Canvas canvas, Paint p) {
+ p.setColor(mMonthOtherMonthColor);
+ p.setAntiAlias(false);
+
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+
+ for (int row = 0; row < 6; row++) {
+ int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1;
+ canvas.drawLine(0, y, width, y, p);
+ }
+ for (int column = 1; column < 7; column++) {
+ int x = mBorder + column * (MONTH_DAY_GAP + mCellWidth) - 1;
+ canvas.drawLine(x, WEEK_GAP, x, height, p);
+ }
+ }
+
+ /**
+ * Draw a single box onto the canvas.
+ * @param day The Julian day.
+ * @param weekNum The week number.
+ * @param row The row of the box (0-5).
+ * @param column The column of the box (0-6).
+ * @param canvas The canvas to draw on.
+ * @param p The paint used for drawing.
+ * @param r The rectangle used for each box.
+ * @param isLandscape Is the current orientation landscape.
+ */
+ private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
+ Rect r, boolean isLandscape) {
+
+ // Only draw the selection if we are in the press state or if we have
+ // moved the cursor with key input.
+ boolean drawSelection = false;
+ if (mSelectionMode != SELECTION_HIDDEN) {
+ drawSelection = mCursor.isSelected(row, column);
+ }
+
+ boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column);
+ boolean isToday = false;
+ int dayOfBox = mCursor.getDayAt(row, column);
+ if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year
+ && mCursor.getMonth() == mToday.month) {
+ isToday = true;
+ }
+
+ int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
+ int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
+
+ r.left = x;
+ r.top = y;
+ r.right = x + mCellWidth;
+ r.bottom = y + mCellHeight;
+
+
+ // Adjust the left column, right column, and bottom row to leave
+ // no border.
+ if (column == 0) {
+ r.left = -1;
+ } else if (column == 6) {
+ r.right += mBorder + 2;
+ }
+
+ if (row == 5) {
+ r.bottom = getMeasuredHeight();
+ }
+
+ // Draw the cell contents (excluding monthDay number)
+ if (!withinCurrentMonth) {
+ boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
+
+ // Adjust cell boundaries to compensate for the different border
+ // style.
+ r.top--;
+ if (column != 0) {
+ r.left--;
+ }
+ } 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.month += mCursor.getSelectedMonthOffset();
+ time.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // Restore the saved hour:minute:second offset from when we entered
+ // this view.
+ time.second = mSavedTime.second;
+ time.minute = mSavedTime.minute;
+ time.hour = mSavedTime.hour;
+ return time.normalize(true);
+ }
+
+ Time getTime() {
+ return mViewCalendar;
+ }
+
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ public void setSelectionMode(int selectionMode) {
+ mSelectionMode = selectionMode;
+ }
+
+ private void drawingCalc(int width, int height) {
+ mCellHeight = (height - (6 * WEEK_GAP)) / 6;
+ mEventGeometry.setHourHeight((mCellHeight - 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(mParentActivity,
+ event.startMillis, event.endMillis, flags);
+ } else {
+ timeRange = DateUtils.formatDateRange(mParentActivity,
+ event.startMillis, event.startMillis, flags);
+ }
+
+ TextView timeView = null;
+ TextView titleView = null;
+ switch (eventIndex) {
+ case 0:
+ timeView = (TextView) mPopupView.findViewById(R.id.time0);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
+ break;
+ case 1:
+ timeView = (TextView) mPopupView.findViewById(R.id.time1);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
+ break;
+ case 2:
+ timeView = (TextView) mPopupView.findViewById(R.id.time2);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
+ break;
+ case 3:
+ timeView = (TextView) mPopupView.findViewById(R.id.time3);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
+ break;
+ }
+
+ timeView.setText(timeRange);
+ titleView.setText(event.title);
+ eventIndex += 1;
+ }
+ if (eventIndex == 0) {
+ // We didn't find any events for this day
+ mPopup.dismiss();
+ return;
+ }
+
+ // Hide the items that have no event information
+ View view;
+ switch (eventIndex) {
+ case 1:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 2:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 3:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 4:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ default:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.VISIBLE);
+ TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more);
+ tv.setVisibility(View.VISIBLE);
+ String format = mResources.getString(R.string.plus_N_more);
+ String plusMore = String.format(format, eventIndex - 4);
+ tv.setText(plusMore);
+ break;
+ }
+
+ if (eventIndex > 5) {
+ eventIndex = 5;
+ }
+ int popupHeight = 20 * eventIndex + 15;
+ mPopup.setHeight(popupHeight);
+
+ if (mPreviousPopupHeight != popupHeight) {
+ mPreviousPopupHeight = popupHeight;
+ mPopup.dismiss();
+ }
+ mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
+ postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ long duration = event.getEventTime() - event.getDownTime();
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (mSelectionMode == SELECTION_HIDDEN) {
+ // Don't do anything unless the selection is visible.
+ break;
+ }
+
+ if (mSelectionMode == SELECTION_PRESSED) {
+ // This was the first press when there was nothing selected.
+ // Change the selection from the "pressed" state to the
+ // the "selected" state. We treat short-press and
+ // long-press the same here because nothing was selected.
+ mSelectionMode = SELECTION_SELECTED;
+ mRedrawScreen = true;
+ invalidate();
+ break;
+ }
+
+ // Check the duration to determine if this was a short press
+ if (duration < ViewConfiguration.getLongPressTimeout()) {
+ long millis = getSelectedTimeInMillis();
+ Utils.startActivity(getContext(), mDetailedView, millis);
+ 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 0000000..e05a603
--- /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.text.format.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 0000000..44943eb
--- /dev/null
+++ b/src/com/android/calendar/SelectCalendarsActivity.java
@@ -0,0 +1,279 @@
+/*
+ * 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();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ 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(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, 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 0000000..fb50662
--- /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 0000000..53c584c
--- /dev/null
+++ b/src/com/android/calendar/Utils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.content.res.Resources;
+import android.text.format.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 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) {
+ Resources res = Resources.getSystem();
+ return time.format(res.getString(com.android.internal.R.string.month_year));
+ }
+
+ // TODO: replace this with the correct i18n way to do this
+ public static final String englishNthDay[] = {
+ "", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th",
+ "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th",
+ "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th",
+ "30th", "31st"
+ };
+
+ public static String formatNth(int nth) {
+ return "the " + englishNthDay[nth];
+ }
+
+ /**
+ * 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 0000000..0e06b51
--- /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 0000000..b0b3244
--- /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 0000000..28a573f
--- /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 0000000..2558ec2
--- /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 0000000..975f6af
--- /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 0000000..222a64a
--- /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 0000000..6f0d775
--- /dev/null
+++ b/tests/src/com/android/calendar/FormatDateRangeTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.text.format.DateUtils;
+import android.text.format.Time;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.util.Calendar;
+
+/**
+ * Unit tests for {@link android.text.format.DateUtils#formatDateRange}.
+ */
+public class FormatDateRangeTest extends AndroidTestCase {
+
+ private class DateTest {
+ public Time date1;
+ public Time date2;
+ public int flags;
+ public String expectedOutput;
+
+ public DateTest(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;
+ }
+
+ // Single point in time. (not a range)
+ public DateTest(int year1, int month1, int day1, int hour1, int minute1,
+ int flags, String output) {
+ this(year1, month1, day1, hour1, minute1,
+ year1, month1, day1, hour1, minute1,
+ flags, output);
+ }
+ }
+
+ private Resources mResources;
+
+ DateTest[] tests = {
+ new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 11, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8am \u2013 11am"),
+ new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 11, 0,
+ DateUtils.FORMAT_SHOW_TIME, "8:00am \u2013 11:00am"),
+ new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 17, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR, "08:00 \u2013 17:00"),
+ new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 12, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8am \u2013 noon"),
+ new DateTest(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 DateTest(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 DateTest(0, 10, 9, 10, 30, 0, 10, 9, 13, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "10:30am \u2013 1pm"),
+ new DateTest(0, 10, 9, 13, 0, 0, 10, 9, 14, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "1pm \u2013 2pm"),
+ new DateTest(0, 10, 9, 0, 0, 0, 10, 9, 14, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 2pm"),
+ new DateTest(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8pm \u2013 midnight"),
+ new DateTest(0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am"),
+ new DateTest(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 DateTest(0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+ "00:00"),
+ new DateTest(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+ new DateTest(0, 10, 10, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 10"),
+ new DateTest(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+ "Nov 9"),
+ new DateTest(0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+ "Nov 10"),
+ new DateTest(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 DateTest(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 DateTest(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 midnight"),
+ new DateTest(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 DateTest(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+ new DateTest(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+ new DateTest(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_UTC, "November 9"),
+ new DateTest(0, 10, 8, 0, 0, 0, 10, 10, 0, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 8 \u2013 9"),
+ new DateTest(0, 10, 9, 0, 0, 0, 10, 11, 0, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9 \u2013 10"),
+ new DateTest(0, 10, 9, 8, 0, 0, 10, 11, 17, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9 \u2013 11"),
+ new DateTest(0, 9, 29, 8, 0, 0, 10, 3, 17, 0,
+ DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Oct 29 \u2013 Nov 3"),
+ new DateTest(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 DateTest(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 DateTest(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 DateTest(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 DateTest(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 DateTest(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 DateTest(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"),
+ // Tests that FORMAT_SHOW_YEAR takes precedence over FORMAT_NO_YEAR:
+ new DateTest(2007, 11, 3, 13, 0, 2007, 11, 3, 13, 0,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_YEAR,
+ "December 3, 2007"),
+ // Tests that year isn't shown by default with no year flags when time is the current year:
+ new DateTest(
+ Calendar.getInstance().get(Calendar.YEAR), 0, 3, 13, 0,
+ DateUtils.FORMAT_SHOW_DATE,
+ "January 3"),
+ // Tests that the year is shown by default with no year flags when time isn't the current year:
+ new DateTest(
+ Calendar.getInstance().get(Calendar.YEAR) - 1, 0, 3, 13, 0,
+ DateUtils.FORMAT_SHOW_DATE,
+ "January 3, 2008"),
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mResources = mContext.getResources();
+ }
+
+ @MediumTest
+ public void testAll() throws Exception {
+ int len = tests.length;
+ for (int index = 0; index < len; index++) {
+ DateTest dateTest = tests[index];
+ long startMillis = dateTest.date1.toMillis(false /* use isDst */);
+ long endMillis = dateTest.date2.toMillis(false /* use isDst */);
+ int flags = dateTest.flags;
+ String output = DateUtils.formatDateRange(mContext, startMillis, endMillis, flags);
+ if (!dateTest.expectedOutput.equals(output)) {
+ Log.i("FormatDateRangeTest", "index " + index
+ + " expected: " + dateTest.expectedOutput
+ + " actual: " + output);
+ }
+ assertEquals(dateTest.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 0000000..6b3774b
--- /dev/null
+++ b/tests/src/com/android/calendar/WeekNumberTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.text.format.DateUtils;
+import android.text.format.Time;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+
+/**
+ * Unit tests for {@link android.text.format.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();
+ }
+
+ @SmallTest
+ 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.formatDateTime(mContext, 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.formatDateTime(mContext, millis, flags);
+ Log.i("WeekNumberTest", "(all-day) index " + index
+ + " date: " + output
+ + " expected: " + test.expectedWeekNumber
+ + " actual: " + weekNumber);
+ }
+ assertEquals(weekNumber, test.expectedWeekNumber);
+ }
+ }
+}