diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-11-01 18:51:00 +0000 |
---|---|---|
committer | Raman Tenneti <rtenneti@google.com> | 2018-11-14 08:31:05 -0800 |
commit | a17d3d7135758b361f3e886d77f5a360f279447f (patch) | |
tree | 9d2e3de07d86faa3e4f677167d1cbbe720b3023e | |
parent | 7b051c6e9ef7922f08d9ebf664ac3087d5fa0552 (diff) | |
parent | 551499ec6d3878cd3dce99bebe08c4552d05c29c (diff) | |
download | Calendar-a17d3d7135758b361f3e886d77f5a360f279447f.tar.gz |
AOSP/Calendar - update the Calendar version to target P (28) or higher.
Bug: 119500483
Test: manual - Ran the following CTS tests on Pixel phone. Tested the calendar UI.
$ adb shell am instrument -w com.android.calendar.tests
$ ./development/testrunner/runtest.py --path cts/tests/tests/provider/src/android/provider/cts/CalendarTest.java
android.provider.cts.CalendarTest:................................
Time: 16.918
OK (32 tests)
$ ./development/testrunner/runtest.py --path cts/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
android.content.cts.AvailableIntentsTest:.................................
Time: 0.202
OK (33 tests)
$ ./development/testrunner/runtest.py --path cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/src/com/android/cts/privilegedupdate/PrivilegedUpdateTest.java
com.android.cts.privilegedupdate.PrivilegedUpdateTest:...
Time: 0.204
OK (3 tests)
$ ./development/testrunner/runtest.py --path cts/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
android.permission2.cts.ProtectedBroadcastsTest:..
Time: 0.139
OK (2 tests)
Change-Id: Id3e48f100d685aaf03c2c726b75def4437b980cb
105 files changed, 86 insertions, 28761 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5673a3b5..9fa49957 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -38,7 +38,8 @@ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" /> - <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="18"></uses-sdk> + <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"></uses-sdk> + <application android:name="CalendarApplication" android:label="@string/app_label" android:icon="@mipmap/ic_launcher_calendar" @@ -130,40 +131,9 @@ </intent-filter> </activity> - <activity android:name=".selectcalendars.SelectVisibleCalendarsActivity" - android:label="@string/select_visible_calendars_title" - android:theme="@android:style/Theme.Holo.Light"/> - - <activity android:name=".selectcalendars.SelectSyncedCalendarsMultiAccountActivity" - android:label="@string/select_synced_calendars_title" - android:theme="@android:style/Theme.Holo.Light"/> - - <activity android:name="CalendarSettingsActivity" android:label="@string/preferences_title" - android:theme="@android:style/Theme.Holo.Light"/> - <!-- Declarations for search --> <!-- Make all activities a searchable context --> - <meta-data android:name="android.app.default_searchable" - android:value="com.android.calendar.SearchActivity"/> - - <activity android:name="SearchActivity" android:label="@string/search_title" - android:launchMode="singleTop" android:theme="@style/CalendarTheme.WithActionBar" - android:windowSoftInputMode="stateAlwaysHidden" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.SEARCH"/> - </intent-filter> - <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> - </activity> - - <provider android:name=".CalendarRecentSuggestionsProvider" - android:exported="false" - android:authorities="com.android.calendar.CalendarRecentSuggestionsProvider" /> - <!-- Declarations for alerts/reminders --> - <activity android:name=".alerts.AlertActivity" android:launchMode="singleInstance" - android:theme="@android:style/Theme.Holo.Dialog" android:excludeFromRecents="true" /> - <activity android:name=".alerts.QuickResponseActivity" android:launchMode="singleInstance" android:theme="@android:style/Theme.Holo.Dialog" android:excludeFromRecents="true" android:label="@string/quick_response_dialog_title" /> diff --git a/proguard.flags b/proguard.flags index bd7f0d54..923f6382 100644 --- a/proguard.flags +++ b/proguard.flags @@ -1,20 +1,11 @@ --keep class com.android.calendar.selectcalendars.SelectCalendarsSyncFragment -keep class com.android.calendar.OtherPreferences --keep class com.android.calendar.AboutPreferences -keep class com.android.calendar.GeneralPreferences -keepclassmembers class com.android.calendar.AllInOneActivity { *** setControlsOffset(...); } --keepclassmembers class com.android.calendar.selectcalendars.SelectVisibleCalendarsActivity { - *** handleSelectSyncedCalendarsClicked(...); -} -keepclassmembers class com.android.calendar.AllInOneActivity { *** handleSelectSyncedCalendarsClicked(...); } --keepclassmembers class com.android.calendar.AsyncQueryService { - *** setTestHandler(...); - *** getLastCancelableOperation(...); -} -keepclassmembers class com.android.calendar.AsyncQueryServiceHelper$OperationInfo { *** equivalent(...); } @@ -31,10 +22,6 @@ -keepclassmembers class com.android.calendar.month.MonthWeekEventsView { *** setAnimateTodayAlpha(...); } --keepclassmembers class com.android.calendar.event.EditEventHelper { - *** updateRecurrenceRule(...); - *** extractDomain(...); -} -keepclassmembers class * implements android.content.SharedPreferences$Editor { public *** apply(); diff --git a/res/layout-sw600dp/agenda_item.xml b/res/layout-sw600dp/agenda_item.xml index f861f54c..b25fa154 100644 --- a/res/layout-sw600dp/agenda_item.xml +++ b/res/layout-sw600dp/agenda_item.xml @@ -30,18 +30,6 @@ android:layout_rowSpan="1" android:layout_columnSpan="3" android:layout_width="match_parent" /> - <com.android.calendar.ColorChipView - android:id="@+id/agenda_item_color" - android:layout_column="0" - android:layout_row="1" - android:layout_rowSpan="1" - android:layout_width="32dip" - android:layout_height="32dip" - android:layout_gravity="top|left" - android:layout_marginTop="12dip" - android:layout_marginBottom="8dip" - android:layout_marginLeft="16dip" - android:layout_marginRight="8dip" /> <LinearLayout android:id="@+id/agenda_item_text_container" diff --git a/res/layout/agenda_item.xml b/res/layout/agenda_item.xml index 16f81506..3180b816 100644 --- a/res/layout/agenda_item.xml +++ b/res/layout/agenda_item.xml @@ -30,18 +30,6 @@ android:layout_rowSpan="1" android:layout_columnSpan="3" android:layout_width="match_parent" /> - <com.android.calendar.ColorChipView - android:id="@+id/agenda_item_color" - android:layout_column="0" - android:layout_row="1" - android:layout_rowSpan="1" - android:layout_width="24dip" - android:layout_height="24dip" - android:layout_gravity="top|left" - android:layout_marginTop="8dip" - android:layout_marginBottom="8dip" - android:layout_marginLeft="16dip" - android:layout_marginRight="8dip" /> <LinearLayout android:id="@+id/agenda_item_text_container" diff --git a/res/layout/calendar_sync_item.xml b/res/layout/calendar_sync_item.xml index 02c2c5ce..ad8c8345 100644 --- a/res/layout/calendar_sync_item.xml +++ b/res/layout/calendar_sync_item.xml @@ -23,15 +23,6 @@ android:layout_marginTop="6dip" android:layout_marginBottom="6dip"> - <com.android.calendar.selectcalendars.CalendarColorSquare - android:id="@+id/color" - android:layout_height="32dip" - android:layout_width="32dip" - android:layout_marginLeft="13dip" - android:layout_gravity="center_vertical" - android:contentDescription="@string/calendar_square_color_picker_description" - style="?android:attr/quickContactBadgeStyleWindowMedium" /> - <LinearLayout android:layout_width="0dip" android:layout_height="wrap_content" @@ -70,4 +61,4 @@ android:clickable="false" android:longClickable="false" android:focusable="false" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/res/layout/event_info.xml b/res/layout/event_info.xml index 674fd1a0..07737abd 100644 --- a/res/layout/event_info.xml +++ b/res/layout/event_info.xml @@ -264,15 +264,6 @@ style="@style/TextAppearance.EditEvent_LabelSmall" /> </LinearLayout> - <!-- GUEST LIST --> - <com.android.calendar.event.AttendeesView - android:id="@+id/long_attendee_list" - android:textColor="@color/event_info_body_color" - android:orientation="vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:visibility="gone" /> - <!-- REMINDERS --> <TextView android:layout_width="match_parent" diff --git a/res/layout/event_info_dialog.xml b/res/layout/event_info_dialog.xml index 2c51c77d..59e036a2 100644 --- a/res/layout/event_info_dialog.xml +++ b/res/layout/event_info_dialog.xml @@ -379,16 +379,6 @@ style="@style/TextAppearance.EditEvent_LabelSmall" /> </LinearLayout> - <!-- GUEST LIST --> - <com.android.calendar.event.AttendeesView - android:id="@+id/long_attendee_list" - android:textColor="@color/event_info_body_color" - android:orientation="vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:visibility="gone" - android:layout_marginTop="5dip"/> - <!-- REMINDERS --> <LinearLayout android:id="@+id/reminders_row" diff --git a/res/layout/mini_calendar_item.xml b/res/layout/mini_calendar_item.xml index f4af9758..939bace2 100644 --- a/res/layout/mini_calendar_item.xml +++ b/res/layout/mini_calendar_item.xml @@ -25,17 +25,6 @@ android:layout_marginBottom="0dip" android:layout_weight="1"> - <com.android.calendar.selectcalendars.CalendarColorSquare - android:id="@+id/color" - android:layout_height="20dip" - android:layout_width="20dip" - android:layout_marginLeft="16dip" - android:layout_gravity="center_vertical" - android:contentDescription="@string/calendar_square_color_picker_description" - style="?android:attr/quickContactBadgeStyleWindowMedium" - android:cropToPadding="true" - /> - <TextView android:id="@+id/calendar" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/res/layout/recurrencepicker.xml b/res/layout/recurrencepicker.xml index 49daabe4..d31cd980 100644 --- a/res/layout/recurrencepicker.xml +++ b/res/layout/recurrencepicker.xml @@ -116,52 +116,6 @@ android:layout_height="wrap_content" /> </LinearLayout> - <com.android.calendar.recurrencepicker.LinearLayoutWithMaxWidth - android:id="@+id/weekGroup" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" - android:layout_marginTop="8dp" - android:gravity="center_horizontal" - android:orientation="horizontal" > - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - </com.android.calendar.recurrencepicker.LinearLayoutWithMaxWidth> - - <com.android.calendar.recurrencepicker.LinearLayoutWithMaxWidth - android:id="@+id/weekGroup2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" - android:gravity="center_horizontal" - android:orientation="horizontal" - android:visibility="gone" > - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" /> - - <com.android.calendar.recurrencepicker.WeekButton - style="@style/RecurrenceDayOfWeekStyle" - android:visibility="invisible" /> - </com.android.calendar.recurrencepicker.LinearLayoutWithMaxWidth> - <RadioGroup android:id="@+id/monthGroup" android:layout_width="match_parent" diff --git a/res/menu-land/all_in_one_title_bar.xml b/res/menu-land/all_in_one_title_bar.xml index 9f20fa41..062b777c 100644 --- a/res/menu-land/all_in_one_title_bar.xml +++ b/res/menu-land/all_in_one_title_bar.xml @@ -23,37 +23,6 @@ android:icon="@drawable/today_icon" android:showAsAction="withText|ifRoom" /> <item - android:id="@+id/action_create_event" - android:alphabeticShortcut="n" - android:title="@string/event_create" - android:icon="@drawable/ic_menu_add_event_holo_light" - android:showAsAction="ifRoom" /> - <item - android:id="@+id/action_refresh" - android:alphabeticShortcut="r" - android:showAsAction="never" - android:title="@string/calendar_refresh" - android:icon="@drawable/ic_menu_refresh_holo_light" /> - <item - android:id="@+id/action_search" - android:alphabeticShortcut="f" - android:title="@string/search" - android:icon="@drawable/ic_menu_search_holo_light" - android:showAsAction="never|collapseActionView" - android:actionViewClass="android.widget.SearchView" - android:imeOptions="actionSearch" /> - <item - android:id="@+id/action_select_visible_calendars" - android:title="@string/menu_select_visible_calendars" - android:icon="@drawable/ic_menu_select_visible_calendars_holo_light" - android:showAsAction="never" /> - <item - android:id="@+id/action_settings" - android:alphabeticShortcut="s" - android:showAsAction="never" - android:title="@string/menu_preferences" - android:icon="@drawable/ic_menu_settings_holo_light" /> - <item android:id="@+id/action_hide_controls" android:alphabeticShortcut="h" android:showAsAction="never" diff --git a/res/menu-sw600dp-land/all_in_one_title_bar.xml b/res/menu-sw600dp-land/all_in_one_title_bar.xml index 8e877e56..5b285fd8 100644 --- a/res/menu-sw600dp-land/all_in_one_title_bar.xml +++ b/res/menu-sw600dp-land/all_in_one_title_bar.xml @@ -23,31 +23,7 @@ android:icon="@drawable/today_icon" android:showAsAction="always|withText" /> <item - android:id="@+id/action_create_event" - android:alphabeticShortcut="n" - android:title="@string/event_create" - android:showAsAction="ifRoom" - android:icon="@drawable/ic_menu_add_event_holo_light" /> - <item - android:id="@+id/action_refresh" - android:alphabeticShortcut="r" - android:title="@string/calendar_refresh" - android:icon="@drawable/ic_menu_refresh_holo_light" /> - <item - android:id="@+id/action_search" - android:alphabeticShortcut="f" - android:title="@string/search" - android:icon="@drawable/ic_menu_search_holo_light" - android:showAsAction="collapseActionView" - android:actionViewClass="android.widget.SearchView" - android:imeOptions="actionSearch" /> - <item android:id="@+id/action_hide_controls" android:alphabeticShortcut="h" android:title="@string/hide_controls" /> - <item - android:id="@+id/action_settings" - android:alphabeticShortcut="s" - android:title="@string/menu_preferences" - android:icon="@drawable/ic_menu_settings_holo_light" /> </menu> diff --git a/res/menu-sw600dp/all_in_one_title_bar.xml b/res/menu-sw600dp/all_in_one_title_bar.xml index 6d88e7ae..5b285fd8 100644 --- a/res/menu-sw600dp/all_in_one_title_bar.xml +++ b/res/menu-sw600dp/all_in_one_title_bar.xml @@ -23,31 +23,7 @@ android:icon="@drawable/today_icon" android:showAsAction="always|withText" /> <item - android:id="@+id/action_create_event" - android:alphabeticShortcut="n" - android:showAsAction="ifRoom" - android:title="@string/event_create" - android:icon="@drawable/ic_menu_add_event_holo_light" /> - <item - android:id="@+id/action_refresh" - android:alphabeticShortcut="r" - android:title="@string/calendar_refresh" - android:icon="@drawable/ic_menu_refresh_holo_light" /> - <item - android:id="@+id/action_search" - android:alphabeticShortcut="f" - android:title="@string/search" - android:icon="@drawable/ic_menu_search_holo_light" - android:showAsAction="collapseActionView" - android:actionViewClass="android.widget.SearchView" - android:imeOptions="actionSearch" /> - <item android:id="@+id/action_hide_controls" android:alphabeticShortcut="h" android:title="@string/hide_controls" /> - <item - android:id="@+id/action_settings" - android:alphabeticShortcut="s" - android:title="@string/menu_preferences" - android:icon="@drawable/ic_menu_settings_holo_light" /> </menu> diff --git a/res/menu/all_in_one_title_bar.xml b/res/menu/all_in_one_title_bar.xml index ad3598b2..64dc84a5 100644 --- a/res/menu/all_in_one_title_bar.xml +++ b/res/menu/all_in_one_title_bar.xml @@ -23,40 +23,4 @@ android:icon="@drawable/today_icon" android:showAsAction="ifRoom" android:orderInCategory="1" /> - <item - android:id="@+id/action_create_event" - android:alphabeticShortcut="n" - android:title="@string/event_create" - android:icon="@drawable/ic_menu_add_event_holo_light" - android:showAsAction="never" - android:orderInCategory="2" /> - <item - android:id="@+id/action_refresh" - android:alphabeticShortcut="r" - android:title="@string/calendar_refresh" - android:icon="@drawable/ic_menu_refresh_holo_light" - android:showAsAction="never" - android:orderInCategory="3" /> - <item - android:id="@+id/action_search" - android:alphabeticShortcut="f" - android:title="@string/search" - android:icon="@drawable/ic_menu_search_holo_light" - android:showAsAction="never|collapseActionView" - android:actionViewClass="android.widget.SearchView" - android:imeOptions="actionSearch" - android:orderInCategory="4" /> - <item - android:id="@+id/action_select_visible_calendars" - android:title="@string/menu_select_visible_calendars" - android:icon="@drawable/ic_menu_select_visible_calendars_holo_light" - android:showAsAction="never" - android:orderInCategory="5" /> - <item - android:id="@+id/action_settings" - android:alphabeticShortcut="s" - android:title="@string/menu_preferences" - android:icon="@drawable/ic_menu_settings_holo_light" - android:showAsAction="never" - android:orderInCategory="10" /> </menu> diff --git a/res/values-en-rGB/arrays.xml b/res/values-en-rGB/arrays.xml index 943b2cb2..fb3842ac 100644 --- a/res/values-en-rGB/arrays.xml +++ b/res/values-en-rGB/arrays.xml @@ -105,7 +105,6 @@ <item msgid="5917946202321434335">"Day"</item> <item msgid="6248480754959562740">"Week"</item> <item msgid="4298472806133153766">"Month"</item> - <item msgid="5033084974413793845">"Agenda"</item> </string-array> <string-array name="preferences_skip_reminders_labels"> <item msgid="2285927842308686988">"Only if declined"</item> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index b14854a3..d14abe43 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -221,7 +221,6 @@ <item>Day</item> <item>Week</item> <item>Month</item> - <item>Agenda</item> </string-array> <!-- Experimental options for skipping reminders. [CHAR LIMIT = 37] --> diff --git a/res/xml/calendar_settings_headers.xml b/res/xml/calendar_settings_headers.xml index a5a5ae72..d497d2d0 100644 --- a/res/xml/calendar_settings_headers.xml +++ b/res/xml/calendar_settings_headers.xml @@ -18,7 +18,4 @@ xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.android.calendar.GeneralPreferences" android:title="@string/menu_general_preferences" /> - - <header android:fragment="com.android.calendar.AboutPreferences" - android:title="@string/menu_about_preferences" /> </preference-headers> diff --git a/res/xml/general_preferences.xml b/res/xml/general_preferences.xml index 5f64b362..c8b5ab7d 100644 --- a/res/xml/general_preferences.xml +++ b/res/xml/general_preferences.xml @@ -41,10 +41,6 @@ android:dependency="preferences_home_tz_enabled" android:defaultValue="@string/preferences_home_tz_default" android:title="@string/preferences_home_tz_title" /> - <PreferenceScreen - android:key="preferences_clear_search_history" - android:title="@string/preferences_clear_search_history_title" - android:summary="@string/preferences_clear_search_history_summary" /> </PreferenceCategory> <PreferenceCategory android:key="preferences_alerts_category" @@ -80,12 +76,6 @@ android:entries="@array/preferences_default_reminder_labels" android:entryValues="@array/preferences_default_reminder_values" android:dialogTitle="@string/preferences_default_reminder_dialog" /> - - <PreferenceScreen - android:key="preferences_quick_responses" - android:fragment="com.android.calendar.QuickResponseSettings" - android:title="@string/quick_response_settings" - android:summary="@string/quick_response_settings_summary" /> </PreferenceCategory> </PreferenceScreen> diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml index ad01b7e1..52a3442d 100644 --- a/res/xml/searchable.xml +++ b/res/xml/searchable.xml @@ -15,14 +15,10 @@ --> <searchable xmlns:android="http://schemas.android.com/apk/res/android" - android:value="com.android.calendar.SearchActivity" android:label="@string/app_label" android:searchMode="showSearchLabelAsBadge" android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" android:voiceLanguageModel="free_form" - - android:searchSuggestAuthority="com.android.calendar.CalendarRecentSuggestionsProvider" - android:searchSuggestSelection=" ? " android:imeOptions="actionSearch" > </searchable> diff --git a/src/com/android/calendar/AboutPreferences.java b/src/com/android/calendar/AboutPreferences.java deleted file mode 100644 index ab682f3f..00000000 --- a/src/com/android/calendar/AboutPreferences.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.app.Activity; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import com.android.calendar.R; - -public class AboutPreferences extends PreferenceFragment { - private static final String BUILD_VERSION = "build_version"; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.about_preferences); - - final Activity activity = getActivity(); - try { - final PackageInfo packageInfo = - activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0); - findPreference(BUILD_VERSION).setSummary(packageInfo.versionName); - } catch (NameNotFoundException e) { - findPreference(BUILD_VERSION).setSummary("?"); - } - } -}
\ No newline at end of file diff --git a/src/com/android/calendar/AbstractCalendarActivity.java b/src/com/android/calendar/AbstractCalendarActivity.java deleted file mode 100644 index a80787dc..00000000 --- a/src/com/android/calendar/AbstractCalendarActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.app.Activity; - -public abstract class AbstractCalendarActivity extends Activity { - protected AsyncQueryService mService; - - public synchronized AsyncQueryService getAsyncQueryService() { - if (mService == null) { - mService = new AsyncQueryService(this); - } - return mService; - } -} diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java index 0e1feb49..d7780379 100644 --- a/src/com/android/calendar/AllInOneActivity.java +++ b/src/com/android/calendar/AllInOneActivity.java @@ -26,6 +26,7 @@ import android.animation.Animator.AnimatorListener; import android.animation.ObjectAnimator; import android.app.ActionBar; import android.app.ActionBar.Tab; +import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -60,17 +61,13 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; -import android.widget.SearchView; -import android.widget.SearchView.OnSuggestionListener; import android.widget.TextView; import com.android.calendar.CalendarController.EventHandler; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.agenda.AgendaFragment; import com.android.calendar.month.MonthByWeekFragment; -import com.android.calendar.selectcalendars.SelectVisibleCalendarsFragment; import java.io.IOException; import java.util.List; @@ -82,9 +79,9 @@ import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY; import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; -public class AllInOneActivity extends AbstractCalendarActivity implements EventHandler, - OnSharedPreferenceChangeListener, SearchView.OnQueryTextListener, ActionBar.TabListener, - ActionBar.OnNavigationListener, OnSuggestionListener { +public class AllInOneActivity extends Activity implements EventHandler, + OnSharedPreferenceChangeListener, ActionBar.TabListener, + ActionBar.OnNavigationListener { private static final String TAG = "AllInOneActivity"; private static final boolean DEBUG = false; private static final String EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment"; @@ -105,8 +102,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH private CalendarController mController; private static boolean mIsMultipane; private static boolean mIsTabletConfig; - private static boolean mShowAgendaWithMonth; - private static boolean mShowEventDetailsWithAgenda; private boolean mOnSaveInstanceStateCalled = false; private boolean mBackToPreviousView = false; private ContentResolver mContentResolver; @@ -126,7 +121,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH private View mSecondaryPane; private String mTimeZone; private boolean mShowCalendarControls; - private boolean mShowEventInfoFullScreenAgenda; private boolean mShowEventInfoFullScreen; private int mWeekNum; private int mCalendarControlsAnimationTime; @@ -144,9 +138,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH private ActionBar.Tab mDayTab; private ActionBar.Tab mWeekTab; private ActionBar.Tab mMonthTab; - private ActionBar.Tab mAgendaTab; - private SearchView mSearchView; - private MenuItem mSearchMenu; private MenuItem mControlsMenu; private Menu mOptionsMenu; private CalendarViewAdapter mActionBarMenuSpinnerAdapter; @@ -164,9 +155,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH private LayoutParams mControlsParams; private LinearLayout.LayoutParams mVerticalControlsParams; - private AllInOneMenuExtensionsInterface mExtensions = ExtensionsFactory - .getAllInOneMenuExtensions(); - private final AnimatorListener mSlideAnimationDoneListener = new AnimatorListener() { @Override @@ -220,23 +208,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH new AccountManagerCallback<Bundle>() { @Override public void run(AccountManagerFuture<Bundle> future) { - if (future.isCancelled()) { - return; - } - try { - Bundle result = future.getResult(); - boolean setupSkipped = result.getBoolean("setupSkipped"); - - if (setupSkipped) { - Utils.setSharedPreference(AllInOneActivity.this, - GeneralPreferences.KEY_SKIP_SETUP, true); - } - - } catch (OperationCanceledException ignore) { - // The account creation process was canceled - } catch (IOException ignore) { - } catch (AuthenticatorException ignore) { - } } }, null); } @@ -277,8 +248,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH } }; - BroadcastReceiver mCalIntentReceiver; - @Override protected void onNewIntent(Intent intent) { String action = intent.getAction(); @@ -302,9 +271,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH @Override protected void onCreate(Bundle icicle) { - if (Utils.getSharedPreference(this, OtherPreferences.KEY_OTHER_1, false)) { - setTheme(R.style.CalendarTheme_WithActionBarWallpaper); - } super.onCreate(icicle); if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) { @@ -312,8 +278,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH } // Launch add google account if this is first time and there are no // accounts yet - if (mCheckForAccounts - && !Utils.getSharedPreference(this, GeneralPreferences.KEY_SKIP_SETUP, false)) { + if (mCheckForAccounts) { mHandler = new QueryHandler(this.getContentResolver()); mHandler.startQuery(0, null, Calendars.CONTENT_URI, new String[] { @@ -379,17 +344,11 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mControlsAnimateHeight = (int)res.getDimension(R.dimen.calendar_controls_height); - mHideControls = !Utils.getSharedPreference( - this, GeneralPreferences.KEY_SHOW_CONTROLS, true); + mHideControls = true; mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config); mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config); - mShowAgendaWithMonth = Utils.getConfigBool(this, R.bool.show_agenda_with_month); mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls); - mShowEventDetailsWithAgenda = - Utils.getConfigBool(this, R.bool.show_event_details_with_agenda); - mShowEventInfoFullScreenAgenda = - Utils.getConfigBool(this, R.bool.agenda_show_event_info_full_screen); mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen); mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time); @@ -476,7 +435,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mActionBar.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this); switch (viewType) { case ViewType.AGENDA: - mActionBar.setSelectedNavigationItem(BUTTON_AGENDA_INDEX); break; case ViewType.DAY: mActionBar.setSelectedNavigationItem(BUTTON_DAY_INDEX); @@ -554,8 +512,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone); // Make sure the today icon is up to date invalidateOptionsMenu(); - - mCalIntentReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater); } @Override @@ -579,7 +535,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH Utils.setDefaultView(this, mController.getViewType()); } Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater); - Utils.clearTimeChangesReceiver(this, mCalIntentReceiver); } @Override @@ -592,18 +547,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH public void onSaveInstanceState(Bundle outState) { mOnSaveInstanceStateCalled = true; super.onSaveInstanceState(outState); - outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime()); - outState.putInt(BUNDLE_KEY_RESTORE_VIEW, mCurrentView); - if (mCurrentView == ViewType.EDIT) { - outState.putLong(BUNDLE_KEY_EVENT_ID, mController.getEventId()); - } else if (mCurrentView == ViewType.AGENDA) { - FragmentManager fm = getFragmentManager(); - Fragment f = fm.findFragmentById(R.id.main_pane); - if (f instanceof AgendaFragment) { - outState.putLong(BUNDLE_KEY_EVENT_ID, ((AgendaFragment)f).getLastShowEventId()); - } - } - outState.putBoolean(BUNDLE_KEY_CHECK_ACCOUNTS, mCheckForAccounts); } @Override @@ -628,11 +571,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH Fragment miniMonthFrag = new MonthByWeekFragment(timeMillis, true); ft.replace(R.id.mini_month, miniMonthFrag); mController.registerEventHandler(R.id.mini_month, (EventHandler) miniMonthFrag); - - Fragment selectCalendarsFrag = new SelectVisibleCalendarsFragment(); - ft.replace(R.id.calendar_list, selectCalendarsFrag); - mController.registerEventHandler( - R.id.calendar_list, (EventHandler) selectCalendarsFrag); } if (!mShowCalendarControls || viewType == ViewType.EDIT) { mMiniMonth.setVisibility(View.GONE); @@ -685,10 +623,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH Time t = new Time(mTimeZone); t.set(timeMillis); - if (viewType == ViewType.AGENDA && icicle != null) { - mController.sendEvent(this, EventType.GO_TO, t, null, - icicle.getLong(BUNDLE_KEY_EVENT_ID, -1), viewType); - } else if (viewType != ViewType.EDIT) { + if (viewType != ViewType.EDIT) { mController.sendEvent(this, EventType.GO_TO, t, null, -1, viewType); } } @@ -708,22 +643,8 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mOptionsMenu = menu; getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu); - // Add additional options (if any). - Integer extensionMenuRes = mExtensions.getExtensionMenuResource(menu); - if (extensionMenuRes != null) { - getMenuInflater().inflate(extensionMenuRes, menu); - } - - mSearchMenu = menu.findItem(R.id.action_search); - mSearchView = (SearchView) mSearchMenu.getActionView(); - if (mSearchView != null) { - Utils.setUpSearchView(mSearchView, this); - mSearchView.setOnQueryTextListener(this); - mSearchView.setOnSuggestionListener(this); - } - // Hide the "show/hide controls" button if this is a phone - // or the view type is "Month" or "Agenda". + // or the view type is "Month". mControlsMenu = menu.findItem(R.id.action_hide_controls); if (!mShowCalendarControls) { @@ -732,8 +653,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mControlsMenu.setEnabled(false); } } else if (mControlsMenu != null && mController != null - && (mController.getViewType() == ViewType.MONTH || - mController.getViewType() == ViewType.AGENDA)) { + && (mController.getViewType() == ViewType.MONTH)) { mControlsMenu.setVisible(false); mControlsMenu.setEnabled(false); } else if (mControlsMenu != null){ @@ -758,37 +678,13 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH int viewType = ViewType.CURRENT; long extras = CalendarController.EXTRA_GOTO_TIME; final int itemId = item.getItemId(); - if (itemId == R.id.action_refresh) { - mController.refreshCalendars(); - return true; - } else if (itemId == R.id.action_today) { + if (itemId == R.id.action_today) { viewType = ViewType.CURRENT; t = new Time(mTimeZone); t.setToNow(); extras |= CalendarController.EXTRA_GOTO_TODAY; - } else if (itemId == R.id.action_create_event) { - t = new Time(); - t.set(mController.getTime()); - if (t.minute > 30) { - t.hour++; - t.minute = 0; - } else if (t.minute > 0 && t.minute < 30) { - t.minute = 30; - } - mController.sendEventRelatedEvent( - this, EventType.CREATE_EVENT, -1, t.toMillis(true), 0, 0, 0, -1); - return true; - } else if (itemId == R.id.action_select_visible_calendars) { - mController.sendEvent(this, EventType.LAUNCH_SELECT_VISIBLE_CALENDARS, null, null, - 0, 0); - return true; - } else if (itemId == R.id.action_settings) { - mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0); - return true; } else if (itemId == R.id.action_hide_controls) { mHideControls = !mHideControls; - Utils.setSharedPreference( - this, GeneralPreferences.KEY_SHOW_CONTROLS, !mHideControls); item.setTitle(mHideControls ? mShowString : mHideString); if (!mHideControls) { mMiniMonth.setVisibility(View.VISIBLE); @@ -802,10 +698,9 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH ObjectAnimator.setFrameDelay(0); slideAnimation.start(); return true; - } else if (itemId == R.id.action_search) { - return false; } else { - return mExtensions.handleItemSelected(item, this); + Log.d(TAG, "Unsupported itemId: " + itemId); + return true; } mController.sendEvent(this, EventType.GO_TO, t, null, t, -1, viewType, extras, null, null); return true; @@ -858,16 +753,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH // Remove this when transition to and from month view looks fine. boolean doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH; FragmentManager fragmentManager = getFragmentManager(); - // Check if our previous view was an Agenda view - // TODO remove this if framework ever supports nested fragments - if (mCurrentView == ViewType.AGENDA) { - // If it was, we need to do some cleanup on it to prevent the - // edit/delete buttons from coming back on a rotation. - Fragment oldFrag = fragmentManager.findFragmentById(viewId); - if (oldFrag instanceof AgendaFragment) { - ((AgendaFragment) oldFrag).removeFragments(fragmentManager); - } - } if (viewType != mCurrentView) { // The rules for this previous view are different than the @@ -882,14 +767,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH Fragment secFrag = null; switch (viewType) { case ViewType.AGENDA: - if (mActionBar != null && (mActionBar.getSelectedTab() != mAgendaTab)) { - mActionBar.selectTab(mAgendaTab); - } - if (mActionBarMenuSpinnerAdapter != null) { - mActionBar.setSelectedNavigationItem(CalendarViewAdapter.AGENDA_BUTTON_INDEX); - } - frag = new AgendaFragment(timeMillis, false); - ExtensionsFactory.getAnalyticsLogger(getBaseContext()).trackView("agenda"); break; case ViewType.DAY: if (mActionBar != null && (mActionBar.getSelectedTab() != mDayTab)) { @@ -899,7 +776,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mActionBar.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX); } frag = new DayFragment(timeMillis, 1); - ExtensionsFactory.getAnalyticsLogger(getBaseContext()).trackView("day"); break; case ViewType.MONTH: if (mActionBar != null && (mActionBar.getSelectedTab() != mMonthTab)) { @@ -909,10 +785,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mActionBar.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX); } frag = new MonthByWeekFragment(timeMillis, false); - if (mShowAgendaWithMonth) { - secFrag = new AgendaFragment(timeMillis, false); - } - ExtensionsFactory.getAnalyticsLogger(getBaseContext()).trackView("month"); break; case ViewType.WEEK: default: @@ -923,7 +795,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mActionBar.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX); } frag = new DayFragment(timeMillis, 7); - ExtensionsFactory.getAnalyticsLogger(getBaseContext()).trackView("week"); break; } @@ -940,8 +811,6 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH // Show date only on tablet configurations in views different than Agenda if (!mIsTabletConfig) { mDateRange.setVisibility(View.GONE); - } else if (viewType != ViewType.AGENDA) { - mDateRange.setVisibility(View.VISIBLE); } else { mDateRange.setVisibility(View.GONE); } @@ -962,30 +831,11 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH } ft.replace(viewId, frag); - if (mShowAgendaWithMonth) { - - // Show/hide secondary fragment - - if (secFrag != null) { - ft.replace(R.id.secondary_pane, secFrag); - mSecondaryPane.setVisibility(View.VISIBLE); - } else { - mSecondaryPane.setVisibility(View.GONE); - Fragment f = fragmentManager.findFragmentById(R.id.secondary_pane); - if (f != null) { - ft.remove(f); - } - mController.deregisterEventHandler(R.id.secondary_pane); - } - } if (DEBUG) { Log.d(TAG, "Adding handler with viewId " + viewId + " and type " + viewType); } // If the key is already registered this will replace it mController.registerEventHandler(viewId, (EventHandler) frag); - if (secFrag != null) { - mController.registerEventHandler(viewId, (EventHandler) secFrag); - } if (doCommit) { if (DEBUG) { @@ -1051,8 +901,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH } if (mHomeTime != null - && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK - || mCurrentView == ViewType.AGENDA) + && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) && !TextUtils.equals(mTimeZone, Time.getCurrentTimezone())) { Time time = new Time(mTimeZone); time.setToNow(); @@ -1098,13 +947,10 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH setMainPane( null, R.id.main_pane, event.viewType, event.startTime.toMillis(false), false); - if (mSearchView != null) { - mSearchView.clearFocus(); - } if (mShowCalendarControls) { int animationSize = (mOrientation == Configuration.ORIENTATION_LANDSCAPE) ? mControlsAnimateWidth : mControlsAnimateHeight; - boolean noControlsView = event.viewType == ViewType.MONTH || event.viewType == ViewType.AGENDA; + boolean noControlsView = event.viewType == ViewType.MONTH; if (mControlsMenu != null) { mControlsMenu.setVisible(!noControlsView); mControlsMenu.setEnabled(!noControlsView); @@ -1131,8 +977,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mCalendarsList.setVisibility(View.VISIBLE); mMiniMonthContainer.setVisibility(View.VISIBLE); if (!mHideControls && - (mController.getPreviousViewType() == ViewType.MONTH || - mController.getPreviousViewType() == ViewType.AGENDA)) { + (mController.getPreviousViewType() == ViewType.MONTH)) { final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this, "controlsOffset", animationSize, 0); slideAnimation.setDuration(mCalendarControlsAnimationTime); @@ -1152,32 +997,15 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH // do not create the event info fragment here, it will be created by the Agenda // fragment - if (mCurrentView == ViewType.AGENDA && mShowEventDetailsWithAgenda) { - if (event.startTime != null && event.endTime != null) { - // Event is all day , adjust the goto time to local time - if (event.isAllDay()) { - Utils.convertAlldayUtcToLocal( - event.startTime, event.startTime.toMillis(false), mTimeZone); - Utils.convertAlldayUtcToLocal( - event.endTime, event.endTime.toMillis(false), mTimeZone); - } - mController.sendEvent(this, EventType.GO_TO, event.startTime, event.endTime, - event.selectedTime, event.id, ViewType.AGENDA, - CalendarController.EXTRA_GOTO_TIME, null, null); - } else if (event.selectedTime != null) { - mController.sendEvent(this, EventType.GO_TO, event.selectedTime, - event.selectedTime, event.id, ViewType.AGENDA); - } - } else { + if (mCurrentView != ViewType.AGENDA) { // TODO Fix the temp hack below: && mCurrentView != // ViewType.AGENDA - if (event.selectedTime != null && mCurrentView != ViewType.AGENDA) { + if (event.selectedTime != null) { mController.sendEvent(this, EventType.GO_TO, event.selectedTime, event.selectedTime, -1, ViewType.CURRENT); } int response = event.getResponse(); - if ((mCurrentView == ViewType.AGENDA && mShowEventInfoFullScreenAgenda) || - ((mCurrentView == ViewType.DAY || (mCurrentView == ViewType.WEEK) || + if (((mCurrentView == ViewType.DAY || (mCurrentView == ViewType.WEEK) || mCurrentView == ViewType.MONTH) && mShowEventInfoFullScreen)){ // start event info as activity Intent intent = new Intent(Intent.ACTION_VIEW); @@ -1195,8 +1023,7 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH EventInfoFragment fragment = new EventInfoFragment(this, event.id, event.startTime.toMillis(false), event.endTime.toMillis(false), response, true, - EventInfoFragment.DIALOG_WINDOW_STYLE, - null /* No reminders to explicitly pass in. */); + EventInfoFragment.DIALOG_WINDOW_STYLE); fragment.setDialogParams(event.x, event.y, mActionBar.getHeight()); FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); @@ -1219,33 +1046,12 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH updateSecondaryTitleFields(displayTime); } - // Needs to be in proguard whitelist - // Specified as listener via android:onClick in a layout xml - public void handleSelectSyncedCalendarsClicked(View v) { - mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, null, 0, 0, - CalendarController.EXTRA_GOTO_TIME, null, - null); - } - @Override public void eventsChanged() { mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); } @Override - public boolean onQueryTextChange(String newText) { - return false; - } - - @Override - public boolean onQueryTextSubmit(String query) { - mSearchMenu.collapseActionView(); - mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query, - getComponentName()); - return true; - } - - @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing()); if (tab == mDayTab && mCurrentView != ViewType.DAY) { @@ -1254,13 +1060,11 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.WEEK); } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) { mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.MONTH); - } else if (tab == mAgendaTab && mCurrentView != ViewType.AGENDA) { - mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.AGENDA); } else { Log.w(TAG, "TabSelected event from unknown tab: " + (tab == null ? "null" : tab.getText())); Log.w(TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab - + " Week:" + mWeekTab + " Month:" + mMonthTab + " Agenda:" + mAgendaTab); + + " Week:" + mWeekTab + " Month:" + mMonthTab); } } @@ -1292,36 +1096,13 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH } break; case CalendarViewAdapter.AGENDA_BUTTON_INDEX: - if (mCurrentView != ViewType.AGENDA) { - mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.AGENDA); - } break; default: Log.w(TAG, "ItemSelected event from unknown button: " + itemPosition); Log.w(TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition + - " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab + - " Agenda:" + mAgendaTab); + " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab); break; } return false; } - - @Override - public boolean onSuggestionSelect(int position) { - return false; - } - - @Override - public boolean onSuggestionClick(int position) { - mSearchMenu.collapseActionView(); - return false; - } - - @Override - public boolean onSearchRequested() { - if (mSearchMenu != null) { - mSearchMenu.expandActionView(); - } - return false; - } } diff --git a/src/com/android/calendar/AllInOneMenuExtensionsInterface.java b/src/com/android/calendar/AllInOneMenuExtensionsInterface.java deleted file mode 100644 index 877db548..00000000 --- a/src/com/android/calendar/AllInOneMenuExtensionsInterface.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.view.Menu; -import android.view.MenuItem; - -/* - * Interface for additional options in the AllInOne menu. - */ -public interface AllInOneMenuExtensionsInterface { - /** - * Returns additional options. - */ - Integer getExtensionMenuResource(Menu menu); - - /** - * Handle selection of the additional options. - */ - boolean handleItemSelected(MenuItem item, Context context); -} diff --git a/src/com/android/calendar/AnalyticsLogger.java b/src/com/android/calendar/AnalyticsLogger.java deleted file mode 100644 index c98984f2..00000000 --- a/src/com/android/calendar/AnalyticsLogger.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.android.calendar; - -import android.content.Context; - -/** - * Interface for analytics logging. - */ -public interface AnalyticsLogger { - - /** - * Open backend of logger. - * - * @param context need to open backend of logger. - * @return true, if analytics logging is ready to be use. - */ - public boolean initialize(Context context); - - /** - * Track what view people are using. - * - * @param name of the view. - */ - public void trackView(String name); -} diff --git a/src/com/android/calendar/AsyncQueryService.java b/src/com/android/calendar/AsyncQueryService.java deleted file mode 100644 index 1f022db9..00000000 --- a/src/com/android/calendar/AsyncQueryService.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.AsyncQueryServiceHelper.OperationInfo; - -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A helper class that executes {@link ContentResolver} calls in a background - * {@link android.app.Service}. This minimizes the chance of the call getting - * lost because the caller ({@link android.app.Activity}) is killed. It is - * designed for easy migration from {@link android.content.AsyncQueryHandler} - * which calls the {@link ContentResolver} in a background thread. This supports - * query/insert/update/delete and also batch mode i.e. - * {@link ContentProviderOperation}. It also supports delay execution and cancel - * which allows for time-limited undo. Note that there's one queue per - * application which serializes all the calls. - */ -public class AsyncQueryService extends Handler { - private static final String TAG = "AsyncQuery"; - static final boolean localLOGV = false; - - // Used for generating unique tokens for calls to this service - private static AtomicInteger mUniqueToken = new AtomicInteger(0); - - private Context mContext; - private Handler mHandler = this; // can be overridden for testing - - /** - * Data class which holds into info of the queued operation - */ - public static class Operation { - static final int EVENT_ARG_QUERY = 1; - static final int EVENT_ARG_INSERT = 2; - static final int EVENT_ARG_UPDATE = 3; - static final int EVENT_ARG_DELETE = 4; - static final int EVENT_ARG_BATCH = 5; - - /** - * unique identify for cancellation purpose - */ - public int token; - - /** - * One of the EVENT_ARG_ constants in the class describing the operation - */ - public int op; - - /** - * {@link SystemClock.elapsedRealtime()} based - */ - public long scheduledExecutionTime; - - protected static char opToChar(int op) { - switch (op) { - case Operation.EVENT_ARG_QUERY: - return 'Q'; - case Operation.EVENT_ARG_INSERT: - return 'I'; - case Operation.EVENT_ARG_UPDATE: - return 'U'; - case Operation.EVENT_ARG_DELETE: - return 'D'; - case Operation.EVENT_ARG_BATCH: - return 'B'; - default: - return '?'; - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Operation [op="); - builder.append(op); - builder.append(", token="); - builder.append(token); - builder.append(", scheduledExecutionTime="); - builder.append(scheduledExecutionTime); - builder.append("]"); - return builder.toString(); - } - } - - public AsyncQueryService(Context context) { - mContext = context; - } - - /** - * returns a practically unique token for db operations - */ - public final int getNextToken() { - return mUniqueToken.getAndIncrement(); - } - - /** - * Gets the last delayed operation. It is typically used for canceling. - * - * @return Operation object which contains of the last cancelable operation - */ - public final Operation getLastCancelableOperation() { - return AsyncQueryServiceHelper.getLastCancelableOperation(); - } - - /** - * Attempts to cancel operation that has not already started. Note that - * there is no guarantee that the operation will be canceled. They still may - * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after - * this call has completed. - * - * @param token The token representing the operation to be canceled. If - * multiple operations have the same token they will all be - * canceled. - */ - public final int cancelOperation(int token) { - return AsyncQueryServiceHelper.cancelOperation(token); - } - - /** - * This method begins an asynchronous query. When the query is done - * {@link #onQueryComplete} is called. - * - * @param token A token passed into {@link #onQueryComplete} to identify the - * query. - * @param cookie An object that gets passed into {@link #onQueryComplete} - * @param uri The URI, using the content:// scheme, for the content to - * retrieve. - * @param projection A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given URI. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in the order that - * they appear in the selection. The values will be bound as - * Strings. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - */ - public void startQuery(int token, Object cookie, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy) { - OperationInfo info = new OperationInfo(); - info.op = Operation.EVENT_ARG_QUERY; - info.resolver = mContext.getContentResolver(); - - info.handler = mHandler; - info.token = token; - info.cookie = cookie; - info.uri = uri; - info.projection = projection; - info.selection = selection; - info.selectionArgs = selectionArgs; - info.orderBy = orderBy; - - AsyncQueryServiceHelper.queueOperation(mContext, info); - } - - /** - * This method begins an asynchronous insert. When the insert operation is - * done {@link #onInsertComplete} is called. - * - * @param token A token passed into {@link #onInsertComplete} to identify - * the insert operation. - * @param cookie An object that gets passed into {@link #onInsertComplete} - * @param uri the Uri passed to the insert operation. - * @param initialValues the ContentValues parameter passed to the insert - * operation. - * @param delayMillis delay in executing the operation. This operation will - * execute before the delayed time when another operation is - * added. Useful for implementing single level undo. - */ - public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues, - long delayMillis) { - OperationInfo info = new OperationInfo(); - info.op = Operation.EVENT_ARG_INSERT; - info.resolver = mContext.getContentResolver(); - info.handler = mHandler; - - info.token = token; - info.cookie = cookie; - info.uri = uri; - info.values = initialValues; - info.delayMillis = delayMillis; - - AsyncQueryServiceHelper.queueOperation(mContext, info); - } - - /** - * This method begins an asynchronous update. When the update operation is - * done {@link #onUpdateComplete} is called. - * - * @param token A token passed into {@link #onUpdateComplete} to identify - * the update operation. - * @param cookie An object that gets passed into {@link #onUpdateComplete} - * @param uri the Uri passed to the update operation. - * @param values the ContentValues parameter passed to the update operation. - * @param selection A filter declaring which rows to update, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will update all rows for the given URI. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in the order that - * they appear in the selection. The values will be bound as - * Strings. - * @param delayMillis delay in executing the operation. This operation will - * execute before the delayed time when another operation is - * added. Useful for implementing single level undo. - */ - public void startUpdate(int token, Object cookie, Uri uri, ContentValues values, - String selection, String[] selectionArgs, long delayMillis) { - OperationInfo info = new OperationInfo(); - info.op = Operation.EVENT_ARG_UPDATE; - info.resolver = mContext.getContentResolver(); - info.handler = mHandler; - - info.token = token; - info.cookie = cookie; - info.uri = uri; - info.values = values; - info.selection = selection; - info.selectionArgs = selectionArgs; - info.delayMillis = delayMillis; - - AsyncQueryServiceHelper.queueOperation(mContext, info); - } - - /** - * This method begins an asynchronous delete. When the delete operation is - * done {@link #onDeleteComplete} is called. - * - * @param token A token passed into {@link #onDeleteComplete} to identify - * the delete operation. - * @param cookie An object that gets passed into {@link #onDeleteComplete} - * @param uri the Uri passed to the delete operation. - * @param selection A filter declaring which rows to delete, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will delete all rows for the given URI. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in the order that - * they appear in the selection. The values will be bound as - * Strings. - * @param delayMillis delay in executing the operation. This operation will - * execute before the delayed time when another operation is - * added. Useful for implementing single level undo. - */ - public void startDelete(int token, Object cookie, Uri uri, String selection, - String[] selectionArgs, long delayMillis) { - OperationInfo info = new OperationInfo(); - info.op = Operation.EVENT_ARG_DELETE; - info.resolver = mContext.getContentResolver(); - info.handler = mHandler; - - info.token = token; - info.cookie = cookie; - info.uri = uri; - info.selection = selection; - info.selectionArgs = selectionArgs; - info.delayMillis = delayMillis; - - AsyncQueryServiceHelper.queueOperation(mContext, info); - } - - /** - * This method begins an asynchronous {@link ContentProviderOperation}. When - * the operation is done {@link #onBatchComplete} is called. - * - * @param token A token passed into {@link #onDeleteComplete} to identify - * the delete operation. - * @param cookie An object that gets passed into {@link #onDeleteComplete} - * @param authority the authority used for the - * {@link ContentProviderOperation}. - * @param cpo the {@link ContentProviderOperation} to be executed. - * @param delayMillis delay in executing the operation. This operation will - * execute before the delayed time when another operation is - * added. Useful for implementing single level undo. - */ - public void startBatch(int token, Object cookie, String authority, - ArrayList<ContentProviderOperation> cpo, long delayMillis) { - OperationInfo info = new OperationInfo(); - info.op = Operation.EVENT_ARG_BATCH; - info.resolver = mContext.getContentResolver(); - info.handler = mHandler; - - info.token = token; - info.cookie = cookie; - info.authority = authority; - info.cpo = cpo; - info.delayMillis = delayMillis; - - AsyncQueryServiceHelper.queueOperation(mContext, info); - } - - /** - * Called when an asynchronous query is completed. - * - * @param token the token to identify the query, passed in from - * {@link #startQuery}. - * @param cookie the cookie object passed in from {@link #startQuery}. - * @param cursor The cursor holding the results from the query. - */ - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (localLOGV) { - Log.d(TAG, "########## default onQueryComplete"); - } - } - - /** - * Called when an asynchronous insert is completed. - * - * @param token the token to identify the query, passed in from - * {@link #startInsert}. - * @param cookie the cookie object that's passed in from - * {@link #startInsert}. - * @param uri the uri returned from the insert operation. - */ - protected void onInsertComplete(int token, Object cookie, Uri uri) { - if (localLOGV) { - Log.d(TAG, "########## default onInsertComplete"); - } - } - - /** - * Called when an asynchronous update is completed. - * - * @param token the token to identify the query, passed in from - * {@link #startUpdate}. - * @param cookie the cookie object that's passed in from - * {@link #startUpdate}. - * @param result the result returned from the update operation - */ - protected void onUpdateComplete(int token, Object cookie, int result) { - if (localLOGV) { - Log.d(TAG, "########## default onUpdateComplete"); - } - } - - /** - * Called when an asynchronous delete is completed. - * - * @param token the token to identify the query, passed in from - * {@link #startDelete}. - * @param cookie the cookie object that's passed in from - * {@link #startDelete}. - * @param result the result returned from the delete operation - */ - protected void onDeleteComplete(int token, Object cookie, int result) { - if (localLOGV) { - Log.d(TAG, "########## default onDeleteComplete"); - } - } - - /** - * Called when an asynchronous {@link ContentProviderOperation} is - * completed. - * - * @param token the token to identify the query, passed in from - * {@link #startDelete}. - * @param cookie the cookie object that's passed in from - * {@link #startDelete}. - * @param results the result returned from executing the - * {@link ContentProviderOperation} - */ - protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) { - if (localLOGV) { - Log.d(TAG, "########## default onBatchComplete"); - } - } - - @Override - public void handleMessage(Message msg) { - OperationInfo info = (OperationInfo) msg.obj; - - int token = msg.what; - int op = msg.arg1; - - if (localLOGV) { - Log.d(TAG, "AsyncQueryService.handleMessage: token=" + token + ", op=" + op - + ", result=" + info.result); - } - - // pass token back to caller on each callback. - switch (op) { - case Operation.EVENT_ARG_QUERY: - onQueryComplete(token, info.cookie, (Cursor) info.result); - break; - - case Operation.EVENT_ARG_INSERT: - onInsertComplete(token, info.cookie, (Uri) info.result); - break; - - case Operation.EVENT_ARG_UPDATE: - onUpdateComplete(token, info.cookie, (Integer) info.result); - break; - - case Operation.EVENT_ARG_DELETE: - onDeleteComplete(token, info.cookie, (Integer) info.result); - break; - - case Operation.EVENT_ARG_BATCH: - onBatchComplete(token, info.cookie, (ContentProviderResult[]) info.result); - break; - } - } - -// @VisibleForTesting - protected void setTestHandler(Handler handler) { - mHandler = handler; - } -} diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.java b/src/com/android/calendar/AsyncQueryServiceHelper.java index 615a779f..c6e0a2bc 100644 --- a/src/com/android/calendar/AsyncQueryServiceHelper.java +++ b/src/com/android/calendar/AsyncQueryServiceHelper.java @@ -16,8 +16,6 @@ package com.android.calendar; -import com.android.calendar.AsyncQueryService.Operation; - import android.app.IntentService; import android.content.ContentProviderOperation; import android.content.ContentResolver; @@ -43,194 +41,6 @@ import java.util.concurrent.TimeUnit; public class AsyncQueryServiceHelper extends IntentService { private static final String TAG = "AsyncQuery"; - private static final PriorityQueue<OperationInfo> sWorkQueue = - new PriorityQueue<OperationInfo>(); - - protected Class<AsyncQueryService> mService = AsyncQueryService.class; - - protected static class OperationInfo implements Delayed{ - public int token; // Used for cancel - public int op; - public ContentResolver resolver; - public Uri uri; - public String authority; - public Handler handler; - public String[] projection; - public String selection; - public String[] selectionArgs; - public String orderBy; - public Object result; - public Object cookie; - public ContentValues values; - public ArrayList<ContentProviderOperation> cpo; - - /** - * delayMillis is relative time e.g. 10,000 milliseconds - */ - public long delayMillis; - - /** - * scheduleTimeMillis is the time scheduled for this to be processed. - * e.g. SystemClock.elapsedRealtime() + 10,000 milliseconds Based on - * {@link android.os.SystemClock#elapsedRealtime } - */ - private long mScheduledTimeMillis = 0; - - // @VisibleForTesting - void calculateScheduledTime() { - mScheduledTimeMillis = SystemClock.elapsedRealtime() + delayMillis; - } - - // @Override // Uncomment with Java6 - public long getDelay(TimeUnit unit) { - return unit.convert(mScheduledTimeMillis - SystemClock.elapsedRealtime(), - TimeUnit.MILLISECONDS); - } - - // @Override // Uncomment with Java6 - public int compareTo(Delayed another) { - OperationInfo anotherArgs = (OperationInfo) another; - if (this.mScheduledTimeMillis == anotherArgs.mScheduledTimeMillis) { - return 0; - } else if (this.mScheduledTimeMillis < anotherArgs.mScheduledTimeMillis) { - return -1; - } else { - return 1; - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("OperationInfo [\n\t token= "); - builder.append(token); - builder.append(",\n\t op= "); - builder.append(Operation.opToChar(op)); - builder.append(",\n\t uri= "); - builder.append(uri); - builder.append(",\n\t authority= "); - builder.append(authority); - builder.append(",\n\t delayMillis= "); - builder.append(delayMillis); - builder.append(",\n\t mScheduledTimeMillis= "); - builder.append(mScheduledTimeMillis); - builder.append(",\n\t resolver= "); - builder.append(resolver); - builder.append(",\n\t handler= "); - builder.append(handler); - builder.append(",\n\t projection= "); - builder.append(Arrays.toString(projection)); - builder.append(",\n\t selection= "); - builder.append(selection); - builder.append(",\n\t selectionArgs= "); - builder.append(Arrays.toString(selectionArgs)); - builder.append(",\n\t orderBy= "); - builder.append(orderBy); - builder.append(",\n\t result= "); - builder.append(result); - builder.append(",\n\t cookie= "); - builder.append(cookie); - builder.append(",\n\t values= "); - builder.append(values); - builder.append(",\n\t cpo= "); - builder.append(cpo); - builder.append("\n]"); - return builder.toString(); - } - - /** - * Compares an user-visible operation to this private OperationInfo - * object - * - * @param o operation to be compared - * @return true if logically equivalent - */ - public boolean equivalent(Operation o) { - return o.token == this.token && o.op == this.op; - } - } - - /** - * Queues the operation for execution - * - * @param context - * @param args OperationInfo object describing the operation - */ - static public void queueOperation(Context context, OperationInfo args) { - // Set the schedule time for execution based on the desired delay. - args.calculateScheduledTime(); - - synchronized (sWorkQueue) { - sWorkQueue.add(args); - sWorkQueue.notify(); - } - - context.startService(new Intent(context, AsyncQueryServiceHelper.class)); - } - - /** - * Gets the last delayed operation. It is typically used for canceling. - * - * @return Operation object which contains of the last cancelable operation - */ - static public Operation getLastCancelableOperation() { - long lastScheduleTime = Long.MIN_VALUE; - Operation op = null; - - synchronized (sWorkQueue) { - // Unknown order even for a PriorityQueue - Iterator<OperationInfo> it = sWorkQueue.iterator(); - while (it.hasNext()) { - OperationInfo info = it.next(); - if (info.delayMillis > 0 && lastScheduleTime < info.mScheduledTimeMillis) { - if (op == null) { - op = new Operation(); - } - - op.token = info.token; - op.op = info.op; - op.scheduledExecutionTime = info.mScheduledTimeMillis; - - lastScheduleTime = info.mScheduledTimeMillis; - } - } - } - - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "getLastCancelableOperation -> Operation:" + Operation.opToChar(op.op) - + " token:" + op.token); - } - return op; - } - - /** - * Attempts to cancel operation that has not already started. Note that - * there is no guarantee that the operation will be canceled. They still may - * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after - * this call has completed. - * - * @param token The token representing the operation to be canceled. If - * multiple operations have the same token they will all be - * canceled. - */ - static public int cancelOperation(int token) { - int canceled = 0; - synchronized (sWorkQueue) { - Iterator<OperationInfo> it = sWorkQueue.iterator(); - while (it.hasNext()) { - if (it.next().token == token) { - it.remove(); - ++canceled; - } - } - } - - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "cancelOperation(" + token + ") -> " + canceled); - } - return canceled; - } - public AsyncQueryServiceHelper(String name) { super(name); } @@ -241,138 +51,20 @@ public class AsyncQueryServiceHelper extends IntentService { @Override protected void onHandleIntent(Intent intent) { - OperationInfo args; - - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onHandleIntent: queue size=" + sWorkQueue.size()); - } - synchronized (sWorkQueue) { - while (true) { - /* - * This method can be called with no work because of - * cancellations - */ - if (sWorkQueue.size() == 0) { - return; - } else if (sWorkQueue.size() == 1) { - OperationInfo first = sWorkQueue.peek(); - long waitTime = first.mScheduledTimeMillis - SystemClock.elapsedRealtime(); - if (waitTime > 0) { - try { - sWorkQueue.wait(waitTime); - } catch (InterruptedException e) { - } - } - } - - args = sWorkQueue.poll(); - if (args != null) { - // Got work to do. Break out of waiting loop - break; - } - } - } - - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onHandleIntent: " + args); - } - - ContentResolver resolver = args.resolver; - if (resolver != null) { - - switch (args.op) { - case Operation.EVENT_ARG_QUERY: - Cursor cursor; - try { - cursor = resolver.query(args.uri, args.projection, args.selection, - args.selectionArgs, args.orderBy); - /* - * Calling getCount() causes the cursor window to be - * filled, which will make the first access on the main - * thread a lot faster - */ - if (cursor != null) { - cursor.getCount(); - } - } catch (Exception e) { - Log.w(TAG, e.toString()); - cursor = null; - } - - args.result = cursor; - break; - - case Operation.EVENT_ARG_INSERT: - args.result = resolver.insert(args.uri, args.values); - break; - - case Operation.EVENT_ARG_UPDATE: - args.result = resolver.update(args.uri, args.values, args.selection, - args.selectionArgs); - break; - - case Operation.EVENT_ARG_DELETE: - try { - args.result = resolver.delete(args.uri, args.selection, args.selectionArgs); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Delete failed."); - Log.w(TAG, e.toString()); - args.result = 0; - } - - break; - - case Operation.EVENT_ARG_BATCH: - try { - args.result = resolver.applyBatch(args.authority, args.cpo); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - args.result = null; - } catch (OperationApplicationException e) { - Log.e(TAG, e.toString()); - args.result = null; - } - break; - } - - /* - * passing the original token value back to the caller on top of the - * event values in arg1. - */ - Message reply = args.handler.obtainMessage(args.token); - reply.obj = args; - reply.arg1 = args.op; - - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onHandleIntent: op=" + Operation.opToChar(args.op) + ", token=" - + reply.what); - } - - reply.sendToTarget(); - } } @Override public void onStart(Intent intent, int startId) { - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onStart startId=" + startId); - } super.onStart(intent, startId); } @Override public void onCreate() { - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onCreate"); - } super.onCreate(); } @Override public void onDestroy() { - if (AsyncQueryService.localLOGV) { - Log.d(TAG, "onDestroy"); - } super.onDestroy(); } } diff --git a/src/com/android/calendar/CalendarApplication.java b/src/com/android/calendar/CalendarApplication.java index 7f1854d0..d0ca4698 100644 --- a/src/com/android/calendar/CalendarApplication.java +++ b/src/com/android/calendar/CalendarApplication.java @@ -28,13 +28,5 @@ public class CalendarApplication extends Application { * service, etc. of Calendar */ GeneralPreferences.setDefaultValues(this); - - // Save the version number, for upcoming 'What's new' screen. This will be later be - // moved to that implementation. - Utils.setSharedPreference(this, GeneralPreferences.KEY_VERSION, - Utils.getVersionCode(this)); - - // Initialize the registry mapping some custom behavior. - ExtensionsFactory.init(getAssets()); } } diff --git a/src/com/android/calendar/CalendarBackupAgent.java b/src/com/android/calendar/CalendarBackupAgent.java index 0e9f9d72..02456fdc 100644 --- a/src/com/android/calendar/CalendarBackupAgent.java +++ b/src/com/android/calendar/CalendarBackupAgent.java @@ -31,19 +31,11 @@ public class CalendarBackupAgent extends BackupAgentHelper @Override public void onCreate() { - addHelper(SHARED_KEY, new SharedPreferencesBackupHelper(this, - GeneralPreferences.SHARED_PREFS_NAME)); } @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { - // See Utils.getRingTonePreference for more info - final Editor editor = getSharedPreferences( - GeneralPreferences.SHARED_PREFS_NAME_NO_BACKUP, Context.MODE_PRIVATE).edit(); - editor.putString(GeneralPreferences.KEY_ALERTS_RINGTONE, - GeneralPreferences.DEFAULT_RINGTONE).commit(); - super.onRestore(data, appVersionCode, newState); } } diff --git a/src/com/android/calendar/CalendarColorPickerDialog.java b/src/com/android/calendar/CalendarColorPickerDialog.java deleted file mode 100644 index e72d1962..00000000 --- a/src/com/android/calendar/CalendarColorPickerDialog.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.app.Activity; -import android.app.Dialog; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Colors; -import android.util.SparseIntArray; - -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; -import com.android.colorpicker.HsvColorComparator; - -import java.util.ArrayList; -import java.util.Arrays; - -public class CalendarColorPickerDialog extends ColorPickerDialog { - - private static final int NUM_COLUMNS = 4; - - private static final String KEY_CALENDAR_ID = "calendar_id"; - private static final String KEY_COLOR_KEYS = "color_keys"; - - private static final int TOKEN_QUERY_CALENDARS = 1 << 1; - private static final int TOKEN_QUERY_COLORS = 1 << 2; - - static final String[] CALENDARS_PROJECTION = new String[] { - Calendars.ACCOUNT_NAME, - Calendars.ACCOUNT_TYPE, - Calendars.CALENDAR_COLOR - }; - - static final int CALENDARS_INDEX_ACCOUNT_NAME = 0; - static final int CALENDARS_INDEX_ACCOUNT_TYPE = 1; - static final int CALENDARS_INDEX_CALENDAR_COLOR = 2; - - static final String[] COLORS_PROJECTION = new String[] { - Colors.COLOR, - Colors.COLOR_KEY - }; - - static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE + - "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_CALENDAR; - - public static final int COLORS_INDEX_COLOR = 0; - public static final int COLORS_INDEX_COLOR_KEY = 1; - - - private QueryService mService; - private SparseIntArray mColorKeyMap = new SparseIntArray(); - private long mCalendarId; - - private class QueryService extends AsyncQueryService { - - private QueryService(Context context) { - super(context); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - // If the query didn't return a cursor for some reason return - if (cursor == null) { - return; - } - - // If the Activity is finishing, then close the cursor. - // Otherwise, use the new cursor in the adapter. - final Activity activity = getActivity(); - if (activity == null || activity.isFinishing()) { - cursor.close(); - return; - } - - switch (token) { - case TOKEN_QUERY_CALENDARS: - if (!cursor.moveToFirst()) { - cursor.close(); - dismiss(); - break; - } - mSelectedColor = Utils.getDisplayColorFromColor( - cursor.getInt(CALENDARS_INDEX_CALENDAR_COLOR)); - Uri uri = Colors.CONTENT_URI; - String[] args = new String[] { - cursor.getString(CALENDARS_INDEX_ACCOUNT_NAME), - cursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) }; - cursor.close(); - startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, - args, null); - break; - case TOKEN_QUERY_COLORS: - if (!cursor.moveToFirst()) { - cursor.close(); - dismiss(); - break; - } - mColorKeyMap.clear(); - ArrayList<Integer> colors = new ArrayList<Integer>(); - do - { - int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY); - int rawColor = cursor.getInt(COLORS_INDEX_COLOR); - int displayColor = Utils.getDisplayColorFromColor(rawColor); - mColorKeyMap.put(displayColor, colorKey); - colors.add(displayColor); - } while (cursor.moveToNext()); - Integer[] colorsToSort = colors.toArray(new Integer[colors.size()]); - Arrays.sort(colorsToSort, new HsvColorComparator()); - mColors = new int[colorsToSort.length]; - for (int i = 0; i < mColors.length; i++) { - mColors[i] = colorsToSort[i]; - } - showPaletteView(); - cursor.close(); - break; - } - } - } - - private class OnCalendarColorSelectedListener implements OnColorSelectedListener { - - @Override - public void onColorSelected(int color) { - if (color == mSelectedColor || mService == null) { - return; - } - - ContentValues values = new ContentValues(); - values.put(Calendars.CALENDAR_COLOR_KEY, mColorKeyMap.get(color)); - mService.startUpdate(mService.getNextToken(), null, ContentUris.withAppendedId( - Calendars.CONTENT_URI, mCalendarId), values, null, null, Utils.UNDO_DELAY); - } - } - - public CalendarColorPickerDialog() { - // Empty constructor required for dialog fragments. - } - - public static CalendarColorPickerDialog newInstance(long calendarId, boolean isTablet) { - CalendarColorPickerDialog ret = new CalendarColorPickerDialog(); - ret.setArguments(R.string.calendar_color_picker_dialog_title, NUM_COLUMNS, - isTablet ? SIZE_LARGE : SIZE_SMALL); - ret.setCalendarId(calendarId); - return ret; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putLong(KEY_CALENDAR_ID, mCalendarId); - saveColorKeys(outState); - } - - private void saveColorKeys(Bundle outState) { - // No color keys to save, so just return - if (mColors == null) { - return; - } - int[] colorKeys = new int[mColors.length]; - for (int i = 0; i < mColors.length; i++) { - colorKeys[i] = mColorKeyMap.get(mColors[i]); - } - outState.putIntArray(KEY_COLOR_KEYS, colorKeys); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mCalendarId = savedInstanceState.getLong(KEY_CALENDAR_ID); - retrieveColorKeys(savedInstanceState); - } - setOnColorSelectedListener(new OnCalendarColorSelectedListener()); - } - - private void retrieveColorKeys(Bundle savedInstanceState) { - int[] colorKeys = savedInstanceState.getIntArray(KEY_COLOR_KEYS); - if (mColors != null && colorKeys != null) { - for (int i = 0; i < mColors.length; i++) { - mColorKeyMap.put(mColors[i], colorKeys[i]); - } - } - } - - @Override - public void setColors(int[] colors) { - throw new IllegalStateException("Must call setCalendarId() to update calendar colors"); - } - - @Override - public void setColors(int[] colors, int selectedColor) { - throw new IllegalStateException("Must call setCalendarId() to update calendar colors"); - } - - public void setCalendarId(long calendarId) { - if (calendarId != mCalendarId) { - mCalendarId = calendarId; - startQuery(); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - mService = new QueryService(getActivity()); - if (mColors == null) { - startQuery(); - } - return dialog; - } - - private void startQuery() { - if (mService != null) { - showProgressBarView(); - mService.startQuery(TOKEN_QUERY_CALENDARS, null, - ContentUris.withAppendedId(Calendars.CONTENT_URI, mCalendarId), - CALENDARS_PROJECTION, null, null, null); - } - } -} diff --git a/src/com/android/calendar/CalendarController.java b/src/com/android/calendar/CalendarController.java index 93b1b489..37286f2e 100644 --- a/src/com/android/calendar/CalendarController.java +++ b/src/com/android/calendar/CalendarController.java @@ -24,8 +24,6 @@ import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; -import android.app.SearchManager; -import android.app.SearchableInfo; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentUris; @@ -40,9 +38,6 @@ import android.text.format.Time; import android.util.Log; import android.util.Pair; -import com.android.calendar.event.EditEventActivity; -import com.android.calendar.selectcalendars.SelectVisibleCalendarsActivity; - import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.LinkedHashMap; @@ -98,8 +93,6 @@ public class CalendarController { * One of the event types that are sent to or from the controller */ public interface EventType { - final long CREATE_EVENT = 1L; - // Simple view of an event final long VIEW_EVENT = 1L << 1; @@ -109,24 +102,14 @@ public class CalendarController { // full detail view in edit mode final long EDIT_EVENT = 1L << 3; - final long DELETE_EVENT = 1L << 4; - final long GO_TO = 1L << 5; - final long LAUNCH_SETTINGS = 1L << 6; - final long EVENTS_CHANGED = 1L << 7; - final long SEARCH = 1L << 8; - - // User has pressed the home key final long USER_HOME = 1L << 9; // date range has changed, update the title final long UPDATE_TITLE = 1L << 10; - - // select which calendars to display - final long LAUNCH_SELECT_VISIBLE_CALENDARS = 1L << 11; } /** @@ -178,9 +161,6 @@ public class CalendarController { * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response. * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay(). * <p> - * For EventType.CREATE_EVENT: - * Set to {@link #EXTRA_CREATE_ALL_DAY} for creating an all-day event. - * <p> * For EventType.GO_TO: * Set to {@link #EXTRA_GOTO_TIME} to go to the specified date/time. * Set to {@link #EXTRA_GOTO_DATE} to consider the date but ignore the time. @@ -249,12 +229,6 @@ public class CalendarController { } /** - * Pass to the ExtraLong parameter for EventType.CREATE_EVENT to create - * an all-day event - */ - public static final long EXTRA_CREATE_ALL_DAY = 0x10; - - /** * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time * can be ignored */ @@ -367,7 +341,7 @@ public class CalendarController { long selectedMillis, String title, long calendarId) { EventInfo info = new EventInfo(); info.eventType = eventType; - if (eventType == EventType.EDIT_EVENT || eventType == EventType.VIEW_EVENT_DETAILS) { + if (eventType == EventType.VIEW_EVENT_DETAILS) { info.viewType = ViewType.CURRENT; } @@ -509,7 +483,7 @@ public class CalendarController { // Store the eventId if we're entering edit event if ((event.eventType - & (EventType.CREATE_EVENT | EventType.EDIT_EVENT | EventType.VIEW_EVENT_DETAILS)) + & (EventType.VIEW_EVENT_DETAILS)) != 0) { if (event.id > 0) { mEventId = event.id; @@ -580,45 +554,6 @@ public class CalendarController { } } } - - if (!handled) { - // Launch Settings - if (event.eventType == EventType.LAUNCH_SETTINGS) { - launchSettings(); - return; - } - - // Launch Calendar Visible Selector - if (event.eventType == EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) { - launchSelectVisibleCalendars(); - return; - } - - // Create/View/Edit/Delete Event - long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); - if (event.eventType == EventType.CREATE_EVENT) { - launchCreateEvent(event.startTime.toMillis(false), endTime, - event.extraLong == EXTRA_CREATE_ALL_DAY, event.eventTitle, - event.calendarId); - return; - } else if (event.eventType == EventType.VIEW_EVENT) { - launchViewEvent(event.id, event.startTime.toMillis(false), endTime, - event.getResponse()); - return; - } else if (event.eventType == EventType.EDIT_EVENT) { - launchEditEvent(event.id, event.startTime.toMillis(false), endTime, true); - return; - } else if (event.eventType == EventType.VIEW_EVENT_DETAILS) { - launchEditEvent(event.id, event.startTime.toMillis(false), endTime, false); - return; - } else if (event.eventType == EventType.DELETE_EVENT) { - launchDeleteEvent(event.id, event.startTime.toMillis(false), endTime); - return; - } else if (event.eventType == EventType.SEARCH) { - launchSearch(event.id, event.query, event.componentName); - return; - } - } } /** @@ -719,40 +654,6 @@ public class CalendarController { return mPreviousViewType; } - private void launchSelectVisibleCalendars() { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setClass(mContext, SelectVisibleCalendarsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); - mContext.startActivity(intent); - } - - private void launchSettings() { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setClass(mContext, CalendarSettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); - mContext.startActivity(intent); - } - - private void launchCreateEvent(long startMillis, long endMillis, boolean allDayEvent, - String title, long calendarId) { - Intent intent = generateCreateEventIntent(startMillis, endMillis, allDayEvent, title, - calendarId); - mEventId = -1; - mContext.startActivity(intent); - } - - public Intent generateCreateEventIntent(long startMillis, long endMillis, - boolean allDayEvent, String title, long calendarId) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setClass(mContext, EditEventActivity.class); - intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); - intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); - intent.putExtra(EXTRA_EVENT_ALL_DAY, allDayEvent); - intent.putExtra(Events.CALENDAR_ID, calendarId); - intent.putExtra(Events.TITLE, title); - return intent; - } - public void launchViewEvent(long eventId, long startMillis, long endMillis, int response) { Intent intent = new Intent(Intent.ACTION_VIEW); Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); @@ -765,64 +666,6 @@ public class CalendarController { mContext.startActivity(intent); } - private void launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit) { - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); - Intent intent = new Intent(Intent.ACTION_EDIT, uri); - intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); - intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); - intent.setClass(mContext, EditEventActivity.class); - intent.putExtra(EVENT_EDIT_ON_LAUNCH, edit); - mEventId = eventId; - mContext.startActivity(intent); - } - -// private void launchAlerts() { -// Intent intent = new Intent(); -// intent.setClass(mContext, AlertActivity.class); -// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); -// mContext.startActivity(intent); -// } - - private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { - launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); - } - - private void launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis, - long endMillis, int deleteWhich) { - DeleteEventHelper deleteEventHelper = new DeleteEventHelper(mContext, parentActivity, - parentActivity != null /* exit when done */); - deleteEventHelper.delete(startMillis, endMillis, eventId, deleteWhich); - } - - private void launchSearch(long eventId, String query, ComponentName componentName) { - final SearchManager searchManager = - (SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE); - final SearchableInfo searchableInfo = searchManager.getSearchableInfo(componentName); - final Intent intent = new Intent(Intent.ACTION_SEARCH); - intent.putExtra(SearchManager.QUERY, query); - intent.setComponent(searchableInfo.getSearchActivity()); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - mContext.startActivity(intent); - } - - /** - * Performs a manual refresh of calendars in all known accounts. - */ - public void refreshCalendars() { - Account[] accounts = AccountManager.get(mContext).getAccounts(); - Log.d(TAG, "Refreshing " + accounts.length + " accounts"); - - String authority = Calendars.CONTENT_URI.getAuthority(); - for (int i = 0; i < accounts.length; i++) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Refreshing calendars for: " + accounts[i]); - } - Bundle extras = new Bundle(); - extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(accounts[i], authority, extras); - } - } - // Forces the viewType. Should only be used for initialization. public void setViewType(int viewType) { mViewType = viewType; @@ -839,24 +682,12 @@ public class CalendarController { StringBuilder builder = new StringBuilder(); if ((eventInfo.eventType & EventType.GO_TO) != 0) { tmp = "Go to time/event"; - } else if ((eventInfo.eventType & EventType.CREATE_EVENT) != 0) { - tmp = "New event"; } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) { tmp = "View event"; } else if ((eventInfo.eventType & EventType.VIEW_EVENT_DETAILS) != 0) { tmp = "View details"; - } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) { - tmp = "Edit event"; - } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) { - tmp = "Delete event"; - } else if ((eventInfo.eventType & EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) != 0) { - tmp = "Launch select visible calendars"; - } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) { - tmp = "Launch settings"; } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) { tmp = "Refresh events"; - } else if ((eventInfo.eventType & EventType.SEARCH) != 0) { - tmp = "Search"; } else if ((eventInfo.eventType & EventType.USER_HOME) != 0) { tmp = "Gone home"; } else if ((eventInfo.eventType & EventType.UPDATE_TITLE) != 0) { diff --git a/src/com/android/calendar/CalendarEventModel.java b/src/com/android/calendar/CalendarEventModel.java deleted file mode 100644 index 8f6507e0..00000000 --- a/src/com/android/calendar/CalendarEventModel.java +++ /dev/null @@ -1,944 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.calendar; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.text.TextUtils; -import android.text.util.Rfc822Token; - -import com.android.calendar.event.EditEventHelper; -import com.android.calendar.event.EventColorCache; -import com.android.common.Rfc822Validator; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.TimeZone; - -/** - * Stores all the information needed to fill out an entry in the events table. - * This is a convenient way for storing information needed by the UI to write to - * the events table. Only fields that are important to the UI are included. - */ -public class CalendarEventModel implements Serializable { - private static final String TAG = "CalendarEventModel"; - - public static class Attendee implements Serializable { - @Override - public int hashCode() { - return (mEmail == null) ? 0 : mEmail.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Attendee)) { - return false; - } - Attendee other = (Attendee) obj; - if (!TextUtils.equals(mEmail, other.mEmail)) { - return false; - } - return true; - } - - String getDisplayName() { - if (TextUtils.isEmpty(mName)) { - return mEmail; - } else { - return mName; - } - } - - public String mName; - public String mEmail; - public int mStatus; - public String mIdentity; - public String mIdNamespace; - - public Attendee(String name, String email) { - this(name, email, Attendees.ATTENDEE_STATUS_NONE, null, null); - } - public Attendee(String name, String email, int status, String identity, - String idNamespace) { - mName = name; - mEmail = email; - mStatus = status; - mIdentity = identity; - mIdNamespace = idNamespace; - } - } - - /** - * A single reminder entry. - * - * Instances of the class are immutable. - */ - public static class ReminderEntry implements Comparable<ReminderEntry>, Serializable { - private final int mMinutes; - private final int mMethod; - - /** - * Returns a new ReminderEntry, with the specified minutes and method. - * - * @param minutes Number of minutes before the start of the event that the alert will fire. - * @param method Type of alert ({@link Reminders#METHOD_ALERT}, etc). - */ - public static ReminderEntry valueOf(int minutes, int method) { - // TODO: cache common instances - return new ReminderEntry(minutes, method); - } - - /** - * Returns a ReminderEntry, with the specified number of minutes and a default alert method. - * - * @param minutes Number of minutes before the start of the event that the alert will fire. - */ - public static ReminderEntry valueOf(int minutes) { - return valueOf(minutes, Reminders.METHOD_DEFAULT); - } - - /** - * Constructs a new ReminderEntry. - * - * @param minutes Number of minutes before the start of the event that the alert will fire. - * @param method Type of alert ({@link Reminders#METHOD_ALERT}, etc). - */ - private ReminderEntry(int minutes, int method) { - // TODO: error-check args - mMinutes = minutes; - mMethod = method; - } - - @Override - public int hashCode() { - return mMinutes * 10 + mMethod; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ReminderEntry)) { - return false; - } - - ReminderEntry re = (ReminderEntry) obj; - - if (re.mMinutes != mMinutes) { - return false; - } - - // Treat ALERT and DEFAULT as equivalent. This is useful during the "has anything - // "changed" test, so that if DEFAULT is present, but we don't change anything, - // the internal conversion of DEFAULT to ALERT doesn't force a database update. - return re.mMethod == mMethod || - (re.mMethod == Reminders.METHOD_DEFAULT && mMethod == Reminders.METHOD_ALERT) || - (re.mMethod == Reminders.METHOD_ALERT && mMethod == Reminders.METHOD_DEFAULT); - } - - @Override - public String toString() { - return "ReminderEntry min=" + mMinutes + " meth=" + mMethod; - } - - /** - * Comparison function for a sort ordered primarily descending by minutes, - * secondarily ascending by method type. - */ - @Override - public int compareTo(ReminderEntry re) { - if (re.mMinutes != mMinutes) { - return re.mMinutes - mMinutes; - } - if (re.mMethod != mMethod) { - return mMethod - re.mMethod; - } - return 0; - } - - /** Returns the minutes. */ - public int getMinutes() { - return mMinutes; - } - - /** Returns the alert method. */ - public int getMethod() { - return mMethod; - } - } - - // TODO strip out fields that don't ever get used - /** - * The uri of the event in the db. This should only be null for new events. - */ - public String mUri = null; - public long mId = -1; - public long mCalendarId = -1; - public String mCalendarDisplayName = ""; // Make sure this is in sync with the mCalendarId - private int mCalendarColor = -1; - private boolean mCalendarColorInitialized = false; - public String mCalendarAccountName; - public String mCalendarAccountType; - public int mCalendarMaxReminders; - public String mCalendarAllowedReminders; - public String mCalendarAllowedAttendeeTypes; - public String mCalendarAllowedAvailability; - - public String mSyncId = null; - public String mSyncAccount = null; - public String mSyncAccountType = null; - - public EventColorCache mEventColorCache; - private int mEventColor = -1; - private boolean mEventColorInitialized = false; - - // PROVIDER_NOTES owner account comes from the calendars table - public String mOwnerAccount = null; - public String mTitle = null; - public String mLocation = null; - public String mDescription = null; - public String mRrule = null; - public String mOrganizer = null; - public String mOrganizerDisplayName = null; - /** - * Read-Only - Derived from other fields - */ - public boolean mIsOrganizer = true; - public boolean mIsFirstEventInSeries = true; - - // This should be set the same as mStart when created and is used for making changes to - // recurring events. It should not be updated after it is initially set. - public long mOriginalStart = -1; - public long mStart = -1; - - // This should be set the same as mEnd when created and is used for making changes to - // recurring events. It should not be updated after it is initially set. - public long mOriginalEnd = -1; - public long mEnd = -1; - public String mDuration = null; - public String mTimezone = null; - public String mTimezone2 = null; - public boolean mAllDay = false; - public boolean mHasAlarm = false; - public int mAvailability = Events.AVAILABILITY_BUSY; - - // PROVIDER_NOTES How does an event not have attendee data? The owner is added - // as an attendee by default. - public boolean mHasAttendeeData = true; - public int mSelfAttendeeStatus = -1; - public int mOwnerAttendeeId = -1; - public String mOriginalSyncId = null; - public long mOriginalId = -1; - public Long mOriginalTime = null; - public Boolean mOriginalAllDay = null; - public boolean mGuestsCanModify = false; - public boolean mGuestsCanInviteOthers = false; - public boolean mGuestsCanSeeGuests = false; - - public boolean mOrganizerCanRespond = false; - public int mCalendarAccessLevel = Calendars.CAL_ACCESS_CONTRIBUTOR; - - public int mEventStatus = Events.STATUS_CONFIRMED; - - // The model can't be updated with a calendar cursor until it has been - // updated with an event cursor. - public boolean mModelUpdatedWithEventCursor; - - public int mAccessLevel = 0; - public ArrayList<ReminderEntry> mReminders; - public ArrayList<ReminderEntry> mDefaultReminders; - - // PROVIDER_NOTES Using EditEventHelper the owner should not be included in this - // list and will instead be added by saveEvent. Is this what we want? - public LinkedHashMap<String, Attendee> mAttendeesList; - - public CalendarEventModel() { - mReminders = new ArrayList<ReminderEntry>(); - mDefaultReminders = new ArrayList<ReminderEntry>(); - mAttendeesList = new LinkedHashMap<String, Attendee>(); - mTimezone = TimeZone.getDefault().getID(); - } - - public CalendarEventModel(Context context) { - this(); - - mTimezone = Utils.getTimeZone(context, null); - SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); - - String defaultReminder = prefs.getString( - GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); - int defaultReminderMins = Integer.parseInt(defaultReminder); - if (defaultReminderMins != GeneralPreferences.NO_REMINDER) { - // Assume all calendars allow at least one reminder. - mHasAlarm = true; - mReminders.add(ReminderEntry.valueOf(defaultReminderMins)); - mDefaultReminders.add(ReminderEntry.valueOf(defaultReminderMins)); - } - } - - public CalendarEventModel(Context context, Intent intent) { - this(context); - - if (intent == null) { - return; - } - - String title = intent.getStringExtra(Events.TITLE); - if (title != null) { - mTitle = title; - } - - String location = intent.getStringExtra(Events.EVENT_LOCATION); - if (location != null) { - mLocation = location; - } - - String description = intent.getStringExtra(Events.DESCRIPTION); - if (description != null) { - mDescription = description; - } - - int availability = intent.getIntExtra(Events.AVAILABILITY, -1); - if (availability != -1) { - mAvailability = availability; - } - - int accessLevel = intent.getIntExtra(Events.ACCESS_LEVEL, -1); - if (accessLevel != -1) { - if (accessLevel > 0) { - // TODO remove this if we add support for - // Events.ACCESS_CONFIDENTIAL - accessLevel--; - } - mAccessLevel = accessLevel; - } - - String rrule = intent.getStringExtra(Events.RRULE); - if (!TextUtils.isEmpty(rrule)) { - mRrule = rrule; - } - - String emails = intent.getStringExtra(Intent.EXTRA_EMAIL); - if (!TextUtils.isEmpty(emails)) { - String[] emailArray = emails.split("[ ,;]"); - for (String email : emailArray) { - if (!TextUtils.isEmpty(email) && email.contains("@")) { - email = email.trim(); - if (!mAttendeesList.containsKey(email)) { - mAttendeesList.put(email, new Attendee("", email)); - } - } - } - } - } - - public boolean isValid() { - if (mCalendarId == -1) { - return false; - } - if (TextUtils.isEmpty(mOwnerAccount)) { - return false; - } - return true; - } - - public boolean isEmpty() { - if (mTitle != null && mTitle.trim().length() > 0) { - return false; - } - - if (mLocation != null && mLocation.trim().length() > 0) { - return false; - } - - if (mDescription != null && mDescription.trim().length() > 0) { - return false; - } - - return true; - } - - public void clear() { - mUri = null; - mId = -1; - mCalendarId = -1; - mCalendarColor = -1; - mCalendarColorInitialized = false; - - mEventColorCache = null; - mEventColor = -1; - mEventColorInitialized = false; - - mSyncId = null; - mSyncAccount = null; - mSyncAccountType = null; - mOwnerAccount = null; - - mTitle = null; - mLocation = null; - mDescription = null; - mRrule = null; - mOrganizer = null; - mOrganizerDisplayName = null; - mIsOrganizer = true; - mIsFirstEventInSeries = true; - - mOriginalStart = -1; - mStart = -1; - mOriginalEnd = -1; - mEnd = -1; - mDuration = null; - mTimezone = null; - mTimezone2 = null; - mAllDay = false; - mHasAlarm = false; - - mHasAttendeeData = true; - mSelfAttendeeStatus = -1; - mOwnerAttendeeId = -1; - mOriginalId = -1; - mOriginalSyncId = null; - mOriginalTime = null; - mOriginalAllDay = null; - - mGuestsCanModify = false; - mGuestsCanInviteOthers = false; - mGuestsCanSeeGuests = false; - mAccessLevel = 0; - mEventStatus = Events.STATUS_CONFIRMED; - mOrganizerCanRespond = false; - mCalendarAccessLevel = Calendars.CAL_ACCESS_CONTRIBUTOR; - mModelUpdatedWithEventCursor = false; - mCalendarAllowedReminders = null; - mCalendarAllowedAttendeeTypes = null; - mCalendarAllowedAvailability = null; - - mReminders = new ArrayList<ReminderEntry>(); - mAttendeesList.clear(); - } - - public void addAttendee(Attendee attendee) { - mAttendeesList.put(attendee.mEmail, attendee); - } - - public void addAttendees(String attendees, Rfc822Validator validator) { - final LinkedHashSet<Rfc822Token> addresses = EditEventHelper.getAddressesFromList( - attendees, validator); - synchronized (this) { - for (final Rfc822Token address : addresses) { - final Attendee attendee = new Attendee(address.getName(), address.getAddress()); - if (TextUtils.isEmpty(attendee.mName)) { - attendee.mName = attendee.mEmail; - } - addAttendee(attendee); - } - } - } - - public void removeAttendee(Attendee attendee) { - mAttendeesList.remove(attendee.mEmail); - } - - public String getAttendeesString() { - StringBuilder b = new StringBuilder(); - for (Attendee attendee : mAttendeesList.values()) { - String name = attendee.mName; - String email = attendee.mEmail; - String status = Integer.toString(attendee.mStatus); - b.append("name:").append(name); - b.append(" email:").append(email); - b.append(" status:").append(status); - } - return b.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (mAllDay ? 1231 : 1237); - result = prime * result + ((mAttendeesList == null) ? 0 : getAttendeesString().hashCode()); - result = prime * result + (int) (mCalendarId ^ (mCalendarId >>> 32)); - result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode()); - result = prime * result + ((mDuration == null) ? 0 : mDuration.hashCode()); - result = prime * result + (int) (mEnd ^ (mEnd >>> 32)); - result = prime * result + (mGuestsCanInviteOthers ? 1231 : 1237); - result = prime * result + (mGuestsCanModify ? 1231 : 1237); - result = prime * result + (mGuestsCanSeeGuests ? 1231 : 1237); - result = prime * result + (mOrganizerCanRespond ? 1231 : 1237); - result = prime * result + (mModelUpdatedWithEventCursor ? 1231 : 1237); - result = prime * result + mCalendarAccessLevel; - result = prime * result + (mHasAlarm ? 1231 : 1237); - result = prime * result + (mHasAttendeeData ? 1231 : 1237); - result = prime * result + (int) (mId ^ (mId >>> 32)); - result = prime * result + (mIsFirstEventInSeries ? 1231 : 1237); - result = prime * result + (mIsOrganizer ? 1231 : 1237); - result = prime * result + ((mLocation == null) ? 0 : mLocation.hashCode()); - result = prime * result + ((mOrganizer == null) ? 0 : mOrganizer.hashCode()); - result = prime * result + ((mOriginalAllDay == null) ? 0 : mOriginalAllDay.hashCode()); - result = prime * result + (int) (mOriginalEnd ^ (mOriginalEnd >>> 32)); - result = prime * result + ((mOriginalSyncId == null) ? 0 : mOriginalSyncId.hashCode()); - result = prime * result + (int) (mOriginalId ^ (mOriginalEnd >>> 32)); - result = prime * result + (int) (mOriginalStart ^ (mOriginalStart >>> 32)); - result = prime * result + ((mOriginalTime == null) ? 0 : mOriginalTime.hashCode()); - result = prime * result + ((mOwnerAccount == null) ? 0 : mOwnerAccount.hashCode()); - result = prime * result + ((mReminders == null) ? 0 : mReminders.hashCode()); - result = prime * result + ((mRrule == null) ? 0 : mRrule.hashCode()); - result = prime * result + mSelfAttendeeStatus; - result = prime * result + mOwnerAttendeeId; - result = prime * result + (int) (mStart ^ (mStart >>> 32)); - result = prime * result + ((mSyncAccount == null) ? 0 : mSyncAccount.hashCode()); - result = prime * result + ((mSyncAccountType == null) ? 0 : mSyncAccountType.hashCode()); - result = prime * result + ((mSyncId == null) ? 0 : mSyncId.hashCode()); - result = prime * result + ((mTimezone == null) ? 0 : mTimezone.hashCode()); - result = prime * result + ((mTimezone2 == null) ? 0 : mTimezone2.hashCode()); - result = prime * result + ((mTitle == null) ? 0 : mTitle.hashCode()); - result = prime * result + (mAvailability); - result = prime * result + ((mUri == null) ? 0 : mUri.hashCode()); - result = prime * result + mAccessLevel; - result = prime * result + mEventStatus; - return result; - } - - // Autogenerated equals method - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof CalendarEventModel)) { - return false; - } - - CalendarEventModel other = (CalendarEventModel) obj; - if (!checkOriginalModelFields(other)) { - return false; - } - - if (mLocation == null) { - if (other.mLocation != null) { - return false; - } - } else if (!mLocation.equals(other.mLocation)) { - return false; - } - - if (mTitle == null) { - if (other.mTitle != null) { - return false; - } - } else if (!mTitle.equals(other.mTitle)) { - return false; - } - - if (mDescription == null) { - if (other.mDescription != null) { - return false; - } - } else if (!mDescription.equals(other.mDescription)) { - return false; - } - - if (mDuration == null) { - if (other.mDuration != null) { - return false; - } - } else if (!mDuration.equals(other.mDuration)) { - return false; - } - - if (mEnd != other.mEnd) { - return false; - } - if (mIsFirstEventInSeries != other.mIsFirstEventInSeries) { - return false; - } - if (mOriginalEnd != other.mOriginalEnd) { - return false; - } - - if (mOriginalStart != other.mOriginalStart) { - return false; - } - if (mStart != other.mStart) { - return false; - } - - if (mOriginalId != other.mOriginalId) { - return false; - } - - if (mOriginalSyncId == null) { - if (other.mOriginalSyncId != null) { - return false; - } - } else if (!mOriginalSyncId.equals(other.mOriginalSyncId)) { - return false; - } - - if (mRrule == null) { - if (other.mRrule != null) { - return false; - } - } else if (!mRrule.equals(other.mRrule)) { - return false; - } - return true; - } - - /** - * Whether the event has been modified based on its original model. - * - * @param originalModel - * @return true if the model is unchanged, false otherwise - */ - public boolean isUnchanged(CalendarEventModel originalModel) { - if (this == originalModel) { - return true; - } - if (originalModel == null) { - return false; - } - - if (!checkOriginalModelFields(originalModel)) { - return false; - } - - if (TextUtils.isEmpty(mLocation)) { - if (!TextUtils.isEmpty(originalModel.mLocation)) { - return false; - } - } else if (!mLocation.equals(originalModel.mLocation)) { - return false; - } - - if (TextUtils.isEmpty(mTitle)) { - if (!TextUtils.isEmpty(originalModel.mTitle)) { - return false; - } - } else if (!mTitle.equals(originalModel.mTitle)) { - return false; - } - - if (TextUtils.isEmpty(mDescription)) { - if (!TextUtils.isEmpty(originalModel.mDescription)) { - return false; - } - } else if (!mDescription.equals(originalModel.mDescription)) { - return false; - } - - if (TextUtils.isEmpty(mDuration)) { - if (!TextUtils.isEmpty(originalModel.mDuration)) { - return false; - } - } else if (!mDuration.equals(originalModel.mDuration)) { - return false; - } - - if (mEnd != mOriginalEnd) { - return false; - } - if (mStart != mOriginalStart) { - return false; - } - - // If this changed the original id and it's not just an exception to the - // original event - if (mOriginalId != originalModel.mOriginalId && mOriginalId != originalModel.mId) { - return false; - } - - if (TextUtils.isEmpty(mRrule)) { - // if the rrule is no longer empty check if this is an exception - if (!TextUtils.isEmpty(originalModel.mRrule)) { - boolean syncIdNotReferenced = mOriginalSyncId == null - || !mOriginalSyncId.equals(originalModel.mSyncId); - boolean localIdNotReferenced = mOriginalId == -1 - || !(mOriginalId == originalModel.mId); - if (syncIdNotReferenced && localIdNotReferenced) { - return false; - } - } - } else if (!mRrule.equals(originalModel.mRrule)) { - return false; - } - - return true; - } - - /** - * Checks against an original model for changes to an event. This covers all - * the fields that should remain consistent between an original event model - * and the new one if nothing in the event was modified. This is also the - * portion that overlaps with equality between two event models. - * - * @param originalModel - * @return true if these fields are unchanged, false otherwise - */ - protected boolean checkOriginalModelFields(CalendarEventModel originalModel) { - if (mAllDay != originalModel.mAllDay) { - return false; - } - if (mAttendeesList == null) { - if (originalModel.mAttendeesList != null) { - return false; - } - } else if (!mAttendeesList.equals(originalModel.mAttendeesList)) { - return false; - } - - if (mCalendarId != originalModel.mCalendarId) { - return false; - } - if (mCalendarColor != originalModel.mCalendarColor) { - return false; - } - if (mCalendarColorInitialized != originalModel.mCalendarColorInitialized) { - return false; - } - if (mGuestsCanInviteOthers != originalModel.mGuestsCanInviteOthers) { - return false; - } - if (mGuestsCanModify != originalModel.mGuestsCanModify) { - return false; - } - if (mGuestsCanSeeGuests != originalModel.mGuestsCanSeeGuests) { - return false; - } - if (mOrganizerCanRespond != originalModel.mOrganizerCanRespond) { - return false; - } - if (mCalendarAccessLevel != originalModel.mCalendarAccessLevel) { - return false; - } - if (mModelUpdatedWithEventCursor != originalModel.mModelUpdatedWithEventCursor) { - return false; - } - if (mHasAlarm != originalModel.mHasAlarm) { - return false; - } - if (mHasAttendeeData != originalModel.mHasAttendeeData) { - return false; - } - if (mId != originalModel.mId) { - return false; - } - if (mIsOrganizer != originalModel.mIsOrganizer) { - return false; - } - - if (mOrganizer == null) { - if (originalModel.mOrganizer != null) { - return false; - } - } else if (!mOrganizer.equals(originalModel.mOrganizer)) { - return false; - } - - if (mOriginalAllDay == null) { - if (originalModel.mOriginalAllDay != null) { - return false; - } - } else if (!mOriginalAllDay.equals(originalModel.mOriginalAllDay)) { - return false; - } - - if (mOriginalTime == null) { - if (originalModel.mOriginalTime != null) { - return false; - } - } else if (!mOriginalTime.equals(originalModel.mOriginalTime)) { - return false; - } - - if (mOwnerAccount == null) { - if (originalModel.mOwnerAccount != null) { - return false; - } - } else if (!mOwnerAccount.equals(originalModel.mOwnerAccount)) { - return false; - } - - if (mReminders == null) { - if (originalModel.mReminders != null) { - return false; - } - } else if (!mReminders.equals(originalModel.mReminders)) { - return false; - } - - if (mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus) { - return false; - } - if (mOwnerAttendeeId != originalModel.mOwnerAttendeeId) { - return false; - } - if (mSyncAccount == null) { - if (originalModel.mSyncAccount != null) { - return false; - } - } else if (!mSyncAccount.equals(originalModel.mSyncAccount)) { - return false; - } - - if (mSyncAccountType == null) { - if (originalModel.mSyncAccountType != null) { - return false; - } - } else if (!mSyncAccountType.equals(originalModel.mSyncAccountType)) { - return false; - } - - if (mSyncId == null) { - if (originalModel.mSyncId != null) { - return false; - } - } else if (!mSyncId.equals(originalModel.mSyncId)) { - return false; - } - - if (mTimezone == null) { - if (originalModel.mTimezone != null) { - return false; - } - } else if (!mTimezone.equals(originalModel.mTimezone)) { - return false; - } - - if (mTimezone2 == null) { - if (originalModel.mTimezone2 != null) { - return false; - } - } else if (!mTimezone2.equals(originalModel.mTimezone2)) { - return false; - } - - if (mAvailability != originalModel.mAvailability) { - return false; - } - - if (mUri == null) { - if (originalModel.mUri != null) { - return false; - } - } else if (!mUri.equals(originalModel.mUri)) { - return false; - } - - if (mAccessLevel != originalModel.mAccessLevel) { - return false; - } - - if (mEventStatus != originalModel.mEventStatus) { - return false; - } - - if (mEventColor != originalModel.mEventColor) { - return false; - } - - if (mEventColorInitialized != originalModel.mEventColorInitialized) { - return false; - } - - return true; - } - - /** - * Sort and uniquify mReminderMinutes. - * - * @return true (for convenience of caller) - */ - public boolean normalizeReminders() { - if (mReminders.size() <= 1) { - return true; - } - - // sort - Collections.sort(mReminders); - - // remove duplicates - ReminderEntry prev = mReminders.get(mReminders.size()-1); - for (int i = mReminders.size()-2; i >= 0; --i) { - ReminderEntry cur = mReminders.get(i); - if (prev.equals(cur)) { - // match, remove later entry - mReminders.remove(i+1); - } - prev = cur; - } - - return true; - } - - public boolean isCalendarColorInitialized() { - return mCalendarColorInitialized; - } - - public boolean isEventColorInitialized() { - return mEventColorInitialized; - } - - public int getCalendarColor() { - return mCalendarColor; - } - - public int getEventColor() { - return mEventColor; - } - - public void setCalendarColor(int color) { - mCalendarColor = color; - mCalendarColorInitialized = true; - } - - public void setEventColor(int color) { - mEventColor = color; - mEventColorInitialized = true; - } - - public int[] getCalendarEventColors() { - if (mEventColorCache != null) { - return mEventColorCache.getColorArray(mCalendarAccountName, mCalendarAccountType); - } - return null; - } - - public int getEventColorKey() { - if (mEventColorCache != null) { - return mEventColorCache.getColorKey(mCalendarAccountName, mCalendarAccountType, - mEventColor); - } - return -1; - } -} diff --git a/src/com/android/calendar/CalendarRecentSuggestionsProvider.java b/src/com/android/calendar/CalendarRecentSuggestionsProvider.java deleted file mode 100644 index 2e3deae7..00000000 --- a/src/com/android/calendar/CalendarRecentSuggestionsProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.calendar; - -import android.content.SearchRecentSuggestionsProvider; - -public class CalendarRecentSuggestionsProvider extends SearchRecentSuggestionsProvider { - - public final static int MODE = DATABASE_MODE_QUERIES; - - public CalendarRecentSuggestionsProvider() { - } - - @Override - public boolean onCreate() { - setupSuggestions(Utils.getSearchAuthority(getContext()), MODE); - return super.onCreate(); - } - -} diff --git a/src/com/android/calendar/CalendarSettingsActivity.java b/src/com/android/calendar/CalendarSettingsActivity.java deleted file mode 100644 index a864b532..00000000 --- a/src/com/android/calendar/CalendarSettingsActivity.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.ActionBar; -import android.content.ContentResolver; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceActivity; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Calendars; -import android.provider.Settings; -import android.text.format.DateUtils; -import android.view.Menu; -import android.view.MenuItem; - -import java.util.List; - -public class CalendarSettingsActivity extends PreferenceActivity { - private static final int CHECK_ACCOUNTS_DELAY = 3000; - private Account[] mAccounts; - private Handler mHandler = new Handler(); - private boolean mHideMenuButtons = false; - - @Override - public void onBuildHeaders(List<Header> target) { - loadHeadersFromResource(R.xml.calendar_settings_headers, target); - - Account[] accounts = AccountManager.get(this).getAccounts(); - if (accounts != null) { - int length = accounts.length; - for (int i = 0; i < length; i++) { - Account acct = accounts[i]; - if (ContentResolver.getIsSyncable(acct, CalendarContract.AUTHORITY) > 0) { - Header accountHeader = new Header(); - accountHeader.title = acct.name; - accountHeader.fragment = - "com.android.calendar.selectcalendars.SelectCalendarsSyncFragment"; - Bundle args = new Bundle(); - args.putString(Calendars.ACCOUNT_NAME, acct.name); - args.putString(Calendars.ACCOUNT_TYPE, acct.type); - accountHeader.fragmentArguments = args; - target.add(1, accountHeader); - } - } - } - mAccounts = accounts; - if (Utils.getTardis() + DateUtils.MINUTE_IN_MILLIS > System.currentTimeMillis()) { - Header tardisHeader = new Header(); - tardisHeader.title = getString(R.string.preferences_experimental_category); - tardisHeader.fragment = "com.android.calendar.OtherPreferences"; - target.add(tardisHeader); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } else if (item.getItemId() == R.id.action_add_account) { - Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); - final String[] array = { "com.android.calendar" }; - nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); - nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(nextIntent); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (!mHideMenuButtons) { - getMenuInflater().inflate(R.menu.settings_title_bar, menu); - } - getActionBar() - .setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); - return true; - } - - @Override - public void onResume() { - if (mHandler != null) { - mHandler.postDelayed(mCheckAccounts, CHECK_ACCOUNTS_DELAY); - } - super.onResume(); - } - - @Override - public void onPause() { - if (mHandler != null) { - mHandler.removeCallbacks(mCheckAccounts); - } - super.onPause(); - } - - @Override - protected boolean isValidFragment(String fragmentName) { - // This activity is not exported so we can just approve everything - return true; - } - - Runnable mCheckAccounts = new Runnable() { - @Override - public void run() { - Account[] accounts = AccountManager.get(CalendarSettingsActivity.this).getAccounts(); - if (accounts != null && !accounts.equals(mAccounts)) { - invalidateHeaders(); - } - } - }; - - public void hideMenuButtons() { - mHideMenuButtons = true; - } -} diff --git a/src/com/android/calendar/CalendarViewAdapter.java b/src/com/android/calendar/CalendarViewAdapter.java index f07d4d8a..524268fc 100644 --- a/src/com/android/calendar/CalendarViewAdapter.java +++ b/src/com/android/calendar/CalendarViewAdapter.java @@ -198,11 +198,6 @@ public class CalendarViewAdapter extends BaseAdapter { weekDay.setVisibility(View.GONE); date.setText(buildMonthYearDate()); break; - case ViewType.AGENDA: - weekDay.setVisibility(View.VISIBLE); - weekDay.setText(buildDayOfWeek()); - date.setText(buildFullDate()); - break; default: v = null; break; @@ -229,9 +224,6 @@ public class CalendarViewAdapter extends BaseAdapter { case ViewType.MONTH: title.setText(mButtonNames [MONTH_BUTTON_INDEX]); break; - case ViewType.AGENDA: - title.setText(mButtonNames [AGENDA_BUTTON_INDEX]); - break; default: v = null; break; @@ -280,12 +272,6 @@ public class CalendarViewAdapter extends BaseAdapter { date.setText(buildMonthDate()); } break; - case AGENDA_BUTTON_INDEX: - viewType.setText(mButtonNames [AGENDA_BUTTON_INDEX]); - if (mShowDate) { - date.setText(buildMonthDayDate()); - } - break; default: v = convertView; break; @@ -379,9 +365,8 @@ public class CalendarViewAdapter extends BaseAdapter { | DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString(); return date; } - private String buildWeekDate() { - + private String buildWeekDate() { // Calculate the start of the week, taking into account the "first day of the week" // setting. @@ -422,4 +407,3 @@ public class CalendarViewAdapter extends BaseAdapter { } } - diff --git a/src/com/android/calendar/CloudNotificationBackplane.java b/src/com/android/calendar/CloudNotificationBackplane.java deleted file mode 100644 index d9ff0cc0..00000000 --- a/src/com/android/calendar/CloudNotificationBackplane.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.calendar; - -import java.io.IOException; - -import android.content.Context; -import android.os.Bundle; - -public interface CloudNotificationBackplane { - public boolean open(Context context); - public boolean subscribeToGroup(String senderId, String account, String groupId) - throws IOException; - public void send(String to, String msgId, Bundle data) throws IOException; - public void close(); -} diff --git a/src/com/android/calendar/ColorChipView.java b/src/com/android/calendar/ColorChipView.java deleted file mode 100644 index daf0933a..00000000 --- a/src/com/android/calendar/ColorChipView.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.util.AttributeSet; -import android.view.View; - - - - -/** - * A custom view for a color chip for an event that can be drawn differently - * accroding to the event's status. - * - */ -public class ColorChipView extends View { - - private static final String TAG = "ColorChipView"; - // Style of drawing - // Full rectangle for accepted events - // Border for tentative events - // Cross-hatched with 50% transparency for declined events - - public static final int DRAW_FULL = 0; - public static final int DRAW_BORDER = 1; - public static final int DRAW_FADED = 2; - - private int mDrawStyle = DRAW_FULL; - private float mDefStrokeWidth; - private Paint mPaint; - - private static final int DEF_BORDER_WIDTH = 4; - - int mBorderWidth = DEF_BORDER_WIDTH; - - int mColor; - - public ColorChipView(Context context) { - super(context); - init(); - } - - public ColorChipView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - private void init() { - mPaint = new Paint(); - mDefStrokeWidth = mPaint.getStrokeWidth(); - mPaint.setStyle(Style.FILL_AND_STROKE); - } - - - public void setDrawStyle(int style) { - if (style != DRAW_FULL && style != DRAW_BORDER && style != DRAW_FADED) { - return; - } - mDrawStyle = style; - invalidate(); - } - - public void setBorderWidth(int width) { - if (width >= 0) { - mBorderWidth = width; - invalidate(); - } - } - - public void setColor(int color) { - mColor = color; - invalidate(); - } - - @Override - public void onDraw(Canvas c) { - - int right = getWidth() - 1; - int bottom = getHeight() - 1; - mPaint.setColor(mDrawStyle == DRAW_FADED ? - Utils.getDeclinedColorFromColor(mColor) : mColor); - - switch (mDrawStyle) { - case DRAW_FADED: - case DRAW_FULL: - mPaint.setStrokeWidth(mDefStrokeWidth); - c.drawRect(0, 0, right, bottom, mPaint); - break; - case DRAW_BORDER: - if (mBorderWidth <= 0) { - return; - } - int halfBorderWidth = mBorderWidth / 2; - int top = halfBorderWidth; - int left = halfBorderWidth; - mPaint.setStrokeWidth(mBorderWidth); - - float[] lines = new float[16]; - int ptr = 0; - lines [ptr++] = 0; - lines [ptr++] = top; - lines [ptr++] = right; - lines [ptr++] = top; - lines [ptr++] = 0; - lines [ptr++] = bottom - halfBorderWidth; - lines [ptr++] = right; - lines [ptr++] = bottom - halfBorderWidth; - lines [ptr++] = left; - lines [ptr++] = 0; - lines [ptr++] = left; - lines [ptr++] = bottom; - lines [ptr++] = right - halfBorderWidth; - lines [ptr++] = 0; - lines [ptr++] = right - halfBorderWidth; - lines [ptr++] = bottom; - c.drawLines(lines, mPaint); - break; - } - } -} diff --git a/src/com/android/calendar/ContactsAsyncHelper.java b/src/com/android/calendar/ContactsAsyncHelper.java deleted file mode 100644 index b7e01dba..00000000 --- a/src/com/android/calendar/ContactsAsyncHelper.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.event.EditEventHelper.AttendeeItem; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.provider.ContactsContract.Contacts; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import java.io.InputStream; - -/** - * Helper class for async access of images. - */ -public class ContactsAsyncHelper extends Handler { - - private static final boolean DBG = false; - private static final String LOG_TAG = "ContactsAsyncHelper"; - - private static ContactsAsyncHelper mInstance = null; - - /** - * Interface for a WorkerHandler result return. - */ - public interface OnImageLoadCompleteListener { - /** - * Called when the image load is complete. - * - * @param imagePresent true if an image was found - */ - public void onImageLoadComplete(int token, Object cookie, ImageView iView, - boolean imagePresent); - } - - // constants - private static final int EVENT_LOAD_IMAGE = 1; - private static final int EVENT_LOAD_DRAWABLE = 2; - private static final int DEFAULT_TOKEN = -1; - - // static objects - private static Handler sThreadHandler; - - private static final class WorkerArgs { - public Context context; - public ImageView view; - public Uri uri; - public int defaultResource; - public Object result; - public AttendeeItem item; - public Runnable callback; - } - - /** - * Thread worker class that handles the task of opening the stream and loading - * the images. - */ - private class WorkerHandler extends Handler { - public WorkerHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - WorkerArgs args = (WorkerArgs) msg.obj; - - switch (msg.arg1) { - case EVENT_LOAD_DRAWABLE: - case EVENT_LOAD_IMAGE: - InputStream inputStream = null; - try { - inputStream = Contacts.openContactPhotoInputStream( - args.context.getContentResolver(), args.uri); - } catch (Exception e) { - Log.e(LOG_TAG, "Error opening photo input stream", e); - } - - if (inputStream != null) { - args.result = Drawable.createFromStream(inputStream, args.uri.toString()); - - if (DBG) Log.d(LOG_TAG, "Loading image: " + msg.arg1 + - " token: " + msg.what + " image URI: " + args.uri); - } else { - args.result = null; - if (DBG) Log.d(LOG_TAG, "Problem with image: " + msg.arg1 + - " token: " + msg.what + " image URI: " + args.uri + - ", using default image."); - } - break; - default: - } - - // send the reply to the enclosing class. - Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what); - reply.arg1 = msg.arg1; - reply.obj = msg.obj; - reply.sendToTarget(); - } - } - - /** - * Private constructor for static class - */ - private ContactsAsyncHelper() { - HandlerThread thread = new HandlerThread("ContactsAsyncWorker"); - thread.start(); - sThreadHandler = new WorkerHandler(thread.getLooper()); - } - - /** - * Start an image load, attach the result to the specified CallerInfo object. - * Note, when the query is started, we make the ImageView INVISIBLE if the - * placeholderImageResource value is -1. When we're given a valid (!= -1) - * placeholderImageResource value, we make sure the image is visible. - */ - public static final void updateImageViewWithContactPhotoAsync(Context context, - ImageView imageView, Uri contact, int placeholderImageResource) { - - // in case the source caller info is null, the URI will be null as well. - // just update using the placeholder image in this case. - if (contact == null) { - if (DBG) Log.d(LOG_TAG, "target image is null, just display placeholder."); - imageView.setVisibility(View.VISIBLE); - imageView.setImageResource(placeholderImageResource); - return; - } - - // Added additional Cookie field in the callee to handle arguments - // sent to the callback function. - - // setup arguments - WorkerArgs args = new WorkerArgs(); - args.context = context; - args.view = imageView; - args.uri = contact; - args.defaultResource = placeholderImageResource; - - if (mInstance == null) { - mInstance = new ContactsAsyncHelper(); - } - // setup message arguments - Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN); - msg.arg1 = EVENT_LOAD_IMAGE; - msg.obj = args; - - if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri + - ", displaying default image for now."); - - // set the default image first, when the query is complete, we will - // replace the image with the correct one. - if (placeholderImageResource != -1) { - imageView.setVisibility(View.VISIBLE); - imageView.setImageResource(placeholderImageResource); - } else { - imageView.setVisibility(View.INVISIBLE); - } - - // notify the thread to begin working - sThreadHandler.sendMessage(msg); - } - - /** - * Start an image load, attach the result to the specified CallerInfo object. - * Note, when the query is started, we make the ImageView INVISIBLE if the - * placeholderImageResource value is -1. When we're given a valid (!= -1) - * placeholderImageResource value, we make sure the image is visible. - */ - public static final void retrieveContactPhotoAsync(Context context, - AttendeeItem item, Runnable run, Uri photoUri) { - - // in case the source caller info is null, the URI will be null as well. - // just return as there's nothing to do. - if (photoUri == null) { - return; - } - - // Added additional Cookie field in the callee to handle arguments - // sent to the callback function. - - // setup arguments - WorkerArgs args = new WorkerArgs(); - args.context = context; - args.item = item; - args.uri = photoUri; - args.callback = run; - - if (mInstance == null) { - mInstance = new ContactsAsyncHelper(); - } - // setup message arguments - Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN); - msg.arg1 = EVENT_LOAD_DRAWABLE; - msg.obj = args; - - if (DBG) Log.d(LOG_TAG, "Begin loading drawable: " + args.uri); - - - // notify the thread to begin working - sThreadHandler.sendMessage(msg); - } - - /** - * Called when loading is done. - */ - @Override - public void handleMessage(Message msg) { - WorkerArgs args = (WorkerArgs) msg.obj; - switch (msg.arg1) { - case EVENT_LOAD_IMAGE: - // if the image has been loaded then display it, otherwise set default. - // in either case, make sure the image is visible. - if (args.result != null) { - args.view.setVisibility(View.VISIBLE); - args.view.setImageDrawable((Drawable) args.result); - } else if (args.defaultResource != -1) { - args.view.setVisibility(View.VISIBLE); - args.view.setImageResource(args.defaultResource); - } - break; - case EVENT_LOAD_DRAWABLE: - if (args.result != null) { - args.item.mBadge = (Drawable) args.result; - if (args.callback != null) { - args.callback.run(); - } - } - break; - default: - } - } -} diff --git a/src/com/android/calendar/DayFragment.java b/src/com/android/calendar/DayFragment.java index 72525959..8999141d 100644 --- a/src/com/android/calendar/DayFragment.java +++ b/src/com/android/calendar/DayFragment.java @@ -139,11 +139,6 @@ public class DayFragment extends Fragment implements CalendarController.EventHan @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - - long time = getSelectedTimeInMillis(); - if (time != -1) { - outState.putLong(BUNDLE_KEY_RESTORE_TIME, time); - } } @Override diff --git a/src/com/android/calendar/DayOfMonthCursor.java b/src/com/android/calendar/DayOfMonthCursor.java deleted file mode 100644 index f727669a..00000000 --- a/src/com/android/calendar/DayOfMonthCursor.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.util.MonthDisplayHelper; - -/** - * Helps control and display a month view of a calendar that has a current - * selected day. - * <ul> - * <li>Keeps track of current month, day, year</li> - * <li>Keeps track of current cursor position (row, column)</li> - * <li>Provides methods to help display the calendar</li> - * <li>Provides methods to move the cursor up / down / left / right.</li> - * </ul> - * - * This should be used by anyone who presents a month view to users and wishes - * to behave consistently with other widgets and apps; if we ever change our - * mind about when to flip the month, we can change it here only. - * - * @hide - */ -public class DayOfMonthCursor extends MonthDisplayHelper { - - private int mRow; - private int mColumn; - - /** - * @param year The initial year. - * @param month The initial month. - * @param dayOfMonth The initial dayOfMonth. - * @param weekStartDay What dayOfMonth of the week the week should start, - * in terms of {@link java.util.Calendar} constants such as - * {@link java.util.Calendar#SUNDAY}. - */ - public DayOfMonthCursor(int year, int month, int dayOfMonth, int weekStartDay) { - super(year, month, weekStartDay); - mRow = getRowOf(dayOfMonth); - mColumn = getColumnOf(dayOfMonth); - } - - - public int getSelectedRow() { - return mRow; - } - - public int getSelectedColumn() { - return mColumn; - } - - public void setSelectedRowColumn(int row, int col) { - mRow = row; - mColumn = col; - } - - public int getSelectedDayOfMonth() { - return getDayAt(mRow, mColumn); - } - - /** - * @return 0 if the selection is in the current month, otherwise -1 or +1 - * depending on whether the selection is in the first or last row. - */ - public int getSelectedMonthOffset() { - if (isWithinCurrentMonth(mRow, mColumn)) { - return 0; - } - if (mRow == 0) { - return -1; - } - return 1; - } - - public void setSelectedDayOfMonth(int dayOfMonth) { - mRow = getRowOf(dayOfMonth); - mColumn = getColumnOf(dayOfMonth); - } - - public boolean isSelected(int row, int column) { - return (mRow == row) && (mColumn == column); - } - - /** - * Move up one box, potentially flipping to the previous month. - * @return Whether the month was flipped to the previous month - * due to the move. - */ - public boolean up() { - if (isWithinCurrentMonth(mRow - 1, mColumn)) { - // within current month, just move up - mRow--; - return false; - } - // flip back to previous month, same column, first position within month - previousMonth(); - mRow = 5; - while(!isWithinCurrentMonth(mRow, mColumn)) { - mRow--; - } - return true; - } - - /** - * Move down one box, potentially flipping to the next month. - * @return Whether the month was flipped to the next month - * due to the move. - */ - public boolean down() { - if (isWithinCurrentMonth(mRow + 1, mColumn)) { - // within current month, just move down - mRow++; - return false; - } - // flip to next month, same column, first position within month - nextMonth(); - mRow = 0; - while (!isWithinCurrentMonth(mRow, mColumn)) { - mRow++; - } - return true; - } - - /** - * Move left one box, potentially flipping to the previous month. - * @return Whether the month was flipped to the previous month - * due to the move. - */ - public boolean left() { - if (mColumn == 0) { - mRow--; - mColumn = 6; - } else { - mColumn--; - } - - if (isWithinCurrentMonth(mRow, mColumn)) { - return false; - } - - // need to flip to last day of previous month - previousMonth(); - int lastDay = getNumberOfDaysInMonth(); - mRow = getRowOf(lastDay); - mColumn = getColumnOf(lastDay); - return true; - } - - /** - * Move right one box, potentially flipping to the next month. - * @return Whether the month was flipped to the next month - * due to the move. - */ - public boolean right() { - if (mColumn == 6) { - mRow++; - mColumn = 0; - } else { - mColumn++; - } - - if (isWithinCurrentMonth(mRow, mColumn)) { - return false; - } - - // need to flip to first day of next month - nextMonth(); - mRow = 0; - mColumn = 0; - while (!isWithinCurrentMonth(mRow, mColumn)) { - mColumn++; - } - return true; - } - -} diff --git a/src/com/android/calendar/DayView.java b/src/com/android/calendar/DayView.java index 68ae6a46..c9f3093d 100644 --- a/src/com/android/calendar/DayView.java +++ b/src/com/android/calendar/DayView.java @@ -112,7 +112,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, // duration to show the event clicked private static final int CLICK_DISPLAY_DURATION = 50; - private static final int MENU_AGENDA = 2; private static final int MENU_DAY = 3; private static final int MENU_EVENT_VIEW = 5; private static final int MENU_EVENT_CREATE = 6; @@ -181,8 +180,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, private int[] mEarliestStartHour; // indexed by the week day offset private boolean[] mHasAllDayEvent; // indexed by the week day offset private String mEventCountTemplate; - private final CharSequence[] mLongPressItems; - private String mLongPressTitle; private Event mClickedEvent; // The event the user clicked on private Event mSavedClickedEvent; private static int mOnDownDelay; @@ -570,11 +567,8 @@ public class DayView extends View implements View.OnCreateContextMenuListener, protected Drawable mAcceptedOrTentativeEventBoxDrawable; private String mAmString; private String mPmString; - private final DeleteEventHelper mDeleteEventHelper; private static int sCounter = 0; - private final ContextMenuHandler mContextMenuHandler = new ContextMenuHandler(); - ScaleGestureDetector mScaleGestureDetector; /** @@ -646,7 +640,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, private AccessibilityManager mAccessibilityMgr = null; private boolean mIsAccessibilityEnabled = false; private boolean mTouchExplorationEnabled = false; - private final String mCreateNewEventString; private final String mNewEventHintString; public DayView(Context context, CalendarController controller, @@ -656,7 +649,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, initAccessibilityVariables(); mResources = context.getResources(); - mCreateNewEventString = mResources.getString(R.string.event_create); mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint); mNumDays = numDays; @@ -750,11 +742,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT); mEventGeometry.setHourGap(HOUR_GAP); mEventGeometry.setCellMargin(DAY_GAP); - mLongPressItems = new CharSequence[] { - mResources.getString(R.string.new_event_dialog_option) - }; - mLongPressTitle = mResources.getString(R.string.new_event_dialog_label); - mDeleteEventHelper = new DeleteEventHelper(context, null, false /* don't exit when done */); mLastPopupEventID = INVALID_EVENT_ID; mController = controller; mViewSwitcher = viewSwitcher; @@ -1407,281 +1394,29 @@ public class DayView extends View implements View.OnCreateContextMenuListener, // 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; - long extraLong = 0; - if (mSelectionAllday) { - extraLong = CalendarController.EXTRA_CREATE_ALL_DAY; - } - mController.sendEventRelatedEventWithExtra(this, EventType.CREATE_EVENT, -1, - startMillis, endMillis, -1, -1, extraLong, -1); - } else { + if (selectedEvent != null) { if (mIsAccessibilityEnabled) { mAccessibilityMgr.interrupt(); } - // Switch to the EventInfo view - mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id, - selectedEvent.startMillis, selectedEvent.endMillis, 0, 0, - getSelectedTimeInMillis()); - } - } 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) { - if (mIsAccessibilityEnabled) { - mAccessibilityMgr.interrupt(); - } - mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id, - selectedEvent.startMillis, selectedEvent.endMillis, 0, 0, - getSelectedTimeInMillis()); } } - } 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; - long extraLong = 0; - if (mSelectionAllday) { - extraLong = CalendarController.EXTRA_CREATE_ALL_DAY; - } - mController.sendEventRelatedEventWithExtra(this, EventType.CREATE_EVENT, -1, - startMillis, endMillis, -1, -1, extraLong, -1); - } else { - if (mIsAccessibilityEnabled) { - mAccessibilityMgr.interrupt(); - } - mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id, - selectedEvent.startMillis, selectedEvent.endMillis, 0, 0, - getSelectedTimeInMillis()); - } } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { mScrolling = false; - long duration = event.getEventTime() - event.getDownTime(); - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - if (mSelectionMode == SELECTION_HIDDEN) { - // Don't do anything unless the selection is visible. - break; - } - - if (mSelectionMode == SELECTION_PRESSED) { - // This was the first press when there was nothing selected. - // Change the selection from the "pressed" state to the - // the "selected" state. We treat short-press and - // long-press the same here because nothing was selected. - mSelectionMode = SELECTION_SELECTED; - invalidate(); - break; - } - - // Check the duration to determine if this was a short press - if (duration < ViewConfiguration.getLongPressTimeout()) { - switchViews(true /* trackball */); - } else { - mSelectionMode = SELECTION_LONGPRESS; - invalidate(); - performLongClick(); - } - break; -// case KeyEvent.KEYCODE_BACK: -// if (event.isTracking() && !event.isCanceled()) { -// mPopup.dismiss(); -// mContext.finish(); -// return true; -// } -// break; - } return super.onKeyUp(keyCode, event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mSelectionMode == SELECTION_HIDDEN) { - if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP - || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { - // Display the selection box but don't move or select it - // on this key press. - mSelectionMode = SELECTION_SELECTED; - invalidate(); - return true; - } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { - // Display the selection box but don't select it - // on this key press. - mSelectionMode = SELECTION_PRESSED; - invalidate(); - return true; - } - } - - mSelectionMode = SELECTION_SELECTED; - mScrolling = false; - boolean redraw; - int selectionDay = mSelectionDay; - - switch (keyCode) { - case KeyEvent.KEYCODE_DEL: - // Delete the selected event, if any - Event selectedEvent = mSelectedEvent; - if (selectedEvent == null) { - return false; - } - mPopup.dismiss(); - mLastPopupEventID = INVALID_EVENT_ID; - - long begin = selectedEvent.startMillis; - long end = selectedEvent.endMillis; - long id = selectedEvent.id; - mDeleteEventHelper.delete(begin, end, id, -1); - return true; - case KeyEvent.KEYCODE_ENTER: - switchViews(true /* trackball or keyboard */); - return true; - case KeyEvent.KEYCODE_BACK: - if (event.getRepeatCount() == 0) { - event.startTracking(); - return true; - } - return super.onKeyDown(keyCode, event); - case KeyEvent.KEYCODE_DPAD_LEFT: - if (mSelectedEvent != null) { - setSelectedEvent(mSelectedEvent.nextLeft); - } - if (mSelectedEvent == null) { - mLastPopupEventID = INVALID_EVENT_ID; - selectionDay -= 1; - } - redraw = true; - break; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (mSelectedEvent != null) { - setSelectedEvent(mSelectedEvent.nextRight); - } - if (mSelectedEvent == null) { - mLastPopupEventID = INVALID_EVENT_ID; - selectionDay += 1; - } - redraw = true; - break; - - case KeyEvent.KEYCODE_DPAD_UP: - if (mSelectedEvent != null) { - setSelectedEvent(mSelectedEvent.nextUp); - } - if (mSelectedEvent == null) { - mLastPopupEventID = INVALID_EVENT_ID; - if (!mSelectionAllday) { - setSelectedHour(mSelectionHour - 1); - adjustHourSelection(); - mSelectedEvents.clear(); - mComputeSelectedEvents = true; - } - } - redraw = true; - break; - - case KeyEvent.KEYCODE_DPAD_DOWN: - if (mSelectedEvent != null) { - setSelectedEvent(mSelectedEvent.nextDown); - } - if (mSelectedEvent == null) { - mLastPopupEventID = INVALID_EVENT_ID; - if (mSelectionAllday) { - mSelectionAllday = false; - } else { - setSelectedHour(mSelectionHour + 1); - adjustHourSelection(); - mSelectedEvents.clear(); - mComputeSelectedEvents = true; - } - } - redraw = true; - break; - - default: - return super.onKeyDown(keyCode, event); - } - - if ((selectionDay < mFirstJulianDay) || (selectionDay > mLastJulianDay)) { - DayView view = (DayView) mViewSwitcher.getNextView(); - Time date = view.mBaseDate; - date.set(mBaseDate); - if (selectionDay < mFirstJulianDay) { - date.monthDay -= mNumDays; - } else { - date.monthDay += mNumDays; - } - date.normalize(true /* ignore isDst */); - view.setSelectedDay(selectionDay); - - initView(view); - - Time end = new Time(date); - end.monthDay += mNumDays - 1; - mController.sendEvent(this, EventType.GO_TO, date, end, -1, ViewType.CURRENT); - return true; - } - if (mSelectionDay != selectionDay) { - Time date = new Time(mBaseDate); - date.setJulianDay(selectionDay); - date.hour = mSelectionHour; - mController.sendEvent(this, EventType.GO_TO, date, date, -1, ViewType.CURRENT); - } - setSelectedDay(selectionDay); - mSelectedEvents.clear(); - mComputeSelectedEvents = true; - mUpdateToast = true; - - if (redraw) { - invalidate(); - return true; - } - return super.onKeyDown(keyCode, event); } @Override public boolean onHoverEvent(MotionEvent event) { - if (DEBUG) { - int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - Log.e(TAG, "ACTION_HOVER_ENTER"); - break; - case MotionEvent.ACTION_HOVER_MOVE: - Log.e(TAG, "ACTION_HOVER_MOVE"); - break; - case MotionEvent.ACTION_HOVER_EXIT: - Log.e(TAG, "ACTION_HOVER_EXIT"); - break; - default: - Log.e(TAG, "Unknown hover event action. " + event); - } - } - - // Mouse also generates hover events - // Send accessibility events if accessibility and exploration are on. - if (!mTouchExplorationEnabled) { - return super.onHoverEvent(event); - } - if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) { - setSelectionFromPosition((int) event.getX(), (int) event.getY(), true); - invalidate(); - } return true; } @@ -1744,8 +1479,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, } appendEventAccessibilityString(b, mSelectedEventForAccessibility); } - } else { - b.append(mCreateNewEventString); } } @@ -2218,7 +1951,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, // Draw the fixed areas (that don't scroll) directly to the canvas. drawAfterScroll(canvas); if (mComputeSelectedEvents && mUpdateToast) { - updateEventDetails(); mUpdateToast = false; } mComputeSelectedEvents = false; @@ -2338,17 +2070,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, canvas.drawRect(r, p); } } - - if (mSelectionAllday && mSelectionMode != SELECTION_HIDDEN) { - // Draw the selection highlight on the selected all-day area - mRect.top = DAY_HEADER_HEIGHT + 1; - mRect.bottom = mRect.top + mAlldayHeight + ALLDAY_TOP_MARGIN - 2; - int daynum = mSelectionDay - mFirstJulianDay; - mRect.left = computeDayLeftPosition(daynum) + 1; - mRect.right = computeDayLeftPosition(daynum + 1); - p.setColor(mCalendarGridAreaSelected); - canvas.drawRect(mRect, p); - } } private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) { @@ -2480,52 +2201,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, } p.setAntiAlias(true); p.setAlpha(alpha); - - drawSelectedRect(r, canvas, p); - } - - private void drawSelectedRect(Rect r, Canvas canvas, Paint p) { - // Draw a highlight on the selected hour (if needed) - if (mSelectionMode != SELECTION_HIDDEN && !mSelectionAllday) { - int daynum = mSelectionDay - mFirstJulianDay; - r.top = mSelectionHour * (mCellHeight + HOUR_GAP); - r.bottom = r.top + mCellHeight + HOUR_GAP; - r.left = computeDayLeftPosition(daynum) + 1; - r.right = computeDayLeftPosition(daynum + 1) + 1; - - saveSelectionPosition(r.left, r.top, r.right, r.bottom); - - // Draw the highlight on the grid - p.setColor(mCalendarGridAreaSelected); - r.top += HOUR_GAP; - r.right -= DAY_GAP; - p.setAntiAlias(false); - canvas.drawRect(r, p); - - // Draw a "new event hint" on top of the highlight - // For the week view, show a "+", for day view, show "+ New event" - p.setColor(mNewEventHintColor); - if (mNumDays > 1) { - p.setStrokeWidth(NEW_EVENT_WIDTH); - int width = r.right - r.left; - int midX = r.left + width / 2; - int midY = r.top + mCellHeight / 2; - int length = Math.min(mCellHeight, width) - NEW_EVENT_MARGIN * 2; - length = Math.min(length, NEW_EVENT_MAX_LENGTH); - int verticalPadding = (mCellHeight - length) / 2; - int horizontalPadding = (width - length) / 2; - canvas.drawLine(r.left + horizontalPadding, midY, r.right - horizontalPadding, - midY, p); - canvas.drawLine(midX, r.top + verticalPadding, midX, r.bottom - verticalPadding, p); - } else { - p.setStyle(Paint.Style.FILL); - p.setTextSize(NEW_EVENT_HINT_FONT_SIZE); - p.setTextAlign(Paint.Align.LEFT); - p.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); - canvas.drawText(mNewEventHintString, r.left + EVENT_TEXT_LEFT_MARGIN, - r.top + Math.abs(p.getFontMetrics().ascent) + EVENT_TEXT_TOP_MARGIN , p); - } - } } private void drawHours(Rect r, Canvas canvas, Paint p) { @@ -2757,16 +2432,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, 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 = computeDayLeftPosition(daynum) + 1; - box.right = computeDayLeftPosition(daynum + 1); - return box; - } - private void setupTextRect(Rect r) { if (r.bottom <= r.top || r.right <= r.left) { r.bottom = r.top; @@ -3139,298 +2804,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, - DAY_HEADER_HEIGHT - mAlldayHeight, false); } eventTextPaint.setAlpha(alpha); - - if (date == mSelectionDay && !mSelectionAllday && isFocused() - && mSelectionMode != SELECTION_HIDDEN) { - computeNeighbors(); - } - } - - // Computes the "nearest" neighbor event in four directions (left, right, - // up, down) for each of the events in the mSelectedEvents array. - private void computeNeighbors() { - int len = mSelectedEvents.size(); - if (len == 0 || mSelectedEvent != null) { - return; - } - - // First, clear all the links - for (int ii = 0; ii < len; ii++) { - Event ev = mSelectedEvents.get(ii); - ev.nextUp = null; - ev.nextDown = null; - ev.nextLeft = null; - ev.nextRight = null; - } - - Event startEvent = mSelectedEvents.get(0); - int startEventDistance1 = 100000; // any large number - int startEventDistance2 = 100000; // any large number - int prevLocation = FROM_NONE; - int prevTop; - int prevBottom; - int prevLeft; - int prevRight; - int prevCenter = 0; - Rect box = getCurrentSelectionPosition(); - if (mPrevSelectedEvent != null) { - prevTop = (int) mPrevSelectedEvent.top; - prevBottom = (int) mPrevSelectedEvent.bottom; - prevLeft = (int) mPrevSelectedEvent.left; - prevRight = (int) mPrevSelectedEvent.right; - // Check if the previously selected event intersects the previous - // selection box. (The previously selected event may be from a - // much older selection box.) - if (prevTop >= mPrevBox.bottom || prevBottom <= mPrevBox.top - || prevRight <= mPrevBox.left || prevLeft >= mPrevBox.right) { - mPrevSelectedEvent = null; - prevTop = mPrevBox.top; - prevBottom = mPrevBox.bottom; - prevLeft = mPrevBox.left; - prevRight = mPrevBox.right; - } else { - // Clip the top and bottom to the previous selection box. - if (prevTop < mPrevBox.top) { - prevTop = mPrevBox.top; - } - if (prevBottom > mPrevBox.bottom) { - prevBottom = mPrevBox.bottom; - } - } - } else { - // Just use the previously drawn selection box - prevTop = mPrevBox.top; - prevBottom = mPrevBox.bottom; - prevLeft = mPrevBox.left; - prevRight = mPrevBox.right; - } - - // Figure out where we came from and compute the center of that area. - if (prevLeft >= box.right) { - // The previously selected event was to the right of us. - prevLocation = FROM_RIGHT; - prevCenter = (prevTop + prevBottom) / 2; - } else if (prevRight <= box.left) { - // The previously selected event was to the left of us. - prevLocation = FROM_LEFT; - prevCenter = (prevTop + prevBottom) / 2; - } else if (prevBottom <= box.top) { - // The previously selected event was above us. - prevLocation = FROM_ABOVE; - prevCenter = (prevLeft + prevRight) / 2; - } else if (prevTop >= box.bottom) { - // The previously selected event was below us. - prevLocation = FROM_BELOW; - prevCenter = (prevLeft + prevRight) / 2; - } - - // For each event in the selected event list "mSelectedEvents", search - // all the other events in that list for the nearest neighbor in 4 - // directions. - for (int ii = 0; ii < len; ii++) { - Event ev = mSelectedEvents.get(ii); - - int startTime = ev.startTime; - int endTime = ev.endTime; - int left = (int) ev.left; - int right = (int) ev.right; - int top = (int) ev.top; - if (top < box.top) { - top = box.top; - } - int bottom = (int) ev.bottom; - if (bottom > box.bottom) { - bottom = box.bottom; - } -// if (false) { -// int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL -// | DateUtils.FORMAT_CAP_NOON_MIDNIGHT; -// if (DateFormat.is24HourFormat(mContext)) { -// flags |= DateUtils.FORMAT_24HOUR; -// } -// String timeRange = DateUtils.formatDateRange(mContext, ev.startMillis, -// ev.endMillis, flags); -// Log.i("Cal", "left: " + left + " right: " + right + " top: " + top + " bottom: " -// + bottom + " ev: " + timeRange + " " + ev.title); -// } - int upDistanceMin = 10000; // any large number - int downDistanceMin = 10000; // any large number - int leftDistanceMin = 10000; // any large number - int rightDistanceMin = 10000; // any large number - Event upEvent = null; - Event downEvent = null; - Event leftEvent = null; - Event rightEvent = null; - - // Pick the starting event closest to the previously selected event, - // if any. distance1 takes precedence over distance2. - int distance1 = 0; - int distance2 = 0; - if (prevLocation == FROM_ABOVE) { - if (left >= prevCenter) { - distance1 = left - prevCenter; - } else if (right <= prevCenter) { - distance1 = prevCenter - right; - } - distance2 = top - prevBottom; - } else if (prevLocation == FROM_BELOW) { - if (left >= prevCenter) { - distance1 = left - prevCenter; - } else if (right <= prevCenter) { - distance1 = prevCenter - right; - } - distance2 = prevTop - bottom; - } else if (prevLocation == FROM_LEFT) { - if (bottom <= prevCenter) { - distance1 = prevCenter - bottom; - } else if (top >= prevCenter) { - distance1 = top - prevCenter; - } - distance2 = left - prevRight; - } else if (prevLocation == FROM_RIGHT) { - if (bottom <= prevCenter) { - distance1 = prevCenter - bottom; - } else if (top >= prevCenter) { - distance1 = top - prevCenter; - } - distance2 = prevLeft - right; - } - if (distance1 < startEventDistance1 - || (distance1 == startEventDistance1 && distance2 < startEventDistance2)) { - startEvent = ev; - startEventDistance1 = distance1; - startEventDistance2 = distance2; - } - - // For each neighbor, figure out if it is above or below or left - // or right of me and compute the distance. - for (int jj = 0; jj < len; jj++) { - if (jj == ii) { - continue; - } - Event neighbor = mSelectedEvents.get(jj); - int neighborLeft = (int) neighbor.left; - int neighborRight = (int) neighbor.right; - if (neighbor.endTime <= startTime) { - // This neighbor is entirely above me. - // If we overlap the same column, then compute the distance. - if (neighborLeft < right && neighborRight > left) { - int distance = startTime - neighbor.endTime; - if (distance < upDistanceMin) { - upDistanceMin = distance; - upEvent = neighbor; - } else if (distance == upDistanceMin) { - int center = (left + right) / 2; - int currentDistance = 0; - int currentLeft = (int) upEvent.left; - int currentRight = (int) upEvent.right; - if (currentRight <= center) { - currentDistance = center - currentRight; - } else if (currentLeft >= center) { - currentDistance = currentLeft - center; - } - - int neighborDistance = 0; - if (neighborRight <= center) { - neighborDistance = center - neighborRight; - } else if (neighborLeft >= center) { - neighborDistance = neighborLeft - center; - } - if (neighborDistance < currentDistance) { - upDistanceMin = distance; - upEvent = neighbor; - } - } - } - } else if (neighbor.startTime >= endTime) { - // This neighbor is entirely below me. - // If we overlap the same column, then compute the distance. - if (neighborLeft < right && neighborRight > left) { - int distance = neighbor.startTime - endTime; - if (distance < downDistanceMin) { - downDistanceMin = distance; - downEvent = neighbor; - } else if (distance == downDistanceMin) { - int center = (left + right) / 2; - int currentDistance = 0; - int currentLeft = (int) downEvent.left; - int currentRight = (int) downEvent.right; - if (currentRight <= center) { - currentDistance = center - currentRight; - } else if (currentLeft >= center) { - currentDistance = currentLeft - center; - } - - int neighborDistance = 0; - if (neighborRight <= center) { - neighborDistance = center - neighborRight; - } else if (neighborLeft >= center) { - neighborDistance = neighborLeft - center; - } - if (neighborDistance < currentDistance) { - downDistanceMin = distance; - downEvent = neighbor; - } - } - } - } - - if (neighborLeft >= right) { - // This neighbor is entirely to the right of me. - // Take the closest neighbor in the y direction. - int center = (top + bottom) / 2; - int distance = 0; - int neighborBottom = (int) neighbor.bottom; - int neighborTop = (int) neighbor.top; - if (neighborBottom <= center) { - distance = center - neighborBottom; - } else if (neighborTop >= center) { - distance = neighborTop - center; - } - if (distance < rightDistanceMin) { - rightDistanceMin = distance; - rightEvent = neighbor; - } else if (distance == rightDistanceMin) { - // Pick the closest in the x direction - int neighborDistance = neighborLeft - right; - int currentDistance = (int) rightEvent.left - right; - if (neighborDistance < currentDistance) { - rightDistanceMin = distance; - rightEvent = neighbor; - } - } - } else if (neighborRight <= left) { - // This neighbor is entirely to the left of me. - // Take the closest neighbor in the y direction. - int center = (top + bottom) / 2; - int distance = 0; - int neighborBottom = (int) neighbor.bottom; - int neighborTop = (int) neighbor.top; - if (neighborBottom <= center) { - distance = center - neighborBottom; - } else if (neighborTop >= center) { - distance = neighborTop - center; - } - if (distance < leftDistanceMin) { - leftDistanceMin = distance; - leftEvent = neighbor; - } else if (distance == leftDistanceMin) { - // Pick the closest in the x direction - int neighborDistance = left - neighborRight; - int currentDistance = left - (int) leftEvent.right; - if (neighborDistance < currentDistance) { - leftDistanceMin = distance; - leftEvent = neighbor; - } - } - } - } - ev.nextUp = upEvent; - ev.nextDown = downEvent; - ev.nextLeft = leftEvent; - ev.nextRight = rightEvent; - } - setSelectedEvent(startEvent); } private Rect drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint, @@ -3488,18 +2861,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, if (mSelectedEvent == event && mClickedEvent != null) { boolean paintIt = false; color = 0; - if (mSelectionMode == SELECTION_PRESSED) { - // Also, remember the last selected event that we drew - mPrevSelectedEvent = event; - color = mPressedColor; - paintIt = true; - } else if (mSelectionMode == SELECTION_SELECTED) { - // Also, remember the last selected event that we drew - mPrevSelectedEvent = event; - color = mPressedColor; - paintIt = true; - } - if (paintIt) { p.setColor(color); canvas.drawRect(r, p); @@ -3507,22 +2868,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, p.setAntiAlias(true); } - // Draw cal color square border - // r.top = (int) event.top + CALENDAR_COLOR_SQUARE_V_OFFSET; - // r.left = (int) event.left + CALENDAR_COLOR_SQUARE_H_OFFSET; - // r.bottom = r.top + CALENDAR_COLOR_SQUARE_SIZE + 1; - // r.right = r.left + CALENDAR_COLOR_SQUARE_SIZE + 1; - // p.setColor(0xFFFFFFFF); - // canvas.drawRect(r, p); - - // Draw cal color - // r.top++; - // r.left++; - // r.bottom--; - // r.right--; - // p.setColor(event.color); - // canvas.drawRect(r, p); - // Setup rect for drawEventText which follows r.top = (int) event.top + EVENT_RECT_TOP_MARGIN; r.bottom = (int) event.bottom - EVENT_RECT_BOTTOM_MARGIN; @@ -3600,84 +2945,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, canvas.restore(); } - // This is to replace p.setStyle(Style.STROKE); canvas.drawRect() since it - // doesn't work well with hardware acceleration -// private void drawEmptyRect(Canvas canvas, Rect r, int color) { -// int linesIndex = 0; -// mLines[linesIndex++] = r.left; -// mLines[linesIndex++] = r.top; -// mLines[linesIndex++] = r.right; -// mLines[linesIndex++] = r.top; -// -// mLines[linesIndex++] = r.left; -// mLines[linesIndex++] = r.bottom; -// mLines[linesIndex++] = r.right; -// mLines[linesIndex++] = r.bottom; -// -// mLines[linesIndex++] = r.left; -// mLines[linesIndex++] = r.top; -// mLines[linesIndex++] = r.left; -// mLines[linesIndex++] = r.bottom; -// -// mLines[linesIndex++] = r.right; -// mLines[linesIndex++] = r.top; -// mLines[linesIndex++] = r.right; -// mLines[linesIndex++] = r.bottom; -// mPaint.setColor(color); -// canvas.drawLines(mLines, 0, linesIndex, mPaint); -// } - - private void updateEventDetails() { - if (mSelectedEvent == null || mSelectionMode == SELECTION_HIDDEN - || mSelectionMode == SELECTION_LONGPRESS) { - mPopup.dismiss(); - return; - } - if (mLastPopupEventID == mSelectedEvent.id) { - return; - } - - mLastPopupEventID = mSelectedEvent.id; - - // Remove any outstanding callbacks to dismiss the popup. - mHandler.removeCallbacks(mDismissPopup); - - Event event = mSelectedEvent; - TextView titleView = (TextView) mPopupView.findViewById(R.id.event_title); - titleView.setText(event.title); - - ImageView imageView = (ImageView) mPopupView.findViewById(R.id.reminder_icon); - imageView.setVisibility(event.hasAlarm ? View.VISIBLE : View.GONE); - - imageView = (ImageView) mPopupView.findViewById(R.id.repeat_icon); - imageView.setVisibility(event.isRepeating ? View.VISIBLE : View.GONE); - - int flags; - if (event.allDay) { - flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL; - } else { - flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL - | DateUtils.FORMAT_CAP_NOON_MIDNIGHT; - } - if (DateFormat.is24HourFormat(mContext)) { - flags |= DateUtils.FORMAT_24HOUR; - } - String timeRange = Utils.formatDateRange(mContext, event.startMillis, event.endMillis, - flags); - TextView timeView = (TextView) mPopupView.findViewById(R.id.time); - timeView.setText(timeRange); - - TextView whereView = (TextView) mPopupView.findViewById(R.id.where); - final boolean empty = TextUtils.isEmpty(event.location); - whereView.setVisibility(empty ? View.GONE : View.VISIBLE); - if (!empty) whereView.setText(event.location); - - mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, mHoursWidth, 5); - mHandler.postDelayed(mDismissPopup, POPUP_DISMISS_DELAY); - } - // The following routines are called from the parent activity when certain // touch events occur. private void doDown(MotionEvent ev) { @@ -3891,18 +3158,7 @@ public class DayView extends View implements View.OnCreateContextMenuListener, boolean pressedSelected = (hasSelection || mTouchExplorationEnabled) && selectedDay == mSelectionDay && selectedHour == mSelectionHour; - if (pressedSelected && mSavedClickedEvent == null) { - // If the tap is on an already selected hour slot, then create a new - // event - long extraLong = 0; - if (mSelectionAllday) { - extraLong = CalendarController.EXTRA_CREATE_ALL_DAY; - } - mSelectionMode = SELECTION_SELECTED; - mController.sendEventRelatedEventWithExtra(this, EventType.CREATE_EVENT, -1, - getSelectedTimeInMillis(), 0, (int) ev.getRawX(), (int) ev.getRawY(), - extraLong, -1); - } else if (mSelectedEvent != null) { + if (mSelectedEvent != null) { // If the tap is on an event, launch the "View event" view if (mIsAccessibilityEnabled) { mAccessibilityMgr.interrupt(); @@ -3925,19 +3181,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, } else { this.post(mClearClick); } - } else { - // Select time - Time startTime = new Time(mBaseDate); - startTime.setJulianDay(mSelectionDay); - startTime.hour = mSelectionHour; - startTime.normalize(true /* ignore isDst */); - - Time endTime = new Time(startTime); - endTime.hour++; - - mSelectionMode = SELECTION_SELECTED; - mController.sendEvent(this, EventType.GO_TO, startTime, endTime, -1, ViewType.CURRENT, - CalendarController.EXTRA_GOTO_TIME, null, null); } invalidate(); } @@ -3962,7 +3205,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, return; } - mSelectionMode = SELECTION_LONGPRESS; invalidate(); performLongClick(); } @@ -4341,7 +3583,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, // we never get onKeyUp() for the long-press. So check for it here // and change the selection to the long-press state. if (mSelectionMode != SELECTION_LONGPRESS) { - mSelectionMode = SELECTION_LONGPRESS; invalidate(); } @@ -4352,191 +3593,9 @@ public class DayView extends View implements View.OnCreateContextMenuListener, final String title = Utils.formatDateRange(mContext, startMillis, startMillis, flags); menu.setHeaderTitle(title); - int numSelectedEvents = mSelectedEvents.size(); - if (mNumDays == 1) { - // Day view. - - // If there is a selected event, then allow it to be viewed and - // edited. - if (numSelectedEvents >= 1) { - item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_info_details); - - int accessLevel = getEventAccessLevel(mContext, mSelectedEvent); - if (accessLevel == ACCESS_LEVEL_EDIT) { - item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_edit); - item.setAlphabeticShortcut('e'); - } - - if (accessLevel >= ACCESS_LEVEL_DELETE) { - item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_delete); - } - - item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_add); - item.setAlphabeticShortcut('n'); - } else { - // Otherwise, if the user long-pressed on a blank hour, allow - // them to create an event. They can also do this by tapping. - item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_add); - item.setAlphabeticShortcut('n'); - } - } else { - // Week view. - - // If there is a selected event, then allow it to be viewed and - // edited. - if (numSelectedEvents >= 1) { - item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_info_details); - - int accessLevel = getEventAccessLevel(mContext, mSelectedEvent); - if (accessLevel == ACCESS_LEVEL_EDIT) { - item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_edit); - item.setAlphabeticShortcut('e'); - } - - if (accessLevel >= ACCESS_LEVEL_DELETE) { - item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_delete); - } - } - - item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_add); - item.setAlphabeticShortcut('n'); - - item = menu.add(0, MENU_DAY, 0, R.string.show_day_view); - item.setOnMenuItemClickListener(mContextMenuHandler); - item.setIcon(android.R.drawable.ic_menu_day); - item.setAlphabeticShortcut('d'); - } - mPopup.dismiss(); } - private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener { - - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case MENU_EVENT_VIEW: { - if (mSelectedEvent != null) { - mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT_DETAILS, - mSelectedEvent.id, mSelectedEvent.startMillis, - mSelectedEvent.endMillis, 0, 0, -1); - } - break; - } - case MENU_EVENT_EDIT: { - if (mSelectedEvent != null) { - mController.sendEventRelatedEvent(this, EventType.EDIT_EVENT, - mSelectedEvent.id, mSelectedEvent.startMillis, - mSelectedEvent.endMillis, 0, 0, -1); - } - break; - } - case MENU_DAY: { - mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1, - ViewType.DAY); - break; - } - case MENU_AGENDA: { - mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1, - ViewType.AGENDA); - break; - } - case MENU_EVENT_CREATE: { - long startMillis = getSelectedTimeInMillis(); - long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS; - mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1, - startMillis, endMillis, 0, 0, -1); - break; - } - case MENU_EVENT_DELETE: { - if (mSelectedEvent != null) { - Event selectedEvent = mSelectedEvent; - long begin = selectedEvent.startMillis; - long end = selectedEvent.endMillis; - long id = selectedEvent.id; - mController.sendEventRelatedEvent(this, EventType.DELETE_EVENT, id, begin, - end, 0, 0, -1); - } - break; - } - default: { - return false; - } - } - return true; - } - } - - private static int getEventAccessLevel(Context context, Event e) { - ContentResolver cr = context.getContentResolver(); - - int accessLevel = Calendars.CAL_ACCESS_NONE; - - // Get the calendar id for this event - Cursor cursor = cr.query(ContentUris.withAppendedId(Events.CONTENT_URI, e.id), - new String[] { Events.CALENDAR_ID }, - null /* selection */, - null /* selectionArgs */, - null /* sort */); - - if (cursor == null) { - return ACCESS_LEVEL_NONE; - } - - if (cursor.getCount() == 0) { - cursor.close(); - return ACCESS_LEVEL_NONE; - } - - cursor.moveToFirst(); - long calId = cursor.getLong(0); - cursor.close(); - - Uri uri = Calendars.CONTENT_URI; - String where = String.format(CALENDARS_WHERE, calId); - cursor = cr.query(uri, CALENDARS_PROJECTION, where, null, null); - - String calendarOwnerAccount = null; - if (cursor != null) { - cursor.moveToFirst(); - accessLevel = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL); - calendarOwnerAccount = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); - cursor.close(); - } - - if (accessLevel < Calendars.CAL_ACCESS_CONTRIBUTOR) { - return ACCESS_LEVEL_NONE; - } - - if (e.guestsCanModify) { - return ACCESS_LEVEL_EDIT; - } - - if (!TextUtils.isEmpty(calendarOwnerAccount) - && calendarOwnerAccount.equalsIgnoreCase(e.organizer)) { - return ACCESS_LEVEL_EDIT; - } - - return ACCESS_LEVEL_DELETE; - } - /** * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position. * If the touch position is not within the displayed grid, then this @@ -4598,20 +3657,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, findSelectedEvent(x, y); -// Log.i("Cal", "setSelectionFromPosition( " + x + ", " + y + " ) day: " + day + " hour: " -// + mSelectionHour + " mFirstCell: " + mFirstCell + " mFirstHourOffset: " -// + mFirstHourOffset); -// if (mSelectedEvent != null) { -// Log.i("Cal", " num events: " + mSelectedEvents.size() + " event: " -// + mSelectedEvent.title); -// for (Event ev : mSelectedEvents) { -// int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL -// | DateUtils.FORMAT_CAP_NOON_MIDNIGHT; -// String timeRange = formatDateRange(mContext, ev.startMillis, ev.endMillis, flags); -// -// Log.i("Cal", " " + timeRange + " " + ev.title); -// } -// } sendAccessibilityEventAsNeeded(true); // Restore old values @@ -4945,30 +3990,6 @@ public class DayView extends View implements View.OnCreateContextMenuListener, @Override public boolean onLongClick(View v) { - int flags = DateUtils.FORMAT_SHOW_WEEKDAY; - long time = getSelectedTimeInMillis(); - if (!mSelectionAllday) { - flags |= DateUtils.FORMAT_SHOW_TIME; - } - if (DateFormat.is24HourFormat(mContext)) { - flags |= DateUtils.FORMAT_24HOUR; - } - mLongPressTitle = Utils.formatDateRange(mContext, time, time, flags); - new AlertDialog.Builder(mContext).setTitle(mLongPressTitle) - .setItems(mLongPressItems, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - long extraLong = 0; - if (mSelectionAllday) { - extraLong = CalendarController.EXTRA_CREATE_ALL_DAY; - } - mController.sendEventRelatedEventWithExtra(this, - EventType.CREATE_EVENT, -1, getSelectedTimeInMillis(), 0, -1, - -1, extraLong, -1); - } - } - }).show().setCanceledOnTouchOutside(true); return true; } diff --git a/src/com/android/calendar/DeleteEventHelper.java b/src/com/android/calendar/DeleteEventHelper.java deleted file mode 100644 index c436f5a1..00000000 --- a/src/com/android/calendar/DeleteEventHelper.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.event.EditEventHelper; -import com.android.calendarcommon2.EventRecurrence; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Events; -import android.text.TextUtils; -import android.text.format.Time; -import android.widget.ArrayAdapter; -import android.widget.Button; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * A helper class for deleting events. If a normal event is selected for - * deletion, then this pops up a confirmation dialog. If the user confirms, - * then the normal event is deleted. - * - * <p> - * If a repeating event is selected for deletion, then this pops up dialog - * asking if the user wants to delete just this one instance, or all the - * events in the series, or this event plus all following events. The user - * may also cancel the delete. - * </p> - * - * <p> - * To use this class, create an instance, passing in the parent activity - * and a boolean that determines if the parent activity should exit if the - * event is deleted. Then to use the instance, call one of the - * {@link delete()} methods on this class. - * - * An instance of this class may be created once and reused (by calling - * {@link #delete()} multiple times). - */ -public class DeleteEventHelper { - private final Activity mParent; - private Context mContext; - - private long mStartMillis; - private long mEndMillis; - private CalendarEventModel mModel; - - /** - * If true, then call finish() on the parent activity when done. - */ - private boolean mExitWhenDone; - // the runnable to execute when the delete is confirmed - private Runnable mCallback; - - /** - * These are the corresponding indices into the array of strings - * "R.array.delete_repeating_labels" in the resource file. - */ - public static final int DELETE_SELECTED = 0; - public static final int DELETE_ALL_FOLLOWING = 1; - public static final int DELETE_ALL = 2; - - private int mWhichDelete; - private ArrayList<Integer> mWhichIndex; - private AlertDialog mAlertDialog; - private Dialog.OnDismissListener mDismissListener; - - private String mSyncId; - - private AsyncQueryService mService; - - private DeleteNotifyListener mDeleteStartedListener = null; - - public interface DeleteNotifyListener { - public void onDeleteStarted(); - } - - - public DeleteEventHelper(Context context, Activity parentActivity, boolean exitWhenDone) { - if (exitWhenDone && parentActivity == null) { - throw new IllegalArgumentException("parentActivity is required to exit when done"); - } - - mContext = context; - mParent = parentActivity; - // TODO move the creation of this service out into the activity. - mService = new AsyncQueryService(mContext) { - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (cursor == null) { - return; - } - cursor.moveToFirst(); - CalendarEventModel mModel = new CalendarEventModel(); - EditEventHelper.setModelFromCursor(mModel, cursor); - cursor.close(); - DeleteEventHelper.this.delete(mStartMillis, mEndMillis, mModel, mWhichDelete); - } - }; - mExitWhenDone = exitWhenDone; - } - - public void 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) { - deleteStarted(); - long id = mModel.mId; // mCursor.getInt(mEventIndexId); - Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, id); - mService.startDelete(mService.getNextToken(), null, uri, null, null, Utils.UNDO_DELAY); - if (mCallback != null) { - mCallback.run(); - } - if (mExitWhenDone) { - mParent.finish(); - } - } - }; - - /** - * This callback is used when an exception to an event is deleted - */ - private DialogInterface.OnClickListener mDeleteExceptionDialogListener = - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int button) { - deleteStarted(); - deleteExceptionEvent(); - if (mCallback != null) { - mCallback.run(); - } - 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) { - // set mWhichDelete to the delete type at that index - mWhichDelete = mWhichIndex.get(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) { - deleteStarted(); - 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(CalendarContract.Events.CONTENT_URI, eventId); - mService.startQuery(mService.getNextToken(), null, uri, EditEventHelper.EVENT_PROJECTION, - null, null, null); - mStartMillis = begin; - mEndMillis = end; - mWhichDelete = which; - } - - public void delete(long begin, long end, long eventId, int which, Runnable callback) { - delete(begin, end, eventId, which); - mCallback = callback; - } - - /** - * Does the required processing for deleting an event. This method - * takes a {@link CalendarEventModel} object, which must have a valid - * uri for referencing the event in the database and have the required - * fields listed below. - * The required fields for a normal event are: - * - * <ul> - * <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> - * - * If the event no longer exists in the db this will still prompt - * the user but will return without modifying the db after the query - * returns. - * - * @param begin the begin time of the event, in UTC milliseconds - * @param end the end time of the event, in UTC milliseconds - * @param cursor the database cursor containing the required fields - * @param which one of the values {@link DELETE_SELECTED}, - * {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1 - */ - public void delete(long begin, long end, CalendarEventModel model, int which) { - mWhichDelete = which; - mStartMillis = begin; - mEndMillis = end; - mModel = model; - mSyncId = model.mSyncId; - - // If this is a repeating event, then pop up a dialog asking the - // user if they want to delete all of the repeating events or - // just some of them. - String rRule = model.mRrule; - String originalEvent = model.mOriginalSyncId; - if (TextUtils.isEmpty(rRule)) { - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setMessage(R.string.delete_this_event_title) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setNegativeButton(android.R.string.cancel, null).create(); - - if (originalEvent == null) { - // This is a normal event. Pop up a confirmation dialog. - dialog.setButton(DialogInterface.BUTTON_POSITIVE, - mContext.getText(android.R.string.ok), - mDeleteNormalDialogListener); - } else { - // This is an exception event. Pop up a confirmation dialog. - dialog.setButton(DialogInterface.BUTTON_POSITIVE, - mContext.getText(android.R.string.ok), - mDeleteExceptionDialogListener); - } - dialog.setOnDismissListener(mDismissListener); - dialog.show(); - mAlertDialog = dialog; - } else { - // This is a repeating event. Pop up a dialog asking which events - // to delete. - Resources res = mContext.getResources(); - ArrayList<String> labelArray = new ArrayList<String>(Arrays.asList(res - .getStringArray(R.array.delete_repeating_labels))); - // asList doesn't like int[] so creating it manually. - int[] labelValues = res.getIntArray(R.array.delete_repeating_values); - ArrayList<Integer> labelIndex = new ArrayList<Integer>(); - for (int val : labelValues) { - labelIndex.add(val); - } - - if (mSyncId == null) { - // remove 'Only this event' item - labelArray.remove(0); - labelIndex.remove(0); - if (!model.mIsOrganizer) { - // remove 'This and future events' item - labelArray.remove(0); - labelIndex.remove(0); - } - } else if (!model.mIsOrganizer) { - // remove 'This and future events' item - labelArray.remove(1); - labelIndex.remove(1); - } - if (which != -1) { - // transform the which to the index in the array - which = labelIndex.indexOf(which); - } - mWhichIndex = labelIndex; - ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext, - android.R.layout.simple_list_item_single_choice, labelArray); - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle( - mContext.getString(R.string.delete_recurring_event_title,model.mTitle)) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setSingleChoiceItems(adapter, which, mDeleteListListener) - .setPositiveButton(android.R.string.ok, mDeleteRepeatingDialogListener) - .setNegativeButton(android.R.string.cancel, null).show(); - dialog.setOnDismissListener(mDismissListener); - mAlertDialog = dialog; - - if (which == -1) { - // Disable the "Ok" button until the user selects which events - // to delete. - Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - ok.setEnabled(false); - } - } - } - - private void deleteExceptionEvent() { - long id = mModel.mId; // mCursor.getInt(mEventIndexId); - - // update a recurrence exception by setting its status to "canceled" - ContentValues values = new ContentValues(); - values.put(Events.STATUS, Events.STATUS_CANCELED); - - Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, id); - mService.startUpdate(mService.getNextToken(), null, uri, values, null, null, - Utils.UNDO_DELAY); - } - - private void deleteRepeatingEvent(int which) { - String rRule = mModel.mRrule; - boolean allDay = mModel.mAllDay; - long dtstart = mModel.mStart; - long id = mModel.mId; // mCursor.getInt(mEventIndexId); - - 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 = mModel.mTitle; - values.put(Events.TITLE, title); - - String timezone = mModel.mTimezone; - long calendarId = mModel.mCalendarId; - values.put(Events.EVENT_TIMEZONE, timezone); - values.put(Events.ALL_DAY, allDay ? 1 : 0); - values.put(Events.ORIGINAL_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_SYNC_ID, mSyncId); - values.put(Events.ORIGINAL_ID, id); - values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis); - values.put(Events.STATUS, Events.STATUS_CANCELED); - - mService.startInsert(mService.getNextToken(), null, Events.CONTENT_URI, values, - Utils.UNDO_DELAY); - break; - } - case DELETE_ALL: { - Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, id); - mService.startDelete(mService.getNextToken(), null, uri, null, null, - Utils.UNDO_DELAY); - 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(CalendarContract.Events.CONTENT_URI, id); - mService.startDelete(mService.getNextToken(), null, uri, null, null, - Utils.UNDO_DELAY); - 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(CalendarContract.Events.CONTENT_URI, id); - mService.startUpdate(mService.getNextToken(), null, uri, values, null, null, - Utils.UNDO_DELAY); - break; - } - } - if (mCallback != null) { - mCallback.run(); - } - if (mExitWhenDone) { - mParent.finish(); - } - } - - public void setDeleteNotificationListener(DeleteNotifyListener listener) { - mDeleteStartedListener = listener; - } - - private void deleteStarted() { - if (mDeleteStartedListener != null) { - mDeleteStartedListener.onDeleteStarted(); - } - } - - public void setOnDismissListener(Dialog.OnDismissListener listener) { - if (mAlertDialog != null) { - mAlertDialog.setOnDismissListener(listener); - } - mDismissListener = listener; - } - - public void dismissAlertDialog() { - if (mAlertDialog != null) { - mAlertDialog.dismiss(); - } - } -} diff --git a/src/com/android/calendar/EditResponseHelper.java b/src/com/android/calendar/EditResponseHelper.java deleted file mode 100644 index 1f32ce01..00000000 --- a/src/com/android/calendar/EditResponseHelper.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.content.DialogInterface.OnDismissListener; -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, OnDismissListener { - private final Activity mParent; - private int mWhichEvents = -1; - private AlertDialog mAlertDialog; - private boolean mClickedOk = false; - - /** - * 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; - } - - /** - * @return whichEvents, representing which events were selected on which to - * apply the response: - * -1 means no choice selected, or the dialog was - * canceled. - * 0 means just the single event. - * 1 means all events. - */ - public int getWhichEvents() { - return mWhichEvents; - } - - public void setWhichEvents(int which) { - mWhichEvents = which; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - setClickedOk(true); - } - - @Override - public void onDismiss(DialogInterface dialog) { - // If the click was not "OK", clear out whichEvents to represent - // that the dialog was canceled. - if (!getClickedOk()) { - setWhichEvents(-1); - } - setClickedOk(false); - - // Call the pre-set dismiss listener too. - if (mDismissListener != null) { - mDismissListener.onDismiss(dialog); - } - - } - - private boolean getClickedOk() { - return mClickedOk; - } - - private void setClickedOk(boolean clickedOk) { - mClickedOk = clickedOk; - } - - /** - * 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); - } - }; - - private DialogInterface.OnDismissListener mDismissListener; - - - /** - * Set the dismiss listener to be called when the dialog is ended. There, - * use getWhichEvents() to see how the dialog was dismissed; if it returns - * -1, the dialog was canceled out. If it is not -1, it's the index of - * which events the user wants to respond to. - * @param onDismissListener - */ - public void setDismissListener(OnDismissListener onDismissListener) { - mDismissListener = onDismissListener; - } - - 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).setIconAttribute(android.R.attr.alertDialogIcon) - .setSingleChoiceItems(R.array.change_response_labels, whichEvents, mListListener) - .setPositiveButton(android.R.string.ok, mDialogListener) - .setNegativeButton(android.R.string.cancel, null).show(); - // The caller may set a dismiss listener to hear back when the dialog is - // finished. Use getWhichEvents() to see how the dialog was dismissed. - dialog.setOnDismissListener(this); - 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); - } - } - - public void dismissAlertDialog() { - if (mAlertDialog != null) { - mAlertDialog.dismiss(); - } - } - -} diff --git a/src/com/android/calendar/EmailAddressAdapter.java b/src/com/android/calendar/EmailAddressAdapter.java deleted file mode 100644 index 1f8519bb..00000000 --- a/src/com/android/calendar/EmailAddressAdapter.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.common.contacts.BaseEmailAddressAdapter; -import com.android.ex.chips.AccountSpecifier; - -import android.content.Context; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -/** -* An adaptation of {@link BaseEmailAddressAdapter} for the Email app. The main -* purpose of the class is to bind the generic implementation to the resources -* defined locally: strings and layouts. -*/ -public class EmailAddressAdapter extends BaseEmailAddressAdapter implements AccountSpecifier { - - private LayoutInflater mInflater; - - public EmailAddressAdapter(Context context) { - super(context); - mInflater = LayoutInflater.from(context); - } - - @Override - protected View inflateItemView(ViewGroup parent) { - return mInflater.inflate(R.layout.email_autocomplete_item, parent, false); - } - - @Override - protected View inflateItemViewLoading(ViewGroup parent) { - return mInflater.inflate(R.layout.email_autocomplete_item_loading, parent, false); - } - - @Override - protected void bindView(View view, String directoryType, String directoryName, - String displayName, String emailAddress) { - TextView text1 = (TextView)view.findViewById(R.id.text1); - TextView text2 = (TextView)view.findViewById(R.id.text2); - text1.setText(displayName); - text2.setText(emailAddress); - } - - @Override - protected void bindViewLoading(View view, String directoryType, String directoryName) { - TextView text1 = (TextView)view.findViewById(R.id.text1); - String text = getContext().getString(R.string.directory_searching_fmt, - TextUtils.isEmpty(directoryName) ? directoryType : directoryName); - text1.setText(text); - } -} diff --git a/src/com/android/calendar/EventInfoActivity.java b/src/com/android/calendar/EventInfoActivity.java index 9b136bd0..626e099d 100644 --- a/src/com/android/calendar/EventInfoActivity.java +++ b/src/com/android/calendar/EventInfoActivity.java @@ -34,15 +34,10 @@ import android.provider.CalendarContract.Attendees; import android.util.Log; import android.widget.Toast; -import com.android.calendar.CalendarEventModel.ReminderEntry; - import java.util.ArrayList; import java.util.List; public class EventInfoActivity extends Activity { -// implements CalendarController.EventHandler, SearchView.OnQueryTextListener, -// SearchView.OnCloseListener { - private static final String TAG = "EventInfoActivity"; private EventInfoFragment mInfoFragment; private long mStartMillis, mEndMillis; @@ -74,7 +69,6 @@ public class EventInfoActivity extends Activity { int attendeeResponse = 0; mEventId = -1; boolean isDialog = false; - ArrayList<ReminderEntry> reminders = null; if (icicle != null) { mEventId = icicle.getLong(EventInfoFragment.BUNDLE_KEY_EVENT_ID); @@ -82,8 +76,6 @@ public class EventInfoActivity extends Activity { mEndMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_END_MILLIS); attendeeResponse = icicle.getInt(EventInfoFragment.BUNDLE_KEY_ATTENDEE_RESPONSE); isDialog = icicle.getBoolean(EventInfoFragment.BUNDLE_KEY_IS_DIALOG); - - reminders = Utils.readRemindersFromBundle(icicle); } else if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { mStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0); mEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0); @@ -155,8 +147,7 @@ public class EventInfoActivity extends Activity { mInfoFragment = new EventInfoFragment(this, mEventId, mStartMillis, mEndMillis, attendeeResponse, isDialog, (isDialog ? EventInfoFragment.DIALOG_WINDOW_STYLE : - EventInfoFragment.FULL_WINDOW_STYLE), - reminders); + EventInfoFragment.FULL_WINDOW_STYLE)); ft.replace(R.id.main_frame, mInfoFragment); ft.commit(); } diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java index e73482cf..b95c7c3d 100644 --- a/src/com/android/calendar/EventInfoFragment.java +++ b/src/com/android/calendar/EventInfoFragment.java @@ -51,7 +51,6 @@ import android.os.Bundle; import android.provider.CalendarContract; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Colors; import android.provider.CalendarContract.Events; import android.provider.CalendarContract.Reminders; import android.provider.ContactsContract; @@ -95,18 +94,10 @@ import android.widget.Toast; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarEventModel.Attendee; -import com.android.calendar.CalendarEventModel.ReminderEntry; import com.android.calendar.alerts.QuickResponseActivity; -import com.android.calendar.event.AttendeesView; -import com.android.calendar.event.EditEventActivity; -import com.android.calendar.event.EditEventHelper; -import com.android.calendar.event.EventColorPickerDialog; -import com.android.calendar.event.EventViewUtils; import com.android.calendarcommon2.DateException; import com.android.calendarcommon2.Duration; import com.android.calendarcommon2.EventRecurrence; -import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; import com.android.colorpicker.HsvColorComparator; import java.util.ArrayList; @@ -115,15 +106,11 @@ import java.util.Collections; import java.util.List; public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener, - CalendarController.EventHandler, OnClickListener, DeleteEventHelper.DeleteNotifyListener, - OnColorSelectedListener { + CalendarController.EventHandler, OnClickListener { public static final boolean DEBUG = false; public static final String TAG = "EventInfoFragment"; - public static final String COLOR_PICKER_DIALOG_TAG = "EventColorPickerDialog"; - - private static final int REQUEST_CODE_COLOR_PICKER = 0; protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id"; protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis"; @@ -228,49 +215,13 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private static final int EVENT_INDEX_DTEND = 20; private static final int EVENT_INDEX_DURATION = 21; - private static final String[] ATTENDEES_PROJECTION = new String[] { - Attendees._ID, // 0 - Attendees.ATTENDEE_NAME, // 1 - Attendees.ATTENDEE_EMAIL, // 2 - Attendees.ATTENDEE_RELATIONSHIP, // 3 - Attendees.ATTENDEE_STATUS, // 4 - Attendees.ATTENDEE_IDENTITY, // 5 - Attendees.ATTENDEE_ID_NAMESPACE // 6 - }; - private static final int ATTENDEES_INDEX_ID = 0; - private static final int ATTENDEES_INDEX_NAME = 1; - private static final int ATTENDEES_INDEX_EMAIL = 2; - private static final int ATTENDEES_INDEX_RELATIONSHIP = 3; - private static final int ATTENDEES_INDEX_STATUS = 4; - private static final int ATTENDEES_INDEX_IDENTITY = 5; - private static final int ATTENDEES_INDEX_ID_NAMESPACE = 6; - static { if (!Utils.isJellybeanOrLater()) { EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // dummy value EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // dummy value - - ATTENDEES_PROJECTION[ATTENDEES_INDEX_IDENTITY] = Attendees._ID; // dummy value - ATTENDEES_PROJECTION[ATTENDEES_INDEX_ID_NAMESPACE] = Attendees._ID; // dummy value } } - private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; - - private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " - + Attendees.ATTENDEE_EMAIL + " ASC"; - - private static final String[] REMINDERS_PROJECTION = new String[] { - Reminders._ID, // 0 - Reminders.MINUTES, // 1 - Reminders.METHOD // 2 - }; - private static final int REMINDERS_INDEX_ID = 0; - private static final int REMINDERS_MINUTES_ID = 1; - private static final int REMINDERS_METHOD_ID = 2; - - private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?"; - static final String[] CALENDARS_PROJECTION = new String[] { Calendars._ID, // 0 Calendars.CALENDAR_DISPLAY_NAME, // 1 @@ -289,15 +240,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?"; static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?"; - static final String[] COLORS_PROJECTION = new String[] { - Colors._ID, // 0 - Colors.COLOR, // 1 - Colors.COLOR_KEY // 2 - }; - - static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE + - "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT; - public static final int COLORS_INDEX_COLOR = 1; public static final int COLORS_INDEX_COLOR_KEY = 2; @@ -306,9 +248,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private Uri mUri; private long mEventId; private Cursor mEventCursor; - private Cursor mAttendeesCursor; private Cursor mCalendarsCursor; - private Cursor mRemindersCursor; private static float mScale = 0; // Used for supporting different screen densities @@ -318,21 +258,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private long mEndMillis; private boolean mAllDay; - private boolean mHasAttendeeData; - private String mEventOrganizerEmail; - private String mEventOrganizerDisplayName = ""; - private boolean mIsOrganizer; - private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; private boolean mOwnerCanRespond; private String mSyncAccountName; private String mCalendarOwnerAccount; - private boolean mCanModifyCalendar; - private boolean mCanModifyEvent; private boolean mIsBusyFreeCalendar; - private int mNumOfAttendees; - private EditResponseHelper mEditResponseHelper; - private boolean mDeleteDialogVisible = false; - private DeleteEventHelper mDeleteHelper; private int mOriginalAttendeeResponse; private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE; @@ -341,19 +270,13 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange // Used as the temporary response until the dialog is confirmed. It is also // able to be used as a state marker for configuration changes. private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE; - private boolean mIsRepeating; private boolean mHasAlarm; - private int mMaxReminders; - private String mCalendarAllowedReminders; // Used to prevent saving changes in event if it is being deleted. private boolean mEventDeletionStarted = false; private TextView mTitle; private TextView mWhenDateTime; private TextView mWhere; - private ExpandableTextView mDesc; - private AttendeesView mLongAttendees; - private Button emailAttendeesButton; private Menu mMenu = null; private View mHeadlines; private ScrollView mScrollView; @@ -362,9 +285,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private ObjectAnimator mAnimateAlpha; private long mLoadingMsgStartTime; - private EventColorPickerDialog mColorPickerDialog; private SparseIntArray mDisplayColorKeyMap = new SparseIntArray(); - private int[] mColors; private int mOriginalColor = -1; private boolean mOriginalColorInitialized = false; private int mCalendarColor = -1; @@ -379,37 +300,9 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private boolean mNoCrossFade = false; // Used to prevent repeated cross-fade private RadioGroup mResponseRadioGroup; - ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>(); - ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>(); - ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>(); - ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>(); ArrayList<String> mToEmails = new ArrayList<String>(); ArrayList<String> mCcEmails = new ArrayList<String>(); - private int mDefaultReminderMinutes; - private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0); - public ArrayList<ReminderEntry> mReminders; - public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>(); - public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>(); - private boolean mUserModifiedReminders = false; - - /** - * Contents of the "minutes" spinner. This has default values from the XML file, augmented - * with any additional values that were already associated with the event. - */ - private ArrayList<Integer> mReminderMinuteValues; - private ArrayList<String> mReminderMinuteLabels; - - /** - * Contents of the "methods" spinner. The "values" list specifies the method constant - * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels. Any methods that - * aren't allowed by the Calendar will be removed. - */ - private ArrayList<Integer> mReminderMethodValues; - private ArrayList<String> mReminderMethodLabels; - - private QueryHandler mHandler; - private final Runnable mTZUpdater = new Runnable() { @Override @@ -430,8 +323,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } }; - private OnItemSelectedListener mReminderChangeListener; - private static int mDialogWidth = 500; private static int mDialogHeight = 600; private static int DIALOG_TOP_MARGIN = 8; @@ -447,191 +338,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private CalendarController mController; - private class QueryHandler extends AsyncQueryService { - public QueryHandler(Context context) { - super(context); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - // if the activity is finishing, then close the cursor and return - final Activity activity = getActivity(); - if (activity == null || activity.isFinishing()) { - if (cursor != null) { - cursor.close(); - } - return; - } - - switch (token) { - case TOKEN_QUERY_EVENT: - mEventCursor = Utils.matrixCursorFromCursor(cursor); - if (!initEventCursor()) { - displayEventNotFound(); - return; - } - if (!mCalendarColorInitialized) { - mCalendarColor = Utils.getDisplayColorFromColor( - mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR)); - mCalendarColorInitialized = true; - } - - if (!mOriginalColorInitialized) { - mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR) - ? mCalendarColor : Utils.getDisplayColorFromColor( - mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR)); - mOriginalColorInitialized = true; - } - - if (!mCurrentColorInitialized) { - mCurrentColor = mOriginalColor; - mCurrentColorInitialized = true; - } - - updateEvent(mView); - prepareReminders(); - - // start calendar query - Uri uri = Calendars.CONTENT_URI; - String[] args = new String[] { - Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))}; - startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION, - CALENDARS_WHERE, args, null); - break; - case TOKEN_QUERY_CALENDARS: - mCalendarsCursor = Utils.matrixCursorFromCursor(cursor); - updateCalendar(mView); - // FRAG_TODO fragments shouldn't set the title anymore - updateTitle(); - - args = new String[] { - mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME), - mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) }; - uri = Colors.CONTENT_URI; - startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args, - null); - - if (!mIsBusyFreeCalendar) { - args = new String[] { Long.toString(mEventId) }; - - // start attendees query - uri = Attendees.CONTENT_URI; - startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION, - ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER); - } else { - sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES); - } - if (mHasAlarm) { - // start reminders query - args = new String[] { Long.toString(mEventId) }; - uri = Reminders.CONTENT_URI; - startQuery(TOKEN_QUERY_REMINDERS, null, uri, - REMINDERS_PROJECTION, REMINDERS_WHERE, args, null); - } else { - sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS); - } - break; - case TOKEN_QUERY_COLORS: - ArrayList<Integer> colors = new ArrayList<Integer>(); - if (cursor.moveToFirst()) { - do - { - int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY); - int rawColor = cursor.getInt(COLORS_INDEX_COLOR); - int displayColor = Utils.getDisplayColorFromColor(rawColor); - mDisplayColorKeyMap.put(displayColor, colorKey); - colors.add(displayColor); - } while (cursor.moveToNext()); - } - cursor.close(); - Integer[] sortedColors = new Integer[colors.size()]; - Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator()); - mColors = new int[sortedColors.length]; - for (int i = 0; i < sortedColors.length; i++) { - mColors[i] = sortedColors[i].intValue(); - - float[] hsv = new float[3]; - Color.colorToHSV(mColors[i], hsv); - if (DEBUG) { - Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]); - } - } - if (mCanModifyCalendar) { - View button = mView.findViewById(R.id.change_color); - if (button != null && mColors.length > 0) { - button.setEnabled(true); - button.setVisibility(View.VISIBLE); - } - } - updateMenu(); - break; - case TOKEN_QUERY_ATTENDEES: - mAttendeesCursor = Utils.matrixCursorFromCursor(cursor); - initAttendeesCursor(mView); - updateResponse(mView); - break; - case TOKEN_QUERY_REMINDERS: - mRemindersCursor = Utils.matrixCursorFromCursor(cursor); - initReminders(mView, mRemindersCursor); - break; - case TOKEN_QUERY_VISIBLE_CALENDARS: - if (cursor.getCount() > 1) { - // Start duplicate calendars query to detect whether to add the calendar - // email to the calendar owner display. - String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); - mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, - Calendars.CONTENT_URI, CALENDARS_PROJECTION, - CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null); - } else { - // Don't need to display the calendar owner when there is only a single - // calendar. Skip the duplicate calendars query. - setVisibilityCommon(mView, R.id.calendar_container, View.GONE); - mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS; - } - break; - case TOKEN_QUERY_DUPLICATE_CALENDARS: - SpannableStringBuilder sb = new SpannableStringBuilder(); - - // Calendar display name - String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); - sb.append(calendarName); - - // Show email account if display name is not unique and - // display name != email - String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); - if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) && - Utils.isValidEmail(email)) { - sb.append(" (").append(email).append(")"); - } - - setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE); - setTextCommon(mView, R.id.calendar_name, sb); - break; - } - cursor.close(); - sendAccessibilityEventIfQueryDone(token); - - // All queries are done, show the view. - if (mCurrentQuery == TOKEN_QUERY_ALL) { - if (mLoadingMsgView.getAlpha() == 1) { - // Loading message is showing, let it stay a bit more (to prevent - // flashing) by adding a start delay to the event animation - long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() - - mLoadingMsgStartTime); - if (timeDiff > 0) { - mAnimateAlpha.setStartDelay(timeDiff); - } - } - if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) { - mAnimateAlpha.start(); - } else { - mScrollView.setAlpha(1); - mLoadingMsgView.setVisibility(View.GONE); - } - } - } - } - private void sendAccessibilityEventIfQueryDone(int token) { mCurrentQuery |= token; if (mCurrentQuery == TOKEN_QUERY_ALL) { @@ -640,8 +346,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis, - int attendeeResponse, boolean isDialog, int windowStyle, - ArrayList<ReminderEntry> reminders) { + int attendeeResponse, boolean isDialog, int windowStyle) { Resources r = context.getResources(); if (mScale == 0) { @@ -664,11 +369,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mEndMillis = endMillis; mAttendeeResponseFromIntent = attendeeResponse; mWindowStyle = windowStyle; - - // Pass in null if no reminders are being specified. - // This may be used to explicitly show certain reminders already known - // about, such as during configuration changes. - mReminders = reminders; } // This is currently required by the fragment manager. @@ -676,10 +376,9 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis, - int attendeeResponse, boolean isDialog, int windowStyle, - ArrayList<ReminderEntry> reminders) { + int attendeeResponse, boolean isDialog, int windowStyle) { this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis, - endMillis, attendeeResponse, isDialog, windowStyle, reminders); + endMillis, attendeeResponse, isDialog, windowStyle); mEventId = eventId; } @@ -687,40 +386,12 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mReminderChangeListener = new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - Integer prevValue = (Integer) parent.getTag(); - if (prevValue == null || prevValue != position) { - parent.setTag(position); - mUserModifiedReminders = true; - } - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - // do nothing - } - - }; - - if (savedInstanceState != null) { - mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false); - mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE, - DIALOG_WINDOW_STYLE); - } - if (mIsDialog) { applyDialogParams(); } final Activity activity = getActivity(); mContext = activity; - mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager() - .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); - if (mColorPickerDialog != null) { - mColorPickerDialog.setOnColorSelectedListener(this); - } } private void applyDialogParams() { @@ -760,30 +431,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange // Implements OnCheckedChangeListener @Override public void onCheckedChanged(RadioGroup group, int checkedId) { - // If we haven't finished the return from the dialog yet, don't display. - if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - return; - } - - // If this is not a repeating event, then don't display the dialog - // asking which events to change. - int response = getResponseFromButtonId(checkedId); - if (!mIsRepeating) { - mUserSetResponse = response; - return; - } - - // If the selection is the same as the original, then don't display the - // dialog asking which events to change. - if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) { - mUserSetResponse = response; - return; - } - - // This is a repeating event. We need to ask the user if they mean to - // change just this one instance or all instances. - mTentativeUserSetResponse = response; - mEditResponseHelper.showDialog(mWhichEvents); } public void onNothingSelected(AdapterView<?> parent) { @@ -803,52 +450,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config); mController = CalendarController.getInstance(mActivity); mController.registerEventHandler(R.layout.event_info, this); - mEditResponseHelper = new EditResponseHelper(activity); - mEditResponseHelper.setDismissListener( - new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - // If the user dismisses the dialog (without hitting OK), - // then we want to revert the selection that opened the dialog. - if (mEditResponseHelper.getWhichEvents() != -1) { - mUserSetResponse = mTentativeUserSetResponse; - mWhichEvents = mEditResponseHelper.getWhichEvents(); - } else { - // Revert the attending response radio selection to whatever - // was selected prior to this selection (possibly nothing). - int oldResponse; - if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - oldResponse = mUserSetResponse; - } else { - oldResponse = mOriginalAttendeeResponse; - } - int buttonToCheck = findButtonIdForResponse(oldResponse); - - if (mResponseRadioGroup != null) { - mResponseRadioGroup.check(buttonToCheck); - } - - // If the radio group is being cleared, also clear the - // dialog's selection of which events should be included - // in this response. - if (buttonToCheck == -1) { - mEditResponseHelper.setWhichEvents(-1); - } - } - // Since OnPause will force the dialog to dismiss, do - // not change the dialog status - if (!mIsPaused) { - mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE; - } - } - }); - - if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { - mEditResponseHelper.setWhichEvents(UPDATE_ALL); - mWhichEvents = mEditResponseHelper.getWhichEvents(); - } - mHandler = new QueryHandler(activity); if (!mIsDialog) { setHasOptionsMenu(true); } @@ -857,47 +459,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - if (savedInstanceState != null) { - mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false); - mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE, - DIALOG_WINDOW_STYLE); - mDeleteDialogVisible = - savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false); - mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR); - mCalendarColorInitialized = - savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT); - mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR); - mOriginalColorInitialized = savedInstanceState.getBoolean( - BUNDLE_KEY_ORIGINAL_COLOR_INIT); - mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR); - mCurrentColorInitialized = savedInstanceState.getBoolean( - BUNDLE_KEY_CURRENT_COLOR_INIT); - mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY); - - mTentativeUserSetResponse = savedInstanceState.getInt( - BUNDLE_KEY_TENTATIVE_USER_RESPONSE, - Attendees.ATTENDEE_STATUS_NONE); - if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE && - mEditResponseHelper != null) { - // If the edit response helper dialog is open, we'll need to - // know if either of the choices were selected. - mEditResponseHelper.setWhichEvents(savedInstanceState.getInt( - BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1)); - } - mUserSetResponse = savedInstanceState.getInt( - BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, - Attendees.ATTENDEE_STATUS_NONE); - if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - // If the response was set by the user before a configuration - // change, we'll need to know which choice was selected. - mWhichEvents = savedInstanceState.getInt( - BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1); - } - - mReminders = Utils.readRemindersFromBundle(savedInstanceState); - } - if (mWindowStyle == DIALOG_WINDOW_STYLE) { mView = inflater.inflate(R.layout.event_info_dialog, container, false); } else { @@ -909,20 +470,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mTitle = (TextView) mView.findViewById(R.id.title); mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime); mWhere = (TextView) mView.findViewById(R.id.where); - mDesc = (ExpandableTextView) mView.findViewById(R.id.description); mHeadlines = mView.findViewById(R.id.event_info_headline); - mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list); mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value); - if (mUri == null) { - // restore event ID from bundle - mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID); - mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); - mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS); - mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS); - } - mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1); mAnimateAlpha.setDuration(FADE_IN_TIME); mAnimateAlpha.addListener(new AnimatorListenerAdapter() { @@ -957,96 +508,17 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mErrorMsgView.setVisibility(View.INVISIBLE); mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY); - // start loading the data - - mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, - null, null, null); - - View b = mView.findViewById(R.id.delete); - b.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!mCanModifyCalendar) { - return; - } - mDeleteHelper = - new DeleteEventHelper(mContext, mActivity, !mIsDialog && !mIsTabletConfig /* exitWhenDone */); - mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this); - mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); - mDeleteDialogVisible = true; - mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); - } - }); - - b = mView.findViewById(R.id.change_color); - b.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!mCanModifyCalendar) { - return; - } - showEventColorPickerDialog(); - } - }); - // Hide Edit/Delete buttons if in full screen mode on a phone if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) { mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE); } - // Create a listener for the email guests button - emailAttendeesButton = (Button) mView.findViewById(R.id.email_attendees_button); - if (emailAttendeesButton != null) { - emailAttendeesButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - emailAttendees(); - } - }); - } - - // Create a listener for the add reminder button - View reminderAddButton = mView.findViewById(R.id.reminder_add); - View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - addReminder(); - mUserModifiedReminders = true; - } - }; - reminderAddButton.setOnClickListener(addReminderOnClickListener); - - // Set reminders variables - - SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); - String defaultReminderString = prefs.getString( - GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); - mDefaultReminderMinutes = Integer.parseInt(defaultReminderString); - prepareReminders(); - return mView; } - private final Runnable onDeleteRunnable = new Runnable() { - @Override - public void run() { - if (EventInfoFragment.this.mIsPaused) { - mDismissOnResume = true; - return; - } - if (EventInfoFragment.this.isVisible()) { - EventInfoFragment.this.dismiss(); - } - } - }; - private void updateTitle() { Resources res = getActivity().getResources(); - if (mCanModifyCalendar && !mIsOrganizer) { - getActivity().setTitle(res.getString(R.string.event_info_title_invite)); - } else { - getActivity().setTitle(res.getString(R.string.event_info_title)); - } + getActivity().setTitle(res.getString(R.string.event_info_title)); } /** @@ -1061,148 +533,14 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mEventCursor.moveToFirst(); mEventId = mEventCursor.getInt(EVENT_INDEX_ID); String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); - mIsRepeating = !TextUtils.isEmpty(rRule); - // mHasAlarm will be true if it was saved in the event already, or if - // we've explicitly been provided reminders (e.g. during rotation). - mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : - (mReminders != null && mReminders.size() > 0); - mMaxReminders = mEventCursor.getInt(EVENT_INDEX_MAX_REMINDERS); - mCalendarAllowedReminders = mEventCursor.getString(EVENT_INDEX_ALLOWED_REMINDERS); + // mHasAlarm will be true if it was saved in the event already. + mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : false; return true; } - @SuppressWarnings("fallthrough") - private void initAttendeesCursor(View view) { - mOriginalAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE; - mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; - mNumOfAttendees = 0; - if (mAttendeesCursor != null) { - mNumOfAttendees = mAttendeesCursor.getCount(); - if (mAttendeesCursor.moveToFirst()) { - mAcceptedAttendees.clear(); - mDeclinedAttendees.clear(); - mTentativeAttendees.clear(); - mNoResponseAttendees.clear(); - - do { - int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); - String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME); - String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL); - - if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) == - Attendees.RELATIONSHIP_ORGANIZER) { - - // Overwrites the one from Event table if available - if (!TextUtils.isEmpty(name)) { - mEventOrganizerDisplayName = name; - if (!mIsOrganizer) { - setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE); - setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName); - } - } - } - - if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE && - mCalendarOwnerAccount.equalsIgnoreCase(email)) { - mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID); - mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); - } else { - String identity = null; - String idNamespace = null; - - if (Utils.isJellybeanOrLater()) { - identity = mAttendeesCursor.getString(ATTENDEES_INDEX_IDENTITY); - idNamespace = mAttendeesCursor.getString(ATTENDEES_INDEX_ID_NAMESPACE); - } - - // Don't show your own status in the list because: - // 1) it doesn't make sense for event without other guests. - // 2) there's a spinner for that for events with guests. - switch(status) { - case Attendees.ATTENDEE_STATUS_ACCEPTED: - mAcceptedAttendees.add(new Attendee(name, email, - Attendees.ATTENDEE_STATUS_ACCEPTED, identity, - idNamespace)); - break; - case Attendees.ATTENDEE_STATUS_DECLINED: - mDeclinedAttendees.add(new Attendee(name, email, - Attendees.ATTENDEE_STATUS_DECLINED, identity, - idNamespace)); - break; - case Attendees.ATTENDEE_STATUS_TENTATIVE: - mTentativeAttendees.add(new Attendee(name, email, - Attendees.ATTENDEE_STATUS_TENTATIVE, identity, - idNamespace)); - break; - default: - mNoResponseAttendees.add(new Attendee(name, email, - Attendees.ATTENDEE_STATUS_NONE, identity, - idNamespace)); - } - } - } while (mAttendeesCursor.moveToNext()); - mAttendeesCursor.moveToFirst(); - - updateAttendees(view); - } - } - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId); - outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis); - outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis); - outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog); - outState.putInt(BUNDLE_KEY_WINDOW_STYLE, mWindowStyle); - outState.putBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE, mDeleteDialogVisible); - outState.putInt(BUNDLE_KEY_CALENDAR_COLOR, mCalendarColor); - outState.putBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT, mCalendarColorInitialized); - outState.putInt(BUNDLE_KEY_ORIGINAL_COLOR, mOriginalColor); - outState.putBoolean(BUNDLE_KEY_ORIGINAL_COLOR_INIT, mOriginalColorInitialized); - outState.putInt(BUNDLE_KEY_CURRENT_COLOR, mCurrentColor); - outState.putBoolean(BUNDLE_KEY_CURRENT_COLOR_INIT, mCurrentColorInitialized); - outState.putInt(BUNDLE_KEY_CURRENT_COLOR_KEY, mCurrentColorKey); - - // We'll need the temporary response for configuration changes. - outState.putInt(BUNDLE_KEY_TENTATIVE_USER_RESPONSE, mTentativeUserSetResponse); - if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE && - mEditResponseHelper != null) { - outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, - mEditResponseHelper.getWhichEvents()); - } - - // Save the current response. - int response; - if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { - response = mAttendeeResponseFromIntent; - } else { - response = mOriginalAttendeeResponse; - } - outState.putInt(BUNDLE_KEY_ATTENDEE_RESPONSE, response); - if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - response = mUserSetResponse; - outState.putInt(BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, response); - outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, mWhichEvents); - } - - // Save the reminders. - mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews, - mReminderMinuteValues, mReminderMethodValues); - int numReminders = mReminders.size(); - ArrayList<Integer> reminderMinutes = - new ArrayList<Integer>(numReminders); - ArrayList<Integer> reminderMethods = - new ArrayList<Integer>(numReminders); - for (ReminderEntry reminder : mReminders) { - reminderMinutes.add(reminder.getMinutes()); - reminderMethods.add(reminder.getMethod()); - } - outState.putIntegerArrayList( - BUNDLE_KEY_REMINDER_MINUTES, reminderMinutes); - outState.putIntegerArrayList( - BUNDLE_KEY_REMINDER_METHODS, reminderMethods); } @Override @@ -1212,7 +550,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) { inflater.inflate(R.menu.event_info_title_bar, menu); mMenu = menu; - updateMenu(); } } @@ -1237,62 +574,13 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mActivity.finish(); return true; } else if (itemId == R.id.info_action_edit) { - doEdit(); mActivity.finish(); - } else if (itemId == R.id.info_action_delete) { - mDeleteHelper = - new DeleteEventHelper(mActivity, mActivity, true /* exitWhenDone */); - mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this); - mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); - mDeleteDialogVisible = true; - mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); - } else if (itemId == R.id.info_action_change_color) { - showEventColorPickerDialog(); } return super.onOptionsItemSelected(item); } - private void showEventColorPickerDialog() { - if (mColorPickerDialog == null) { - mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor, - mCalendarColor, mIsTabletConfig); - mColorPickerDialog.setOnColorSelectedListener(this); - } - final FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.executePendingTransactions(); - if (!mColorPickerDialog.isAdded()) { - mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); - } - } - - private boolean saveEventColor() { - if (mCurrentColor == mOriginalColor) { - return false; - } - - ContentValues values = new ContentValues(); - if (mCurrentColor != mCalendarColor) { - values.put(Events.EVENT_COLOR_KEY, mCurrentColorKey); - } else { - values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR); - } - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); - mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, - null, null, Utils.UNDO_DELAY); - return true; - } - @Override public void onStop() { - Activity act = getActivity(); - if (!mEventDeletionStarted && act != null && !act.isChangingConfigurations()) { - - boolean responseSaved = saveResponse(); - boolean eventColorSaved = saveEventColor(); - if (saveReminders() || responseSaved || eventColorSaved) { - Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show(); - } - } super.onStop(); } @@ -1304,86 +592,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange if (mCalendarsCursor != null) { mCalendarsCursor.close(); } - if (mAttendeesCursor != null) { - mAttendeesCursor.close(); - } super.onDestroy(); } /** - * Asynchronously saves the response to an invitation if the user changed - * the response. Returns true if the database will be updated. - * - * @return true if the database will be changed - */ - private boolean saveResponse() { - if (mAttendeesCursor == null || mEventCursor == null) { - return false; - } - - int status = getResponseFromButtonId( - mResponseRadioGroup.getCheckedRadioButtonId()); - if (status == Attendees.ATTENDEE_STATUS_NONE) { - return false; - } - - // If the status has not changed, then don't update the database - if (status == mOriginalAttendeeResponse) { - return false; - } - - // If we never got an owner attendee id we can't set the status - if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) { - return false; - } - - if (!mIsRepeating) { - // This is a non-repeating event - updateResponse(mEventId, mCalendarOwnerAttendeeId, status); - mOriginalAttendeeResponse = status; - return true; - } - - if (DEBUG) { - Log.d(TAG, "Repeating event: mWhichEvents=" + mWhichEvents); - } - // This is a repeating event - switch (mWhichEvents) { - case -1: - return false; - case UPDATE_SINGLE: - createExceptionResponse(mEventId, status); - mOriginalAttendeeResponse = status; - return true; - case UPDATE_ALL: - updateResponse(mEventId, mCalendarOwnerAttendeeId, status); - mOriginalAttendeeResponse = status; - return true; - default: - Log.e(TAG, "Unexpected choice for updating invitation response"); - break; - } - return false; - } - - private void updateResponse(long eventId, long attendeeId, int status) { - // Update the attendee status in the attendees table. the provider - // takes care of updating the self attendance status. - ContentValues values = new ContentValues(); - - if (!TextUtils.isEmpty(mCalendarOwnerAccount)) { - values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount); - } - values.put(Attendees.ATTENDEE_STATUS, status); - values.put(Attendees.EVENT_ID, eventId); - - Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId); - - mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, - null, null, Utils.UNDO_DELAY); - } - - /** * Creates an exception to a recurring event. The only change we're making is to the * "self attendee status" value. The provider will take care of updating the corresponding * Attendees.attendeeStatus entry. @@ -1401,61 +613,14 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI, String.valueOf(eventId)); ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build()); - - mHandler.startBatch(mHandler.getNextToken(), null, CalendarContract.AUTHORITY, ops, - Utils.UNDO_DELAY); } public static int getResponseFromButtonId(int buttonId) { - int response; - if (buttonId == R.id.response_yes) { - response = Attendees.ATTENDEE_STATUS_ACCEPTED; - } else if (buttonId == R.id.response_maybe) { - response = Attendees.ATTENDEE_STATUS_TENTATIVE; - } else if (buttonId == R.id.response_no) { - response = Attendees.ATTENDEE_STATUS_DECLINED; - } else { - response = Attendees.ATTENDEE_STATUS_NONE; - } - return response; + return Attendees.ATTENDEE_STATUS_NONE; } public static int findButtonIdForResponse(int response) { - int buttonId; - switch (response) { - case Attendees.ATTENDEE_STATUS_ACCEPTED: - buttonId = R.id.response_yes; - break; - case Attendees.ATTENDEE_STATUS_TENTATIVE: - buttonId = R.id.response_maybe; - break; - case Attendees.ATTENDEE_STATUS_DECLINED: - buttonId = R.id.response_no; - break; - default: - buttonId = -1; - } - return buttonId; - } - - private void doEdit() { - Context c = getActivity(); - // This ensures that we aren't in the process of closing and have been - // unattached already - if (c != null) { - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); - Intent intent = new Intent(Intent.ACTION_EDIT, uri); - intent.setClass(mActivity, EditEventActivity.class); - intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis); - intent.putExtra(EXTRA_EVENT_END_TIME, mEndMillis); - intent.putExtra(EXTRA_EVENT_ALL_DAY, mAllDay); - intent.putExtra(EditEventActivity.EXTRA_EVENT_COLOR, mCurrentColor); - intent.putExtra(EditEventActivity.EXTRA_EVENT_REMINDERS, EventViewUtils - .reminderItemsToReminders(mReminderViews, mReminderMinuteValues, - mReminderMethodValues)); - intent.putExtra(EVENT_EDIT_ON_LAUNCH, true); - startActivity(intent); - } + return -1; } private void displayEventNotFound() { @@ -1546,25 +711,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange setTextCommon(view, R.id.when_datetime, sb); } - // Display the repeat string (if any) - String repeatString = null; - if (!TextUtils.isEmpty(rRule)) { - EventRecurrence eventRecurrence = new EventRecurrence(); - eventRecurrence.parse(rRule); - Time date = new Time(localTimezone); - date.set(mStartMillis); - if (mAllDay) { - date.timezone = Time.TIMEZONE_UTC; - } - eventRecurrence.setStartDate(date); - repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources, - eventRecurrence, true); - } - if (repeatString == null) { - view.findViewById(R.id.when_repeat).setVisibility(View.GONE); - } else { - setTextCommon(view, R.id.when_repeat, repeatString); - } + view.findViewById(R.id.when_repeat).setVisibility(View.GONE); // Organizer view is setup in the updateCalendar method @@ -1575,44 +722,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } else { final TextView textView = mWhere; if (textView != null) { - textView.setAutoLinkMask(0); textView.setText(location.trim()); - try { - textView.setText(Utils.extendedLinkify(textView.getText().toString(), true)); - - // Linkify.addLinks() sets the TextView movement method if it finds any links. - // We must do the same here, in case linkify by itself did not find any. - // (This is cloned from Linkify.addLinkMovementMethod().) - MovementMethod mm = textView.getMovementMethod(); - if ((mm == null) || !(mm instanceof LinkMovementMethod)) { - if (textView.getLinksClickable()) { - textView.setMovementMethod(LinkMovementMethod.getInstance()); - } - } - } catch (Exception ex) { - // unexpected - Log.e(TAG, "Linkification failed", ex); - } - - textView.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - try { - return v.onTouchEvent(event); - } catch (ActivityNotFoundException e) { - // ignore - return true; - } - } - }); } } - // Description - if (description != null && description.length() != 0) { - mDesc.setText(description); - } - // Launch Custom App if (Utils.isJellybeanOrLater()) { updateCustomAppButton(); @@ -1620,74 +733,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } private void updateCustomAppButton() { - buttonSetup: { - final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button); - if (launchButton == null) - break buttonSetup; - - final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE); - final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI); - - if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri)) - break buttonSetup; - - PackageManager pm = mContext.getPackageManager(); - if (pm == null) - break buttonSetup; - - ApplicationInfo info; - try { - info = pm.getApplicationInfo(customAppPackage, 0); - if (info == null) - break buttonSetup; - } catch (NameNotFoundException e) { - break buttonSetup; - } - - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); - final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri); - intent.setPackage(customAppPackage); - intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri); - intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis); - - // See if we have a taker for our intent - if (pm.resolveActivity(intent, 0) == null) - break buttonSetup; - - Drawable icon = pm.getApplicationIcon(info); - if (icon != null) { - - Drawable[] d = launchButton.getCompoundDrawables(); - icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize); - launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]); - } - - CharSequence label = pm.getApplicationLabel(info); - if (label != null && label.length() != 0) { - launchButton.setText(label); - } else if (icon == null) { - // No icon && no label. Hide button? - break buttonSetup; - } - - // Launch custom app - launchButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - startActivityForResult(intent, 0); - } catch (ActivityNotFoundException e) { - // Shouldn't happen as we checked it already - setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE); - } - } - }); - - setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE); - return; - - } - setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE); return; } @@ -1704,11 +749,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange event.setPackageName(getActivity().getPackageName()); List<CharSequence> text = event.getText(); - addFieldToAccessibilityEvent(text, mTitle, null); - addFieldToAccessibilityEvent(text, mWhenDateTime, null); - addFieldToAccessibilityEvent(text, mWhere, null); - addFieldToAccessibilityEvent(text, null, mDesc); - if (mResponseRadioGroup.getVisibility() == View.VISIBLE) { int id = mResponseRadioGroup.getCheckedRadioButtonId(); if (id != View.NO_ID) { @@ -1721,26 +761,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange am.sendAccessibilityEvent(event); } - private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv, - ExpandableTextView etv) { - CharSequence cs; - if (tv != null) { - cs = tv.getText(); - } else if (etv != null) { - cs = etv.getText(); - } else { - return; - } - - if (!TextUtils.isEmpty(cs)) { - cs = cs.toString().trim(); - if (cs.length() > 0) { - text.add(cs); - text.add(PERIOD_SPACE); - } - } - } - private void updateCalendar(View view) { mCalendarOwnerAccount = ""; @@ -1751,29 +771,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0; mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME); - // start visible calendars query - mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI, - CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null); - - mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER); - mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail); - - if (!TextUtils.isEmpty(mEventOrganizerEmail) && - !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) { - mEventOrganizerDisplayName = mEventOrganizerEmail; - } - - if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) { - setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName); - setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE); - } else { - setVisibilityCommon(view, R.id.organizer_container, View.GONE); - } - mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; - mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) - >= Calendars.CAL_ACCESS_CONTRIBUTOR; - // TODO add "|| guestCanModify" after b/1299071 is fixed - mCanModifyEvent = mCanModifyCalendar && mIsOrganizer; + setVisibilityCommon(view, R.id.organizer_container, View.GONE); mIsBusyFreeCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY; @@ -1784,7 +782,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - doEdit(); // For dialogs, just close the fragment // For full screen, close activity on phone, leave it for tablet if (mIsDialog) { @@ -1797,20 +794,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange }); } View button; - if (mCanModifyCalendar) { - button = mView.findViewById(R.id.delete); - if (button != null) { - button.setEnabled(true); - button.setVisibility(View.VISIBLE); - } - } - if (mCanModifyEvent) { - button = mView.findViewById(R.id.edit); - if (button != null) { - button.setEnabled(true); - button.setVisibility(View.VISIBLE); - } - } if ((!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) { mActivity.invalidateOptionsMenu(); @@ -1821,190 +804,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } } - /** - * - */ - private void updateMenu() { - if (mMenu == null) { - return; - } - MenuItem delete = mMenu.findItem(R.id.info_action_delete); - MenuItem edit = mMenu.findItem(R.id.info_action_edit); - MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color); - if (delete != null) { - delete.setVisible(mCanModifyCalendar); - delete.setEnabled(mCanModifyCalendar); - } - if (edit != null) { - edit.setVisible(mCanModifyEvent); - edit.setEnabled(mCanModifyEvent); - } - if (changeColor != null && mColors != null && mColors.length > 0) { - changeColor.setVisible(mCanModifyCalendar); - changeColor.setEnabled(mCanModifyCalendar); - } - } - - private void updateAttendees(View view) { - if (mAcceptedAttendees.size() + mDeclinedAttendees.size() + - mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) { - mLongAttendees.clearAttendees(); - (mLongAttendees).addAttendees(mAcceptedAttendees); - (mLongAttendees).addAttendees(mDeclinedAttendees); - (mLongAttendees).addAttendees(mTentativeAttendees); - (mLongAttendees).addAttendees(mNoResponseAttendees); - mLongAttendees.setEnabled(false); - mLongAttendees.setVisibility(View.VISIBLE); - } else { - mLongAttendees.setVisibility(View.GONE); - } - - if (hasEmailableAttendees()) { - setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE); - if (emailAttendeesButton != null) { - emailAttendeesButton.setText(R.string.email_guests_label); - } - } else if (hasEmailableOrganizer()) { - setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE); - if (emailAttendeesButton != null) { - emailAttendeesButton.setText(R.string.email_organizer_label); - } - } else { - setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE); - } - } - - /** - * Returns true if there is at least 1 attendee that is not the viewer. - */ - private boolean hasEmailableAttendees() { - for (Attendee attendee : mAcceptedAttendees) { - if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { - return true; - } - } - for (Attendee attendee : mTentativeAttendees) { - if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { - return true; - } - } - for (Attendee attendee : mNoResponseAttendees) { - if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { - return true; - } - } - for (Attendee attendee : mDeclinedAttendees) { - if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { - return true; - } - } - return false; - } - - private boolean hasEmailableOrganizer() { - return mEventOrganizerEmail != null && - Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName); - } - - public void initReminders(View view, Cursor cursor) { - - // Add reminders - mOriginalReminders.clear(); - mUnsupportedReminders.clear(); - while (cursor.moveToNext()) { - int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES); - int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD); - - if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) { - // Stash unsupported reminder types separately so we don't alter - // them in the UI - mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method)); - } else { - mOriginalReminders.add(ReminderEntry.valueOf(minutes, method)); - } - } - // Sort appropriately for display (by time, then type) - Collections.sort(mOriginalReminders); - - if (mUserModifiedReminders) { - // If the user has changed the list of reminders don't change what's - // shown. - return; - } - - LinearLayout parent = (LinearLayout) mScrollView - .findViewById(R.id.reminder_items_container); - if (parent != null) { - parent.removeAllViews(); - } - if (mReminderViews != null) { - mReminderViews.clear(); - } - - if (mHasAlarm) { - ArrayList<ReminderEntry> reminders; - // If applicable, use reminders saved in the bundle. - if (mReminders != null) { - reminders = mReminders; - } else { - reminders = mOriginalReminders; - } - // Insert any minute values that aren't represented in the minutes list. - for (ReminderEntry re : reminders) { - EventViewUtils.addMinutesToList( - mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes()); - } - // Create a UI element for each reminder. We display all of the reminders we get - // from the provider, even if the count exceeds the calendar maximum. (Also, for - // a new event, we won't have a maxReminders value available.) - for (ReminderEntry re : reminders) { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, - mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, - mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener); - } - EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); - // TODO show unsupported reminder types in some fashion. - } - } - - void updateResponse(View view) { - // we only let the user accept/reject/etc. a meeting if: - // a) you can edit the event's containing calendar AND - // b) you're not the organizer and only attendee AND - // c) organizerCanRespond is enabled for the calendar - // (if the attendee data has been hidden, the visible number of attendees - // will be 1 -- the calendar owner's). - // (there are more cases involved to be 100% accurate, such as - // paying attention to whether or not an attendee status was - // included in the feed, but we're currently omitting those corner cases - // for simplicity). - - // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel. - if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) || - (mIsOrganizer && !mOwnerCanRespond)) { - setVisibilityCommon(view, R.id.response_container, View.GONE); - return; - } - - setVisibilityCommon(view, R.id.response_container, View.VISIBLE); - - - int response; - if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - response = mTentativeUserSetResponse; - } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { - response = mUserSetResponse; - } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { - response = mAttendeeResponseFromIntent; - } else { - response = mOriginalAttendeeResponse; - } - - int buttonToCheck = findButtonIdForResponse(response); - mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons - mResponseRadioGroup.setOnCheckedChangeListener(this); - } - private void setTextCommon(View view, int id, CharSequence text) { TextView textView = (TextView) view.findViewById(id); if (textView == null) @@ -2020,58 +819,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange return; } - /** - * Taken from com.google.android.gm.HtmlConversationActivity - * - * Send the intent that shows the Contact info corresponding to the email address. - */ - public void showContactInfo(Attendee attendee, Rect rect) { - // First perform lookup query to find existing contact - final ContentResolver resolver = getActivity().getContentResolver(); - final String address = attendee.mEmail; - final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI, - Uri.encode(address)); - final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri); - - if (lookupUri != null) { - // Found matching contact, trigger QuickContact - QuickContact.showQuickContact(getActivity(), rect, lookupUri, - QuickContact.MODE_MEDIUM, null); - } else { - // No matching contact, ask user to create one - final Uri mailUri = Uri.fromParts("mailto", address, null); - final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri); - - // Pass along full E-mail string for possible create dialog - Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null); - intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString()); - - // Only provide personal name hint if we have one - final String senderPersonal = attendee.mName; - if (!TextUtils.isEmpty(senderPersonal)) { - intent.putExtra(Intents.Insert.NAME, senderPersonal); - } - - startActivity(intent); - } - } - @Override public void onPause() { mIsPaused = true; - mHandler.removeCallbacks(onDeleteRunnable); super.onPause(); - // Remove event deletion alert box since it is being rebuild in the OnResume - // This is done to get the same behavior on OnResume since the AlertDialog is gone on - // rotation but not if you press the HOME key - if (mDeleteDialogVisible && mDeleteHelper != null) { - mDeleteHelper.dismissAlertDialog(); - mDeleteHelper = null; - } - if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE - && mEditResponseHelper != null) { - mEditResponseHelper.dismissAlertDialog(); - } } @Override @@ -2082,20 +833,9 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange applyDialogParams(); } mIsPaused = false; - if (mDismissOnResume) { - mHandler.post(onDeleteRunnable); - } - // Display the "delete confirmation" or "edit response helper" dialog if needed - if (mDeleteDialogVisible) { - mDeleteHelper = new DeleteEventHelper( - mContext, mActivity, - !mIsDialog && !mIsTabletConfig /* exitWhenDone */); - mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); - mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); - } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { + if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { int buttonId = findButtonIdForResponse(mTentativeUserSetResponse); mResponseRadioGroup.check(buttonId); - mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents()); } } @@ -2114,164 +854,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } public void reloadEvents() { - if (mHandler != null) { - mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, - null, null, null); - } } @Override public void onClick(View view) { - - // This must be a click on one of the "remove reminder" buttons - LinearLayout reminderItem = (LinearLayout) view.getParent(); - LinearLayout parent = (LinearLayout) reminderItem.getParent(); - parent.removeView(reminderItem); - mReminderViews.remove(reminderItem); - mUserModifiedReminders = true; - EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); - } - - - /** - * Add a new reminder when the user hits the "add reminder" button. We use the default - * reminder time and method. - */ - private void addReminder() { - // TODO: when adding a new reminder, make it different from the - // last one in the list (if any). - if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, - mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, - mReminderMethodLabels, - ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders, - mReminderChangeListener); - } else { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, - mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, - mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes), - mMaxReminders, mReminderChangeListener); - } - - EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); - } - - synchronized private void prepareReminders() { - // Nothing to do if we've already built these lists _and_ we aren't - // removing not allowed methods - if (mReminderMinuteValues != null && mReminderMinuteLabels != null - && mReminderMethodValues != null && mReminderMethodLabels != null - && mCalendarAllowedReminders == null) { - return; - } - // Load the labels and corresponding numeric values for the minutes and methods lists - // from the assets. If we're switching calendars, we need to clear and re-populate the - // lists (which may have elements added and removed based on calendar properties). This - // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a - // new event that aren't in the default set. - Resources r = mActivity.getResources(); - mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values); - mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels); - mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values); - mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels); - - // Remove any reminder methods that aren't allowed for this calendar. If this is - // a new event, mCalendarAllowedReminders may not be set the first time we're called. - if (mCalendarAllowedReminders != null) { - EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels, - mCalendarAllowedReminders); - } - if (mView != null) { - mView.invalidate(); - } - } - - - private boolean saveReminders() { - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); - - // Read reminders from UI - mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews, - mReminderMinuteValues, mReminderMethodValues); - mOriginalReminders.addAll(mUnsupportedReminders); - Collections.sort(mOriginalReminders); - mReminders.addAll(mUnsupportedReminders); - Collections.sort(mReminders); - - // Check if there are any changes in the reminder - boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders, - mOriginalReminders, false /* no force save */); - - if (!changed) { - return false; - } - - // save new reminders - AsyncQueryService service = new AsyncQueryService(getActivity()); - service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0); - mOriginalReminders = mReminders; - // Update the "hasAlarm" field for the event - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); - int len = mReminders.size(); - boolean hasAlarm = len > 0; - if (hasAlarm != mHasAlarm) { - ContentValues values = new ContentValues(); - values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0); - service.startUpdate(0, null, uri, values, null, null, 0); - } - return true; - } - - /** - * Email all the attendees of the event, except for the viewer (so as to not email - * himself) and resources like conference rooms. - */ - private void emailAttendees() { - Intent i = new Intent(getActivity(), QuickResponseActivity.class); - i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(i); - } - - /** - * Loads an integer array asset into a list. - */ - private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) { - int[] vals = r.getIntArray(resNum); - int size = vals.length; - ArrayList<Integer> list = new ArrayList<Integer>(size); - - for (int i = 0; i < size; i++) { - list.add(vals[i]); - } - - return list; - } - /** - * Loads a String array asset into a list. - */ - private static ArrayList<String> loadStringArray(Resources r, int resNum) { - String[] labels = r.getStringArray(resNum); - ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels)); - return list; - } - - @Override - public void onDeleteStarted() { - mEventDeletionStarted = true; - } - - private Dialog.OnDismissListener createDeleteOnDismissListener() { - return new Dialog.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - // Since OnPause will force the dialog to dismiss , do - // not change the dialog status - if (!mIsPaused) { - mDeleteDialogVisible = false; - } - } - }; } public long getEventId() { @@ -2288,11 +874,4 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width); mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height); } - - @Override - public void onColorSelected(int color) { - mCurrentColor = color; - mCurrentColorKey = mDisplayColorKeyMap.get(color); - mHeadlines.setBackgroundColor(color); - } } diff --git a/src/com/android/calendar/EventRecurrenceFormatter.java b/src/com/android/calendar/EventRecurrenceFormatter.java deleted file mode 100644 index b9e33fdd..00000000 --- a/src/com/android/calendar/EventRecurrenceFormatter.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.content.Context; -import android.content.res.Resources; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.TimeFormatException; - -import com.android.calendarcommon2.EventRecurrence; - -import java.util.Calendar; - -public class EventRecurrenceFormatter -{ - - private static int[] mMonthRepeatByDayOfWeekIds; - private static String[][] mMonthRepeatByDayOfWeekStrs; - - public static String getRepeatString(Context context, Resources r, EventRecurrence recurrence, - boolean includeEndString) { - String endString = ""; - if (includeEndString) { - StringBuilder sb = new StringBuilder(); - if (recurrence.until != null) { - try { - Time t = new Time(); - t.parse(recurrence.until); - final String dateStr = DateUtils.formatDateTime(context, - t.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE); - sb.append(r.getString(R.string.endByDate, dateStr)); - } catch (TimeFormatException e) { - } - } - - if (recurrence.count > 0) { - sb.append(r.getQuantityString(R.plurals.endByCount, recurrence.count, - recurrence.count)); - } - endString = sb.toString(); - } - - // TODO Implement "Until" portion of string, as well as custom settings - int interval = recurrence.interval <= 1 ? 1 : recurrence.interval; - switch (recurrence.freq) { - case EventRecurrence.DAILY: - return r.getQuantityString(R.plurals.daily, interval, interval) + endString; - case EventRecurrence.WEEKLY: { - if (recurrence.repeatsOnEveryWeekDay()) { - return r.getString(R.string.every_weekday) + endString; - } else { - String string; - - int dayOfWeekLength = DateUtils.LENGTH_MEDIUM; - if (recurrence.bydayCount == 1) { - dayOfWeekLength = DateUtils.LENGTH_LONG; - } - - StringBuilder days = new StringBuilder(); - - // Do one less iteration in the loop so the last element is added out of the - // loop. This is done so the comma is not placed after the last item. - - if (recurrence.bydayCount > 0) { - int count = recurrence.bydayCount - 1; - for (int i = 0 ; i < count ; i++) { - days.append(dayToString(recurrence.byday[i], dayOfWeekLength)); - days.append(", "); - } - days.append(dayToString(recurrence.byday[count], dayOfWeekLength)); - - string = days.toString(); - } else { - // There is no "BYDAY" specifier, so use the day of the - // first event. For this to work, the setStartDate() - // method must have been used by the caller to set the - // date of the first event in the recurrence. - if (recurrence.startDate == null) { - return null; - } - - int day = EventRecurrence.timeDay2Day(recurrence.startDate.weekDay); - string = dayToString(day, DateUtils.LENGTH_LONG); - } - return r.getQuantityString(R.plurals.weekly, interval, interval, string) - + endString; - } - } - case EventRecurrence.MONTHLY: { - if (recurrence.bydayCount == 1) { - int weekday = recurrence.startDate.weekDay; - // Cache this stuff so we won't have to redo work again later. - cacheMonthRepeatStrings(r, weekday); - int dayNumber = (recurrence.startDate.monthDay - 1) / 7; - StringBuilder sb = new StringBuilder(); - sb.append(r.getString(R.string.monthly)); - sb.append(" ("); - sb.append(mMonthRepeatByDayOfWeekStrs[weekday][dayNumber]); - sb.append(")"); - sb.append(endString); - return sb.toString(); - } - return r.getString(R.string.monthly) + endString; - } - case EventRecurrence.YEARLY: - return r.getString(R.string.yearly_plain) + endString; - } - - return null; - } - - private static void cacheMonthRepeatStrings(Resources r, int weekday) { - if (mMonthRepeatByDayOfWeekIds == null) { - mMonthRepeatByDayOfWeekIds = new int[7]; - mMonthRepeatByDayOfWeekIds[0] = R.array.repeat_by_nth_sun; - mMonthRepeatByDayOfWeekIds[1] = R.array.repeat_by_nth_mon; - mMonthRepeatByDayOfWeekIds[2] = R.array.repeat_by_nth_tues; - mMonthRepeatByDayOfWeekIds[3] = R.array.repeat_by_nth_wed; - mMonthRepeatByDayOfWeekIds[4] = R.array.repeat_by_nth_thurs; - mMonthRepeatByDayOfWeekIds[5] = R.array.repeat_by_nth_fri; - mMonthRepeatByDayOfWeekIds[6] = R.array.repeat_by_nth_sat; - } - if (mMonthRepeatByDayOfWeekStrs == null) { - mMonthRepeatByDayOfWeekStrs = new String[7][]; - } - if (mMonthRepeatByDayOfWeekStrs[weekday] == null) { - mMonthRepeatByDayOfWeekStrs[weekday] = - r.getStringArray(mMonthRepeatByDayOfWeekIds[weekday]); - } - } - - /** - * Converts day of week to a String. - * @param day a EventRecurrence constant - * @return day of week as a string - */ - private static String dayToString(int day, int dayOfWeekLength) { - return DateUtils.getDayOfWeekString(dayToUtilDay(day), dayOfWeekLength); - } - - /** - * Converts EventRecurrence's day of week to DateUtil's day of week. - * @param day of week as an EventRecurrence value - * @return day of week as a DateUtil value. - */ - private static int dayToUtilDay(int day) { - switch (day) { - case EventRecurrence.SU: return Calendar.SUNDAY; - case EventRecurrence.MO: return Calendar.MONDAY; - case EventRecurrence.TU: return Calendar.TUESDAY; - case EventRecurrence.WE: return Calendar.WEDNESDAY; - case EventRecurrence.TH: return Calendar.THURSDAY; - case EventRecurrence.FR: return Calendar.FRIDAY; - case EventRecurrence.SA: return Calendar.SATURDAY; - default: throw new IllegalArgumentException("bad day argument: " + day); - } - } -} diff --git a/src/com/android/calendar/ExpandableTextView.java b/src/com/android/calendar/ExpandableTextView.java deleted file mode 100644 index 508528ec..00000000 --- a/src/com/android/calendar/ExpandableTextView.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class ExpandableTextView extends LinearLayout implements OnClickListener { - - TextView mTv; - ImageButton mButton; // Button to expand/collapse - - private boolean mRelayout = false; - private boolean mCollapsed = true; // Show short version as default. - private int mMaxCollapsedLines = 8; // The default number of lines; - private Drawable mExpandDrawable; - private Drawable mCollapseDrawable; - - public ExpandableTextView(Context context) { - super(context); - init(); - } - - public ExpandableTextView(Context context, AttributeSet attrs) { - super(context, attrs, 0); - init(); - } - - public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - void init() { - mMaxCollapsedLines = getResources().getInteger((R.integer.event_info_desc_line_num)); - mExpandDrawable = getResources().getDrawable(R.drawable.ic_expand_small_holo_light); - mCollapseDrawable = getResources().getDrawable(R.drawable.ic_collapse_small_holo_light); - } - - @Override - public void onClick(View v) { - if (mButton.getVisibility() != View.VISIBLE) { - return; - } - - mCollapsed = !mCollapsed; - mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable); - mTv.setMaxLines(mCollapsed ? mMaxCollapsedLines : Integer.MAX_VALUE); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If no change, measure and return - if (!mRelayout || getVisibility() == View.GONE) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - mRelayout = false; - - // Setup with optimistic case - // i.e. Everything fits. No button needed - mButton.setVisibility(View.GONE); - mTv.setMaxLines(Integer.MAX_VALUE); - - // Measure - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // If the text fits in collapsed mode, we are done. - if (mTv.getLineCount() <= mMaxCollapsedLines) { - return; - } - - // Doesn't fit in collapsed mode. Collapse text view as needed. Show - // button. - if (mCollapsed) { - mTv.setMaxLines(mMaxCollapsedLines); - } - mButton.setVisibility(View.VISIBLE); - - // Re-measure with new setup - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void findViews() { - mTv = (TextView) findViewById(R.id.expandable_text); - mTv.setOnClickListener(this); - mButton = (ImageButton) findViewById(R.id.expand_collapse); - mButton.setOnClickListener(this); - } - - public void setText(String text) { - mRelayout = true; - if (mTv == null) { - findViews(); - } - String trimmedText = text.trim(); - mTv.setText(trimmedText); - this.setVisibility(trimmedText.length() == 0 ? View.GONE : View.VISIBLE); - } - - public CharSequence getText() { - if (mTv == null) { - return ""; - } - return mTv.getText(); - } -} diff --git a/src/com/android/calendar/ExtensionsFactory.java b/src/com/android/calendar/ExtensionsFactory.java deleted file mode 100644 index 697e2b4e..00000000 --- a/src/com/android/calendar/ExtensionsFactory.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.AssetManager; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - - -/* - * Skeleton for additional options in the AllInOne menu. - */ -public class ExtensionsFactory { - - private static String TAG = "ExtensionsFactory"; - - // Config filename for mappings of various class names to their custom - // implementations. - private static String EXTENSIONS_PROPERTIES = "calendar_extensions.properties"; - - private static String ALL_IN_ONE_MENU_KEY = "AllInOneMenuExtensions"; - private static String CLOUD_NOTIFICATION_KEY = "CloudNotificationChannel"; - private static String ANALYTICS_LOGGER_KEY = "AnalyticsLogger"; - - private static Properties sProperties = new Properties(); - private static AllInOneMenuExtensionsInterface sAllInOneMenuExtensions = null; - private static AnalyticsLogger sAnalyticsLogger = null; - - public static void init(AssetManager assetManager) { - try { - InputStream fileStream = assetManager.open(EXTENSIONS_PROPERTIES); - sProperties.load(fileStream); - fileStream.close(); - } catch (FileNotFoundException e) { - // No custom extensions. Ignore. - Log.d(TAG, "No custom extensions."); - } catch (IOException e) { - Log.d(TAG, e.toString()); - } - } - - private static <T> T createInstance(String className) { - try { - Class<?> c = Class.forName(className); - return (T) c.newInstance(); - } catch (ClassNotFoundException e) { - Log.e(TAG, className + ": unable to create instance.", e); - } catch (IllegalAccessException e) { - Log.e(TAG, className + ": unable to create instance.", e); - } catch (InstantiationException e) { - Log.e(TAG, className + ": unable to create instance.", e); - } - return null; - } - - public static AllInOneMenuExtensionsInterface getAllInOneMenuExtensions() { - if ((sAllInOneMenuExtensions != null)) { - return sAllInOneMenuExtensions; - } - - String className = sProperties.getProperty(ALL_IN_ONE_MENU_KEY); - if (className != null) { - sAllInOneMenuExtensions = createInstance(className); - } else { - Log.d(TAG, ALL_IN_ONE_MENU_KEY + " not found in properties file."); - } - - if (sAllInOneMenuExtensions == null) { - sAllInOneMenuExtensions = new AllInOneMenuExtensionsInterface() { - @Override - public Integer getExtensionMenuResource(Menu menu) { - return null; - } - - @Override - public boolean handleItemSelected(MenuItem item, Context context) { - return false; - } - }; - } - return sAllInOneMenuExtensions; - } - - public static CloudNotificationBackplane getCloudNotificationBackplane() { - CloudNotificationBackplane cnb = null; - - String className = sProperties.getProperty(CLOUD_NOTIFICATION_KEY); - if (className != null) { - cnb = createInstance(className); - } else { - Log.d(TAG, CLOUD_NOTIFICATION_KEY + " not found in properties file."); - } - - if (cnb == null) { - cnb = new CloudNotificationBackplane() { - @Override - public boolean open(Context context) { - return true; - } - - @Override - public boolean subscribeToGroup(String senderId, String account, String groupId) - throws IOException { - return true;} - - @Override - public void send(String to, String msgId, Bundle data) { - } - - @Override - public void close() { - } - }; - } - - return cnb; - } - - public static AnalyticsLogger getAnalyticsLogger(Context context) { - if (sAnalyticsLogger != null) { - return sAnalyticsLogger; - } - - String className = sProperties.getProperty(ANALYTICS_LOGGER_KEY); - if (className != null) { - sAnalyticsLogger = createInstance(className); - } else { - Log.d(TAG, ANALYTICS_LOGGER_KEY + " not found in properties file."); - } - - if (sAnalyticsLogger == null) { - sAnalyticsLogger = new AnalyticsLogger() { - @Override - public boolean initialize(Context context) { - return true; - } - - @Override - public void trackView(String name) { - } - }; - } - - sAnalyticsLogger.initialize(context); - return sAnalyticsLogger; - } -} diff --git a/src/com/android/calendar/GeneralPreferences.java b/src/com/android/calendar/GeneralPreferences.java index 54fc0e78..a42f07e3 100644 --- a/src/com/android/calendar/GeneralPreferences.java +++ b/src/com/android/calendar/GeneralPreferences.java @@ -118,7 +118,6 @@ public class GeneralPreferences extends PreferenceFragment implements CheckBoxPreference mAlert; CheckBoxPreference mVibrate; - RingtonePreference mRingtone; CheckBoxPreference mPopup; CheckBoxPreference mUseHomeTZ; CheckBoxPreference mHideDeclined; @@ -165,17 +164,6 @@ public class GeneralPreferences extends PreferenceFragment implements mAlertGroup.removePreference(mVibrate); } - mRingtone = (RingtonePreference) preferenceScreen.findPreference(KEY_ALERTS_RINGTONE); - String ringToneUri = Utils.getRingTonePreference(activity); - - // Set the ringToneUri to the backup-able shared pref only so that - // the Ringtone dialog will open up with the correct value. - final Editor editor = preferenceScreen.getEditor(); - editor.putString(GeneralPreferences.KEY_ALERTS_RINGTONE, ringToneUri).apply(); - - String ringtoneDisplayString = getRingtoneTitleFromUri(activity, ringToneUri); - mRingtone.setSummary(ringtoneDisplayString == null ? "" : ringtoneDisplayString); - mPopup = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_POPUP); mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED); mHideDeclined = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HIDE_DECLINED); @@ -263,7 +251,6 @@ public class GeneralPreferences extends PreferenceFragment implements mHomeTZ.setOnPreferenceChangeListener(listener); mWeekStart.setOnPreferenceChangeListener(listener); mDefaultReminder.setOnPreferenceChangeListener(listener); - mRingtone.setOnPreferenceChangeListener(listener); mHideDeclined.setOnPreferenceChangeListener(listener); mVibrate.setOnPreferenceChangeListener(listener); } @@ -324,13 +311,6 @@ public class GeneralPreferences extends PreferenceFragment implements } else if (preference == mDefaultReminder) { mDefaultReminder.setValue((String) newValue); mDefaultReminder.setSummary(mDefaultReminder.getEntry()); - } else if (preference == mRingtone) { - if (newValue instanceof String) { - Utils.setRingTonePreference(activity, (String) newValue); - String ringtone = getRingtoneTitleFromUri(activity, (String) newValue); - mRingtone.setSummary(ringtone == null ? "" : ringtone); - } - return true; } else if (preference == mVibrate) { mVibrate.setChecked((Boolean) newValue); return true; @@ -391,11 +371,9 @@ public class GeneralPreferences extends PreferenceFragment implements private void updateChildPreferences() { if (mAlert.isChecked()) { mVibrate.setEnabled(true); - mRingtone.setEnabled(true); mPopup.setEnabled(true); } else { mVibrate.setEnabled(false); - mRingtone.setEnabled(false); mPopup.setEnabled(false); } } @@ -405,17 +383,7 @@ public class GeneralPreferences extends PreferenceFragment implements public boolean onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference) { final String key = preference.getKey(); - if (KEY_CLEAR_SEARCH_HISTORY.equals(key)) { - SearchRecentSuggestions suggestions = new SearchRecentSuggestions(getActivity(), - Utils.getSearchAuthority(getActivity()), - CalendarRecentSuggestionsProvider.MODE); - suggestions.clearHistory(); - Toast.makeText(getActivity(), R.string.search_history_cleared, - Toast.LENGTH_SHORT).show(); - return true; - } else { - return super.onPreferenceTreeClick(preferenceScreen, preference); - } + return super.onPreferenceTreeClick(preferenceScreen, preference); } @Override diff --git a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java index fef89e8e..3970115b 100644 --- a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java +++ b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java @@ -19,227 +19,17 @@ package com.android.calendar; import android.app.Activity; import android.content.ActivityNotFoundException; -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; -import android.widget.Toast; - -import com.android.calendarcommon2.DateException; -import com.android.calendarcommon2.Duration; public class GoogleCalendarUriIntentFilter extends Activity { - private static final String TAG = "GoogleCalendarUriIntentFilter"; - static final boolean debug = false; - - private static final int EVENT_INDEX_ID = 0; - private static final int EVENT_INDEX_START = 1; - private static final int EVENT_INDEX_END = 2; - private static final int EVENT_INDEX_DURATION = 3; - - private static final String[] EVENT_PROJECTION = new String[] { - Events._ID, // 0 - Events.DTSTART, // 1 - Events.DTEND, // 2 - Events.DURATION, // 3 - }; - - /** - * Extracts the ID and calendar email from the eid parameter of a URI. - * - * The URI contains an "eid" parameter, which is comprised of an ID, followed - * by a space, followed by the calendar email address. The domain is sometimes - * shortened. See the switch statement. This is Base64-encoded before being - * added to the URI. - * - * @param uri incoming request - * @return the decoded event ID and calendar email - */ - private String[] extractEidAndEmail(Uri uri) { - try { - String eidParam = uri.getQueryParameter("eid"); - if (debug) Log.d(TAG, "eid=" + eidParam ); - if (eidParam == null) { - return null; - } - - byte[] decodedBytes = Base64.decode(eidParam, Base64.DEFAULT); - if (debug) Log.d(TAG, "decoded eid=" + new String(decodedBytes) ); - - for (int spacePosn = 0; spacePosn < decodedBytes.length; spacePosn++) { - if (decodedBytes[spacePosn] == ' ') { - int emailLen = decodedBytes.length - spacePosn - 1; - if (spacePosn == 0 || emailLen < 3) { - break; - } - - String domain = null; - if (decodedBytes[decodedBytes.length - 2] == '@') { - // Drop the special one character domain - emailLen--; - - switch(decodedBytes[decodedBytes.length - 1]) { - case 'm': - domain = "gmail.com"; - break; - case 'g': - domain = "group.calendar.google.com"; - break; - case 'h': - domain = "holiday.calendar.google.com"; - break; - case 'i': - domain = "import.calendar.google.com"; - break; - case 'v': - domain = "group.v.calendar.google.com"; - break; - default: - Log.wtf(TAG, "Unexpected one letter domain: " - + decodedBytes[decodedBytes.length - 1]); - // Add sql wild card char to handle new cases - // that we don't know about. - domain = "%"; - break; - } - } - - String eid = new String(decodedBytes, 0, spacePosn); - String email = new String(decodedBytes, spacePosn + 1, emailLen); - if (debug) Log.d(TAG, "eid= " + eid ); - if (debug) Log.d(TAG, "email= " + email ); - if (debug) Log.d(TAG, "domain=" + domain ); - if (domain != null) { - email += domain; - } - - return new String[] { eid, email }; - } - } - } catch (RuntimeException e) { - Log.w(TAG, "Punting malformed URI " + uri); - } - return null; - } - @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); Intent intent = getIntent(); if (intent != null) { - Uri uri = intent.getData(); - if (uri != null) { - String[] eidParts = extractEidAndEmail(uri); - if (eidParts == null) { - Log.i(TAG, "Could not find event for uri: " +uri); - } else { - final String syncId = eidParts[0]; - final String ownerAccount = eidParts[1]; - if (debug) Log.d(TAG, "eidParts=" + syncId + "/" + ownerAccount); - final String selection = Events._SYNC_ID + " LIKE \"%" + syncId + "\" AND " - + Calendars.OWNER_ACCOUNT + " LIKE \"" + ownerAccount + "\""; - - if (debug) Log.d(TAG, "selection: " + selection); - Cursor eventCursor = getContentResolver().query(Events.CONTENT_URI, - EVENT_PROJECTION, selection, null, - Calendars.CALENDAR_ACCESS_LEVEL + " desc"); - if (debug) Log.d(TAG, "Found: " + eventCursor.getCount()); - - if (eventCursor == null || eventCursor.getCount() == 0) { - Log.i(TAG, "NOTE: found no matches on event with id='" + syncId + "'"); - return; - } - Log.i(TAG, "NOTE: found " + eventCursor.getCount() - + " matches on event with id='" + syncId + "'"); - // Don't print eidPart[1] as it contains the user's PII - - try { - // Get info from Cursor - while (eventCursor.moveToNext()) { - int eventId = eventCursor.getInt(EVENT_INDEX_ID); - long startMillis = eventCursor.getLong(EVENT_INDEX_START); - long endMillis = eventCursor.getLong(EVENT_INDEX_END); - if (debug) Log.d(TAG, "_id: " + eventCursor.getLong(EVENT_INDEX_ID)); - if (debug) Log.d(TAG, "startMillis: " + startMillis); - if (debug) Log.d(TAG, "endMillis: " + endMillis); - - if (endMillis == 0) { - String duration = eventCursor.getString(EVENT_INDEX_DURATION); - if (debug) Log.d(TAG, "duration: " + duration); - if (TextUtils.isEmpty(duration)) { - continue; - } - - try { - Duration d = new Duration(); - d.parse(duration); - endMillis = startMillis + d.getMillis(); - if (debug) Log.d(TAG, "startMillis! " + startMillis); - if (debug) Log.d(TAG, "endMillis! " + endMillis); - if (endMillis < startMillis) { - continue; - } - } catch (DateException e) { - if (debug) Log.d(TAG, "duration:" + e.toString()); - continue; - } - } - - // Pick up attendee status action from uri clicked - int attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; - if ("RESPOND".equals(uri.getQueryParameter("action"))) { - try { - switch (Integer.parseInt(uri.getQueryParameter("rst"))) { - case 1: // Yes - attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; - break; - case 2: // No - attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED; - break; - case 3: // Maybe - attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; - break; - } - } catch (NumberFormatException e) { - // ignore this error as if the response code - // wasn't in the uri. - } - } - - final Uri calendarUri = ContentUris.withAppendedId( - Events.CONTENT_URI, eventId); - intent = new Intent(Intent.ACTION_VIEW, calendarUri); - intent.setClass(this, EventInfoActivity.class); - intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis); - intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis); - if (attendeeStatus == Attendees.ATTENDEE_STATUS_NONE) { - startActivity(intent); - } else { - updateSelfAttendeeStatus( - eventId, ownerAccount, attendeeStatus, intent); - } - finish(); - return; - } - } finally { - eventCursor.close(); - } - } - } - - // Can't handle the intent. Pass it on to the next Activity. + // Pass it on to the next Activity. try { startNextMatchingActivity(intent); } catch (ActivityNotFoundException ex) { @@ -248,44 +38,4 @@ public class GoogleCalendarUriIntentFilter extends Activity { } finish(); } - - private void updateSelfAttendeeStatus( - int eventId, String ownerAccount, final int status, final Intent intent) { - final ContentResolver cr = getContentResolver(); - final AsyncQueryHandler queryHandler = - new AsyncQueryHandler(cr) { - @Override - protected void onUpdateComplete(int token, Object cookie, int result) { - if (result == 0) { - Log.w(TAG, "No rows updated - starting event viewer"); - intent.putExtra(Attendees.ATTENDEE_STATUS, status); - startActivity(intent); - return; - } - final int toastId; - switch (status) { - case Attendees.ATTENDEE_STATUS_ACCEPTED: - toastId = R.string.rsvp_accepted; - break; - case Attendees.ATTENDEE_STATUS_DECLINED: - toastId = R.string.rsvp_declined; - break; - case Attendees.ATTENDEE_STATUS_TENTATIVE: - toastId = R.string.rsvp_tentative; - break; - default: - return; - } - Toast.makeText(GoogleCalendarUriIntentFilter.this, - toastId, Toast.LENGTH_LONG).show(); - } - }; - final ContentValues values = new ContentValues(); - values.put(Attendees.ATTENDEE_STATUS, status); - queryHandler.startUpdate(0, null, - Attendees.CONTENT_URI, - values, - Attendees.ATTENDEE_EMAIL + "=? AND " + Attendees.EVENT_ID + "=?", - new String[]{ ownerAccount, String.valueOf(eventId) }); - } } diff --git a/src/com/android/calendar/OtherPreferences.java b/src/com/android/calendar/OtherPreferences.java index 2dd855d0..a59d3f46 100644 --- a/src/com/android/calendar/OtherPreferences.java +++ b/src/com/android/calendar/OtherPreferences.java @@ -72,7 +72,6 @@ public class OtherPreferences extends PreferenceFragment implements OnPreferenc private static final String format12Hour = "%I:%M%P"; private Preference mCopyDb; - private ListPreference mSkipReminders; private CheckBoxPreference mQuietHours; private Preference mQuietHoursStart; private Preference mQuietHoursEnd; @@ -96,13 +95,6 @@ public class OtherPreferences extends PreferenceFragment implements OnPreferenc addPreferencesFromResource(R.xml.other_preferences); mCopyDb = findPreference(KEY_OTHER_COPY_DB); - mSkipReminders = (ListPreference) findPreference(KEY_OTHER_REMINDERS_RESPONDED); - String skipPreferencesValue = null; - if (mSkipReminders != null) { - skipPreferencesValue = mSkipReminders.getValue(); - mSkipReminders.setOnPreferenceChangeListener(this); - } - updateSkipRemindersSummary(skipPreferencesValue); Activity activity = getActivity(); if (activity == null) { @@ -138,13 +130,6 @@ public class OtherPreferences extends PreferenceFragment implements OnPreferenc @Override public boolean onPreferenceChange(Preference preference, Object objValue) { - final String key = preference.getKey(); - - if (KEY_OTHER_REMINDERS_RESPONDED.equals(key)) { - String value = String.valueOf(objValue); - updateSkipRemindersSummary(value); - } - return true; } @@ -222,30 +207,4 @@ public class OtherPreferences extends PreferenceFragment implements OnPreferenc String format = mIs24HourMode? format24Hour : format12Hour; return time.format(format); } - - /** - * Update the summary for the SkipReminders preference. - * @param value The corresponding value of which summary to set. If null, the default summary - * will be set, and the value will be set accordingly too. - */ - private void updateSkipRemindersSummary(String value) { - if (mSkipReminders != null) { - // Default to "declined". Must match with R.array.preferences_skip_reminders_values. - int index = 0; - - CharSequence[] values = mSkipReminders.getEntryValues(); - CharSequence[] entries = mSkipReminders.getEntries(); - for(int value_i = 0; value_i < values.length; value_i++) { - if (values[value_i].equals(value)) { - index = value_i; - break; - } - } - mSkipReminders.setSummary(entries[index].toString()); - if (value == null) { - // Value was not known ahead of time, so the default value will be set. - mSkipReminders.setValue(values[index].toString()); - } - } - } } diff --git a/src/com/android/calendar/QuickResponseSettings.java b/src/com/android/calendar/QuickResponseSettings.java deleted file mode 100644 index 1e4e7547..00000000 --- a/src/com/android/calendar/QuickResponseSettings.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.os.Bundle; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.util.Log; - -import java.util.Arrays; - -/** - * Fragment to facilitate editing of quick responses when emailing guests - * - */ -public class QuickResponseSettings extends PreferenceFragment implements OnPreferenceChangeListener { - private static final String TAG = "QuickResponseSettings"; - - EditTextPreference[] mEditTextPrefs; - String[] mResponses; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getActivity()); - ps.setTitle(R.string.quick_response_settings_title); - - mResponses = Utils.getQuickResponses(getActivity()); - - if (mResponses != null) { - mEditTextPrefs = new EditTextPreference[mResponses.length]; - - Arrays.sort(mResponses); - int i = 0; - for (String response : mResponses) { - EditTextPreference et = new EditTextPreference(getActivity()); - et.setDialogTitle(R.string.quick_response_settings_edit_title); - et.setTitle(response); // Display Text - et.setText(response); // Value to edit - et.setOnPreferenceChangeListener(this); - mEditTextPrefs[i++] = et; - ps.addPreference(et); - } - } else { - Log.wtf(TAG, "No responses found"); - } - setPreferenceScreen(ps); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - ((CalendarSettingsActivity) activity).hideMenuButtons(); - } - - @Override - public void onResume() { - super.onResume(); - CalendarSettingsActivity activity = (CalendarSettingsActivity) getActivity(); - if (!activity.isMultiPane()) { - activity.setTitle(R.string.quick_response_settings_title); - } - } - - // Implements OnPreferenceChangeListener - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - for (int i = 0; i < mEditTextPrefs.length; i++) { - if (mEditTextPrefs[i].compareTo(preference) == 0) { - if (!mResponses[i].equals(newValue)) { - mResponses[i] = (String) newValue; - mEditTextPrefs[i].setTitle(mResponses[i]); - mEditTextPrefs[i].setText(mResponses[i]); - Utils.setSharedPreference(getActivity(), Utils.KEY_QUICK_RESPONSES, mResponses); - } - return true; - } - } - return false; - } -} diff --git a/src/com/android/calendar/RecipientAdapter.java b/src/com/android/calendar/RecipientAdapter.java deleted file mode 100644 index bbff0486..00000000 --- a/src/com/android/calendar/RecipientAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.ex.chips.BaseRecipientAdapter; - -import android.accounts.Account; -import android.content.Context; - -public class RecipientAdapter extends BaseRecipientAdapter { - public RecipientAdapter(Context context) { - super(context); - } - - /** - * Set the account when known. Causes the search to prioritize contacts from - * that account. - */ - public void setAccount(Account account) { - if (account != null) { - // TODO: figure out how to infer the contacts account - // type from the email account - super.setAccount(new android.accounts.Account(account.name, "unknown")); - } - } -} diff --git a/src/com/android/calendar/SearchActivity.java b/src/com/android/calendar/SearchActivity.java deleted file mode 100644 index 929e4a61..00000000 --- a/src/com/android/calendar/SearchActivity.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.calendar; - -import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; -import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; - -import android.app.ActionBar; -import android.app.Activity; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.app.SearchManager; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Intent; -import android.database.ContentObserver; -import android.graphics.drawable.LayerDrawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.provider.CalendarContract.Events; -import android.provider.SearchRecentSuggestions; -import android.text.format.Time; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MenuItem.OnActionExpandListener; -import android.widget.SearchView; - -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.agenda.AgendaFragment; - -public class SearchActivity extends Activity implements CalendarController.EventHandler, - SearchView.OnQueryTextListener, OnActionExpandListener { - - private static final String TAG = SearchActivity.class.getSimpleName(); - - private static final boolean DEBUG = false; - - private static final int HANDLER_KEY = 0; - - protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; - - protected static final String BUNDLE_KEY_RESTORE_SEARCH_QUERY = - "key_restore_search_query"; - - // display event details to the side of the event list - private boolean mShowEventDetailsWithAgenda; - private static boolean mIsMultipane; - - private CalendarController mController; - - private EventInfoFragment mEventInfoFragment; - - private long mCurrentEventId = -1; - - private String mQuery; - - private SearchView mSearchView; - - private DeleteEventHelper mDeleteEventHelper; - - private Handler mHandler; - private BroadcastReceiver mTimeChangesReceiver; - private ContentResolver mContentResolver; - - private final ContentObserver mObserver = new ContentObserver(new Handler()) { - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(boolean selfChange) { - eventsChanged(); - } - }; - - // runs when a timezone was changed and updates the today icon - private final Runnable mTimeChangesUpdater = new Runnable() { - @Override - public void run() { - Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, - Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); - SearchActivity.this.invalidateOptionsMenu(); - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - // This needs to be created before setContentView - mController = CalendarController.getInstance(this); - mHandler = new Handler(); - - mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config); - mShowEventDetailsWithAgenda = - Utils.getConfigBool(this, R.bool.show_event_details_with_agenda); - - setContentView(R.layout.search); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - mContentResolver = getContentResolver(); - - if (mIsMultipane) { - getActionBar().setDisplayOptions( - ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); - } else { - getActionBar().setDisplayOptions(0, - ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME); - } - - // Must be the first to register because this activity can modify the - // list of event handlers in it's handle method. This affects who the - // rest of the handlers the controller dispatches to are. - mController.registerEventHandler(HANDLER_KEY, this); - - mDeleteEventHelper = new DeleteEventHelper(this, this, - false /* don't exit when done */); - - long millis = 0; - if (icicle != null) { - // Returns 0 if key not found - millis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME); - if (DEBUG) { - Log.v(TAG, "Restore value from icicle: " + millis); - } - } - if (millis == 0) { - // Didn't find a time in the bundle, look in intent or current time - millis = Utils.timeFromIntentInMillis(getIntent()); - } - - Intent intent = getIntent(); - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query; - if (icicle != null && icicle.containsKey(BUNDLE_KEY_RESTORE_SEARCH_QUERY)) { - query = icicle.getString(BUNDLE_KEY_RESTORE_SEARCH_QUERY); - } else { - query = intent.getStringExtra(SearchManager.QUERY); - } - if ("TARDIS".equalsIgnoreCase(query)) { - Utils.tardis(); - } - initFragments(millis, query); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mController.deregisterAllEventHandlers(); - CalendarController.removeInstance(this); - } - - private void initFragments(long timeMillis, String query) { - FragmentManager fragmentManager = getFragmentManager(); - FragmentTransaction ft = fragmentManager.beginTransaction(); - - AgendaFragment searchResultsFragment = new AgendaFragment(timeMillis, true); - ft.replace(R.id.search_results, searchResultsFragment); - mController.registerEventHandler(R.id.search_results, searchResultsFragment); - - ft.commit(); - Time t = new Time(); - t.set(timeMillis); - search(query, t); - } - - private void showEventInfo(EventInfo event) { - if (mShowEventDetailsWithAgenda) { - FragmentManager fragmentManager = getFragmentManager(); - FragmentTransaction ft = fragmentManager.beginTransaction(); - - mEventInfoFragment = new EventInfoFragment(this, event.id, - event.startTime.toMillis(false), event.endTime.toMillis(false), - event.getResponse(), false, EventInfoFragment.DIALOG_WINDOW_STYLE, - null /* No reminders to explicitly pass in. */); - ft.replace(R.id.agenda_event_info, mEventInfoFragment); - ft.commit(); - } else { - Intent intent = new Intent(Intent.ACTION_VIEW); - Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id); - intent.setData(eventUri); - intent.setClass(this, EventInfoActivity.class); - intent.putExtra(EXTRA_EVENT_BEGIN_TIME, - event.startTime != null ? event.startTime.toMillis(true) : -1); - intent.putExtra( - EXTRA_EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true) : -1); - startActivity(intent); - } - mCurrentEventId = event.id; - } - - private void search(String searchQuery, Time goToTime) { - // save query in recent queries - SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, - Utils.getSearchAuthority(this), - CalendarRecentSuggestionsProvider.MODE); - suggestions.saveRecentQuery(searchQuery, null); - - - EventInfo searchEventInfo = new EventInfo(); - searchEventInfo.eventType = EventType.SEARCH; - searchEventInfo.query = searchQuery; - searchEventInfo.viewType = ViewType.AGENDA; - if (goToTime != null) { - searchEventInfo.startTime = goToTime; - } - mController.sendEvent(this, searchEventInfo); - mQuery = searchQuery; - if (mSearchView != null) { - mSearchView.setQuery(mQuery, false); - mSearchView.clearFocus(); - } - } - - private void deleteEvent(long eventId, long startMillis, long endMillis) { - mDeleteEventHelper.delete(startMillis, endMillis, eventId, -1); - if (mIsMultipane && mEventInfoFragment != null - && eventId == mCurrentEventId) { - FragmentManager fragmentManager = getFragmentManager(); - FragmentTransaction ft = fragmentManager.beginTransaction(); - ft.remove(mEventInfoFragment); - ft.commit(); - mEventInfoFragment = null; - mCurrentEventId = -1; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.search_title_bar, menu); - - // replace the default top layer drawable of the today icon with a custom drawable - // that shows the day of the month of today - MenuItem menuItem = menu.findItem(R.id.action_today); - if (Utils.isJellybeanOrLater()) { - LayerDrawable icon = (LayerDrawable) menuItem.getIcon(); - Utils.setTodayIcon( - icon, this, Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); - } else { - menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light); - } - - MenuItem item = menu.findItem(R.id.action_search); - item.expandActionView(); - item.setOnActionExpandListener(this); - mSearchView = (SearchView) item.getActionView(); - Utils.setUpSearchView(mSearchView, this); - mSearchView.setQuery(mQuery, false); - mSearchView.clearFocus(); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - Time t = null; - final int itemId = item.getItemId(); - if (itemId == R.id.action_today) { - t = new Time(); - t.setToNow(); - mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT); - return true; - } else if (itemId == R.id.action_search) { - return false; - } else if (itemId == R.id.action_settings) { - mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0); - return true; - } else if (itemId == android.R.id.home) { - Utils.returnToCalendarHome(this); - return true; - } else { - return false; - } - } - - @Override - protected void onNewIntent(Intent intent) { - // From the Android Dev Guide: "It's important to note that when - // onNewIntent(Intent) is called, the Activity has not been restarted, - // so the getIntent() method will still return the Intent that was first - // received with onCreate(). This is why setIntent(Intent) is called - // inside onNewIntent(Intent) (just in case you call getIntent() at a - // later time)." - setIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - search(query, null); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime()); - outState.putString(BUNDLE_KEY_RESTORE_SEARCH_QUERY, mQuery); - } - - @Override - protected void onResume() { - super.onResume(); - - Utils.setMidnightUpdater( - mHandler, mTimeChangesUpdater, Utils.getTimeZone(this, mTimeChangesUpdater)); - // Make sure the today icon is up to date - invalidateOptionsMenu(); - mTimeChangesReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater); - mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); - // We call this in case the user changed the time zone - eventsChanged(); - } - - @Override - protected void onPause() { - super.onPause(); - Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater); - Utils.clearTimeChangesReceiver(this, mTimeChangesReceiver); - mContentResolver.unregisterContentObserver(mObserver); - } - - @Override - public void eventsChanged() { - mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); - } - - @Override - public long getSupportedEventTypes() { - return EventType.VIEW_EVENT | EventType.DELETE_EVENT; - } - - @Override - public void handleEvent(EventInfo event) { - long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); - if (event.eventType == EventType.VIEW_EVENT) { - showEventInfo(event); - } else if (event.eventType == EventType.DELETE_EVENT) { - deleteEvent(event.id, event.startTime.toMillis(false), endTime); - } - } - - @Override - public boolean onQueryTextChange(String newText) { - return false; - } - - @Override - public boolean onQueryTextSubmit(String query) { - mQuery = query; - mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query, - getComponentName()); - return false; - } - - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - Utils.returnToCalendarHome(this); - return false; - } -} diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java index 41961852..cc55c999 100644 --- a/src/com/android/calendar/Utils.java +++ b/src/com/android/calendar/Utils.java @@ -50,10 +50,8 @@ import android.text.format.Time; import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.Log; -import android.widget.SearchView; import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.CalendarEventModel.ReminderEntry; import com.android.calendar.CalendarUtils.TimeZoneUtils; import java.util.ArrayList; @@ -254,13 +252,6 @@ public class Utils { } /** - * Gets the intent action for telling the widget to update. - */ - public static String getSearchAuthority(Context context) { - return context.getPackageName() + ".CalendarRecentSuggestionsProvider"; - } - - /** * Writes a new home time zone to the db. Updates the home time zone in the * db asynchronously and updates the local cache. Sending a time zone of * **tbd** will cause it to be set to the device's time zone. null or empty @@ -401,41 +392,6 @@ public class Utils { prefs.edit().remove(key).apply(); } - // The backed up ring tone preference should not used because it is a device - // specific Uri. The preference now lives in a separate non-backed-up - // shared_pref file (SHARED_PREFS_NAME_NO_BACKUP). The preference in the old - // backed-up shared_pref file (SHARED_PREFS_NAME) is used only to control the - // default value when the ringtone dialog opens up. - // - // At backup manager "restore" time (which should happen before launcher - // comes up for the first time), the value will be set/reset to default - // ringtone. - public static String getRingTonePreference(Context context) { - SharedPreferences prefs = context.getSharedPreferences( - GeneralPreferences.SHARED_PREFS_NAME_NO_BACKUP, Context.MODE_PRIVATE); - String ringtone = prefs.getString(GeneralPreferences.KEY_ALERTS_RINGTONE, null); - - // If it hasn't been populated yet, that means new code is running for - // the first time and restore hasn't happened. Migrate value from - // backed-up shared_pref to non-shared_pref. - if (ringtone == null) { - // Read from the old place with a default of DEFAULT_RINGTONE - ringtone = getSharedPreference(context, GeneralPreferences.KEY_ALERTS_RINGTONE, - GeneralPreferences.DEFAULT_RINGTONE); - - // Write it to the new place - setRingTonePreference(context, ringtone); - } - - return ringtone; - } - - public static void setRingTonePreference(Context context, String value) { - SharedPreferences prefs = context.getSharedPreferences( - GeneralPreferences.SHARED_PREFS_NAME_NO_BACKUP, Context.MODE_PRIVATE); - prefs.edit().putString(GeneralPreferences.KEY_ALERTS_RINGTONE, value).apply(); - } - /** * Save default agenda/day/week/month view for next time * @@ -774,39 +730,6 @@ public class Utils { return recycle.normalize(true); } - /** - * Scan through a cursor of calendars and check if names are duplicated. - * This travels a cursor containing calendar display names and fills in the - * provided map with whether or not each name is repeated. - * - * @param isDuplicateName The map to put the duplicate check results in. - * @param cursor The query of calendars to check - * @param nameIndex The column of the query that contains the display name - */ - public static void checkForDuplicateNames( - Map<String, Boolean> isDuplicateName, Cursor cursor, int nameIndex) { - isDuplicateName.clear(); - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - String displayName = cursor.getString(nameIndex); - // Set it to true if we've seen this name before, false otherwise - if (displayName != null) { - isDuplicateName.put(displayName, isDuplicateName.containsKey(displayName)); - } - } - } - - /** - * Null-safe object comparison - * - * @param s1 - * @param s2 - * @return - */ - public static boolean equals(Object o1, Object o2) { - return o1 == null ? o2 == null : o1.equals(o2); - } - public static void setAllowWeekForDetailView(boolean allowWeekView) { mAllowWeekForDetailView = allowWeekView; } @@ -1305,19 +1228,6 @@ public class Utils { } /** - * This sets up a search view to use Calendar's search suggestions provider - * and to allow refining the search. - * - * @param view The {@link SearchView} to set up - * @param act The activity using the view - */ - public static void setUpSearchView(SearchView view, Activity act) { - SearchManager searchManager = (SearchManager) act.getSystemService(Context.SEARCH_SERVICE); - view.setSearchableInfo(searchManager.getSearchableInfo(act.getComponentName())); - view.setQueryRefinementEnabled(true); - } - - /** * Given a context and a time in millis since unix epoch figures out the * correct week of the year for that time. * @@ -1531,116 +1441,6 @@ public class Utils { } /** - * Create an intent for emailing attendees of an event. - * - * @param resources The resources for translating strings. - * @param eventTitle The title of the event to use as the email subject. - * @param body The default text for the email body. - * @param toEmails The list of emails for the 'to' line. - * @param ccEmails The list of emails for the 'cc' line. - * @param ownerAccount The owner account to use as the email sender. - */ - public static Intent createEmailAttendeesIntent(Resources resources, String eventTitle, - String body, List<String> toEmails, List<String> ccEmails, String ownerAccount) { - List<String> toList = toEmails; - List<String> ccList = ccEmails; - if (toEmails.size() <= 0) { - if (ccEmails.size() <= 0) { - // TODO: Return a SEND intent if no one to email to, to at least populate - // a draft email with the subject (and no recipients). - throw new IllegalArgumentException("Both toEmails and ccEmails are empty."); - } - - // Email app does not work with no "to" recipient. Move all 'cc' to 'to' - // in this case. - toList = ccEmails; - ccList = null; - } - - // Use the event title as the email subject (prepended with 'Re: '). - String subject = null; - if (eventTitle != null) { - subject = resources.getString(R.string.email_subject_prefix) + eventTitle; - } - - // Use the SENDTO intent with a 'mailto' URI, because using SEND will cause - // the picker to show apps like text messaging, which does not make sense - // for email addresses. We put all data in the URI instead of using the extra - // Intent fields (ie. EXTRA_CC, etc) because some email apps might not handle - // those (though gmail does). - Uri.Builder uriBuilder = new Uri.Builder(); - uriBuilder.scheme("mailto"); - - // We will append the first email to the 'mailto' field later (because the - // current state of the Email app requires it). Add the remaining 'to' values - // here. When the email codebase is updated, we can simplify this. - if (toList.size() > 1) { - for (int i = 1; i < toList.size(); i++) { - // The Email app requires repeated parameter settings instead of - // a single comma-separated list. - uriBuilder.appendQueryParameter("to", toList.get(i)); - } - } - - // Add the subject parameter. - if (subject != null) { - uriBuilder.appendQueryParameter("subject", subject); - } - - // Add the subject parameter. - if (body != null) { - uriBuilder.appendQueryParameter("body", body); - } - - // Add the cc parameters. - if (ccList != null && ccList.size() > 0) { - for (String email : ccList) { - uriBuilder.appendQueryParameter("cc", email); - } - } - - // Insert the first email after 'mailto:' in the URI manually since Uri.Builder - // doesn't seem to have a way to do this. - String uri = uriBuilder.toString(); - if (uri.startsWith("mailto:")) { - StringBuilder builder = new StringBuilder(uri); - builder.insert(7, Uri.encode(toList.get(0))); - uri = builder.toString(); - } - - // Start the email intent. Email from the account of the calendar owner in case there - // are multiple email accounts. - Intent emailIntent = new Intent(android.content.Intent.ACTION_SENDTO, Uri.parse(uri)); - emailIntent.putExtra("fromAccountString", ownerAccount); - - // Workaround a Email bug that overwrites the body with this intent extra. If not - // set, it clears the body. - if (body != null) { - emailIntent.putExtra(Intent.EXTRA_TEXT, body); - } - - return Intent.createChooser(emailIntent, resources.getString(R.string.email_picker_label)); - } - - /** - * Example fake email addresses used as attendee emails are resources like conference rooms, - * or another calendar, etc. These all end in "calendar.google.com". - */ - public static boolean isValidEmail(String email) { - return email != null && !email.endsWith(MACHINE_GENERATED_ADDRESS); - } - - /** - * Returns true if: - * (1) the email is not a resource like a conference room or another calendar. - * Catch most of these by filtering out suffix calendar.google.com. - * (2) the email is not equal to the sync account to prevent mailing himself. - */ - public static boolean isEmailableFrom(String email, String syncAccountName) { - return Utils.isValidEmail(email) && !email.equals(syncAccountName); - } - - /** * Inserts a drawable with today's day into the today's icon in the option menu * @param icon - today's icon from the options menu */ @@ -1663,43 +1463,6 @@ public class Utils { icon.setDrawableByLayerId(R.id.today_icon_day, today); } - private static class CalendarBroadcastReceiver extends BroadcastReceiver { - - Runnable mCallBack; - - public CalendarBroadcastReceiver(Runnable callback) { - super(); - mCallBack = callback; - } - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED) || - intent.getAction().equals(Intent.ACTION_TIME_CHANGED) || - intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED) || - intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { - if (mCallBack != null) { - mCallBack.run(); - } - } - } - } - - public static BroadcastReceiver setTimeChangesReceiver(Context c, Runnable callback) { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_DATE_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_LOCALE_CHANGED); - - CalendarBroadcastReceiver r = new CalendarBroadcastReceiver(callback); - c.registerReceiver(r, filter); - return r; - } - - public static void clearTimeChangesReceiver(Context c, BroadcastReceiver r) { - c.unregisterReceiver(r); - } - /** * Get a list of quick responses used for emailing guests from the * SharedPreferences. If not are found, get the hard coded ones that shipped @@ -1733,394 +1496,4 @@ public class Utils { } return sVersion; } - - /** - * Checks the server for an updated list of Calendars (in the background). - * - * If a Calendar is added on the web (and it is selected and not - * hidden) then it will be added to the list of calendars on the phone - * (when this finishes). When a new calendar from the - * web is added to the phone, then the events for that calendar are also - * downloaded from the web. - * - * This sync is done automatically in the background when the - * SelectCalendars activity and fragment are started. - * - * @param account - The account to sync. May be null to sync all accounts. - */ - public static void startCalendarMetafeedSync(Account account) { - Bundle extras = new Bundle(); - extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - extras.putBoolean("metafeedonly", true); - ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras); - } - - /** - * Replaces stretches of text that look like addresses and phone numbers with clickable - * links. If lastDitchGeo is true, then if no links are found in the textview, the entire - * string will be converted to a single geo link. Any spans that may have previously been - * in the text will be cleared out. - * <p> - * This is really just an enhanced version of Linkify.addLinks(). - * - * @param text - The string to search for links. - * @param lastDitchGeo - If no links are found, turn the entire string into one geo link. - * @return Spannable object containing the list of URL spans found. - */ - public static Spannable extendedLinkify(String text, boolean lastDitchGeo) { - // We use a copy of the string argument so it's available for later if necessary. - Spannable spanText = SpannableString.valueOf(text); - - /* - * If the text includes a street address like "1600 Amphitheater Parkway, 94043", - * the current Linkify code will identify "94043" as a phone number and invite - * you to dial it (and not provide a map link for the address). For outside US, - * use Linkify result iff it spans the entire text. Otherwise send the user to maps. - */ - String defaultPhoneRegion = System.getProperty("user.region", "US"); - if (!defaultPhoneRegion.equals("US")) { - Linkify.addLinks(spanText, Linkify.ALL); - - // If Linkify links the entire text, use that result. - URLSpan[] spans = spanText.getSpans(0, spanText.length(), URLSpan.class); - if (spans.length == 1) { - int linkStart = spanText.getSpanStart(spans[0]); - int linkEnd = spanText.getSpanEnd(spans[0]); - if (linkStart <= indexFirstNonWhitespaceChar(spanText) && - linkEnd >= indexLastNonWhitespaceChar(spanText) + 1) { - return spanText; - } - } - - // Otherwise, to be cautious and to try to prevent false positives, reset the spannable. - spanText = SpannableString.valueOf(text); - // If lastDitchGeo is true, default the entire string to geo. - if (lastDitchGeo && !text.isEmpty()) { - Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q="); - } - return spanText; - } - - /* - * For within US, we want to have better recognition of phone numbers without losing - * any of the existing annotations. Ideally this would be addressed by improving Linkify. - * For now we manage it as a second pass over the text. - * - * URIs and e-mail addresses are pretty easy to pick out of text. Phone numbers - * are a bit tricky because they have radically different formats in different - * countries, in terms of both the digits and the way in which they are commonly - * written or presented (e.g. the punctuation and spaces in "(650) 555-1212"). - * The expected format of a street address is defined in WebView.findAddress(). It's - * pretty narrowly defined, so it won't often match. - * - * The RFC 3966 specification defines the format of a "tel:" URI. - * - * Start by letting Linkify find anything that isn't a phone number. We have to let it - * run first because every invocation removes all previous URLSpan annotations. - * - * Ideally we'd use the external/libphonenumber routines, but those aren't available - * to unbundled applications. - */ - boolean linkifyFoundLinks = Linkify.addLinks(spanText, - Linkify.ALL & ~(Linkify.PHONE_NUMBERS)); - - /* - * Get a list of any spans created by Linkify, for the coordinate overlapping span check. - */ - URLSpan[] existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class); - - /* - * Check for coordinates. - * This must be done before phone numbers because longitude may look like a phone number. - */ - Matcher coordMatcher = COORD_PATTERN.matcher(spanText); - int coordCount = 0; - while (coordMatcher.find()) { - int start = coordMatcher.start(); - int end = coordMatcher.end(); - if (spanWillOverlap(spanText, existingSpans, start, end)) { - continue; - } - - URLSpan span = new URLSpan("geo:0,0?q=" + coordMatcher.group()); - spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - coordCount++; - } - - /* - * Update the list of existing spans, for the phone number overlapping span check. - */ - existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class); - - /* - * Search for phone numbers. - * - * Some URIs contain strings of digits that look like phone numbers. If both the URI - * scanner and the phone number scanner find them, we want the URI link to win. Since - * the URI scanner runs first, we just need to avoid creating overlapping spans. - */ - int[] phoneSequences = findNanpPhoneNumbers(text); - - /* - * Insert spans for the numbers we found. We generate "tel:" URIs. - */ - int phoneCount = 0; - for (int match = 0; match < phoneSequences.length / 2; match++) { - int start = phoneSequences[match*2]; - int end = phoneSequences[match*2 + 1]; - - if (spanWillOverlap(spanText, existingSpans, start, end)) { - continue; - } - - /* - * The Linkify code takes the matching span and strips out everything that isn't a - * digit or '+' sign. We do the same here. Extension numbers will get appended - * without a separator, but the dialer wasn't doing anything useful with ";ext=" - * anyway. - */ - - //String dialStr = phoneUtil.format(match.number(), - // PhoneNumberUtil.PhoneNumberFormat.RFC3966); - StringBuilder dialBuilder = new StringBuilder(); - for (int i = start; i < end; i++) { - char ch = spanText.charAt(i); - if (ch == '+' || Character.isDigit(ch)) { - dialBuilder.append(ch); - } - } - URLSpan span = new URLSpan("tel:" + dialBuilder.toString()); - - spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - phoneCount++; - } - - /* - * If lastDitchGeo, and no other links have been found, set the entire string as a geo link. - */ - if (lastDitchGeo && !text.isEmpty() && - !linkifyFoundLinks && phoneCount == 0 && coordCount == 0) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "No linkification matches, using geo default"); - } - Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q="); - } - - return spanText; - } - - private static int indexFirstNonWhitespaceChar(CharSequence str) { - for (int i = 0; i < str.length(); i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return i; - } - } - return -1; - } - - private static int indexLastNonWhitespaceChar(CharSequence str) { - for (int i = str.length() - 1; i >= 0; i--) { - if (!Character.isWhitespace(str.charAt(i))) { - return i; - } - } - return -1; - } - - /** - * Finds North American Numbering Plan (NANP) phone numbers in the input text. - * - * @param text The text to scan. - * @return A list of [start, end) pairs indicating the positions of phone numbers in the input. - */ - // @VisibleForTesting - static int[] findNanpPhoneNumbers(CharSequence text) { - ArrayList<Integer> list = new ArrayList<Integer>(); - - int startPos = 0; - int endPos = text.length() - NANP_MIN_DIGITS + 1; - if (endPos < 0) { - return new int[] {}; - } - - /* - * We can't just strip the whitespace out and crunch it down, because the whitespace - * is significant. March through, trying to figure out where numbers start and end. - */ - while (startPos < endPos) { - // skip whitespace - while (Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) { - startPos++; - } - if (startPos == endPos) { - break; - } - - // check for a match at this position - int matchEnd = findNanpMatchEnd(text, startPos); - if (matchEnd > startPos) { - list.add(startPos); - list.add(matchEnd); - startPos = matchEnd; // skip past match - } else { - // skip to next whitespace char - while (!Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) { - startPos++; - } - } - } - - int[] result = new int[list.size()]; - for (int i = list.size() - 1; i >= 0; i--) { - result[i] = list.get(i); - } - return result; - } - - /** - * Checks to see if there is a valid phone number in the input, starting at the specified - * offset. If so, the index of the last character + 1 is returned. The input is assumed - * to begin with a non-whitespace character. - * - * @return Exclusive end position, or -1 if not a match. - */ - private static int findNanpMatchEnd(CharSequence text, int startPos) { - /* - * A few interesting cases: - * 94043 # too short, ignore - * 123456789012 # too long, ignore - * +1 (650) 555-1212 # 11 digits, spaces - * (650) 555 5555 # Second space, only when first is present. - * (650) 555-1212, (650) 555-1213 # two numbers, return first - * 1-650-555-1212 # 11 digits with leading '1' - * *#650.555.1212#*! # 10 digits, include #*, ignore trailing '!' - * 555.1212 # 7 digits - * - * For the most part we want to break on whitespace, but it's common to leave a space - * between the initial '1' and/or after the area code. - */ - - // Check for "tel:" URI prefix. - if (text.length() > startPos+4 - && text.subSequence(startPos, startPos+4).toString().equalsIgnoreCase("tel:")) { - startPos += 4; - } - - int endPos = text.length(); - int curPos = startPos; - int foundDigits = 0; - char firstDigit = 'x'; - boolean foundWhiteSpaceAfterAreaCode = false; - - while (curPos <= endPos) { - char ch; - if (curPos < endPos) { - ch = text.charAt(curPos); - } else { - ch = 27; // fake invalid symbol at end to trigger loop break - } - - if (Character.isDigit(ch)) { - if (foundDigits == 0) { - firstDigit = ch; - } - foundDigits++; - if (foundDigits > NANP_MAX_DIGITS) { - // too many digits, stop early - return -1; - } - } else if (Character.isWhitespace(ch)) { - if ( (firstDigit == '1' && foundDigits == 4) || - (foundDigits == 3)) { - foundWhiteSpaceAfterAreaCode = true; - } else if (firstDigit == '1' && foundDigits == 1) { - } else if (foundWhiteSpaceAfterAreaCode - && ( (firstDigit == '1' && (foundDigits == 7)) || (foundDigits == 6))) { - } else { - break; - } - } else if (NANP_ALLOWED_SYMBOLS.indexOf(ch) == -1) { - break; - } - // else it's an allowed symbol - - curPos++; - } - - if ((firstDigit != '1' && (foundDigits == 7 || foundDigits == 10)) || - (firstDigit == '1' && foundDigits == 11)) { - // match - return curPos; - } - - return -1; - } - - /** - * Determines whether a new span at [start,end) will overlap with any existing span. - */ - private static boolean spanWillOverlap(Spannable spanText, URLSpan[] spanList, int start, - int end) { - if (start == end) { - // empty span, ignore - return false; - } - for (URLSpan span : spanList) { - int existingStart = spanText.getSpanStart(span); - int existingEnd = spanText.getSpanEnd(span); - if ((start >= existingStart && start < existingEnd) || - end > existingStart && end <= existingEnd) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - CharSequence seq = spanText.subSequence(start, end); - Log.v(TAG, "Not linkifying " + seq + " as phone number due to overlap"); - } - return true; - } - } - - return false; - } - - /** - * @param bundle The incoming bundle that contains the reminder info. - * @return ArrayList<ReminderEntry> of the reminder minutes and methods. - */ - public static ArrayList<ReminderEntry> readRemindersFromBundle(Bundle bundle) { - ArrayList<ReminderEntry> reminders = null; - - ArrayList<Integer> reminderMinutes = bundle.getIntegerArrayList( - EventInfoFragment.BUNDLE_KEY_REMINDER_MINUTES); - ArrayList<Integer> reminderMethods = bundle.getIntegerArrayList( - EventInfoFragment.BUNDLE_KEY_REMINDER_METHODS); - if (reminderMinutes == null || reminderMethods == null) { - if (reminderMinutes != null || reminderMethods != null) { - String nullList = (reminderMinutes == null? - "reminderMinutes" : "reminderMethods"); - Log.d(TAG, String.format("Error resolving reminders: %s was null", - nullList)); - } - return null; - } - - int numReminders = reminderMinutes.size(); - if (numReminders == reminderMethods.size()) { - // Only if the size of the reminder minutes we've read in is - // the same as the size of the reminder methods. Otherwise, - // something went wrong with bundling them. - reminders = new ArrayList<ReminderEntry>(numReminders); - for (int reminder_i = 0; reminder_i < numReminders; - reminder_i++) { - int minutes = reminderMinutes.get(reminder_i); - int method = reminderMethods.get(reminder_i); - reminders.add(ReminderEntry.valueOf(minutes, method)); - } - } else { - Log.d(TAG, String.format("Error resolving reminders." + - " Found %d reminderMinutes, but %d reminderMethods.", - numReminders, reminderMethods.size())); - } - - return reminders; - } - } diff --git a/src/com/android/calendar/agenda/AgendaAdapter.java b/src/com/android/calendar/agenda/AgendaAdapter.java deleted file mode 100644 index 9e492832..00000000 --- a/src/com/android/calendar/agenda/AgendaAdapter.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.agenda; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.provider.CalendarContract.Attendees; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.ResourceCursorAdapter; -import android.widget.TextView; - -import com.android.calendar.ColorChipView; -import com.android.calendar.R; -import com.android.calendar.Utils; - -import java.util.Formatter; -import java.util.Locale; -import java.util.TimeZone; - -public class AgendaAdapter extends ResourceCursorAdapter { - private final String mNoTitleLabel; - private final Resources mResources; - private final int mDeclinedColor; - private final int mStandardColor; - private final int mWhereColor; - private final int mWhereDeclinedColor; - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private final Formatter mFormatter; - private final StringBuilder mStringBuilder; - private float mScale; - - private int COLOR_CHIP_ALL_DAY_HEIGHT; - private int COLOR_CHIP_HEIGHT; - - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - notifyDataSetChanged(); - } - }; - - static class ViewHolder { - - public static final int DECLINED_RESPONSE = 0; - public static final int TENTATIVE_RESPONSE = 1; - public static final int ACCEPTED_RESPONSE = 2; - - /* Event */ - TextView title; - TextView when; - TextView where; - View selectedMarker; - LinearLayout textContainer; - long instanceId; - ColorChipView colorChip; - long startTimeMilli; - boolean allDay; - boolean grayed; - int julianDay; - } - - public AgendaAdapter(Context context, int resource) { - super(context, resource, null); - - mResources = context.getResources(); - mNoTitleLabel = mResources.getString(R.string.no_title_label); - mDeclinedColor = mResources.getColor(R.color.agenda_item_declined_color); - mStandardColor = mResources.getColor(R.color.agenda_item_standard_color); - mWhereDeclinedColor = mResources.getColor(R.color.agenda_item_where_declined_text_color); - mWhereColor = mResources.getColor(R.color.agenda_item_where_text_color); - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - - COLOR_CHIP_ALL_DAY_HEIGHT = mResources.getInteger(R.integer.color_chip_all_day_height); - COLOR_CHIP_HEIGHT = mResources.getInteger(R.integer.color_chip_height); - if (mScale == 0) { - mScale = mResources.getDisplayMetrics().density; - if (mScale != 1) { - COLOR_CHIP_ALL_DAY_HEIGHT *= mScale; - COLOR_CHIP_HEIGHT *= mScale; - } - } - - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ViewHolder holder = null; - - // Listview may get confused and pass in a different type of view since - // we keep shifting data around. Not a big problem. - Object tag = view.getTag(); - if (tag instanceof ViewHolder) { - holder = (ViewHolder) view.getTag(); - } - - if (holder == null) { - holder = new ViewHolder(); - view.setTag(holder); - holder.title = (TextView) view.findViewById(R.id.title); - holder.when = (TextView) view.findViewById(R.id.when); - holder.where = (TextView) view.findViewById(R.id.where); - holder.textContainer = (LinearLayout) - view.findViewById(R.id.agenda_item_text_container); - holder.selectedMarker = view.findViewById(R.id.selected_marker); - holder.colorChip = (ColorChipView)view.findViewById(R.id.agenda_item_color); - } - - holder.startTimeMilli = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - // Fade text if event was declined and set the color chip mode (response - boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - holder.allDay = allDay; - int selfAttendeeStatus = cursor.getInt(AgendaWindowAdapter.INDEX_SELF_ATTENDEE_STATUS); - if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) { - holder.title.setTextColor(mDeclinedColor); - holder.when.setTextColor(mWhereDeclinedColor); - holder.where.setTextColor(mWhereDeclinedColor); - holder.colorChip.setDrawStyle(ColorChipView.DRAW_FADED); - } else { - holder.title.setTextColor(mStandardColor); - holder.when.setTextColor(mWhereColor); - holder.where.setTextColor(mWhereColor); - if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) { - holder.colorChip.setDrawStyle(ColorChipView.DRAW_BORDER); - } else { - holder.colorChip.setDrawStyle(ColorChipView.DRAW_FULL); - } - } - - // Set the size of the color chip - ViewGroup.LayoutParams params = holder.colorChip.getLayoutParams(); - if (allDay) { - params.height = COLOR_CHIP_ALL_DAY_HEIGHT; - } else { - params.height = COLOR_CHIP_HEIGHT; - - } - holder.colorChip.setLayoutParams(params); - - // Deal with exchange events that the owner cannot respond to - int canRespond = cursor.getInt(AgendaWindowAdapter.INDEX_CAN_ORGANIZER_RESPOND); - if (canRespond == 0) { - String owner = cursor.getString(AgendaWindowAdapter.INDEX_OWNER_ACCOUNT); - String organizer = cursor.getString(AgendaWindowAdapter.INDEX_ORGANIZER); - if (owner.equals(organizer)) { - holder.colorChip.setDrawStyle(ColorChipView.DRAW_FULL); - holder.title.setTextColor(mStandardColor); - holder.when.setTextColor(mStandardColor); - holder.where.setTextColor(mStandardColor); - } - } - - TextView title = holder.title; - TextView when = holder.when; - TextView where = holder.where; - - holder.instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID); - - /* Calendar Color */ - int color = Utils.getDisplayColorFromColor(cursor.getInt(AgendaWindowAdapter.INDEX_COLOR)); - holder.colorChip.setColor(color); - - // What - String titleString = cursor.getString(AgendaWindowAdapter.INDEX_TITLE); - if (titleString == null || titleString.length() == 0) { - titleString = mNoTitleLabel; - } - title.setText(titleString); - - // When - long begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - long end = cursor.getLong(AgendaWindowAdapter.INDEX_END); - String eventTz = cursor.getString(AgendaWindowAdapter.INDEX_TIME_ZONE); - int flags = 0; - String whenString; - // It's difficult to update all the adapters so just query this each - // time we need to build the view. - String tzString = Utils.getTimeZone(context, mTZUpdater); - if (allDay) { - tzString = Time.TIMEZONE_UTC; - } else { - flags = DateUtils.FORMAT_SHOW_TIME; - } - if (DateFormat.is24HourFormat(context)) { - flags |= DateUtils.FORMAT_24HOUR; - } - mStringBuilder.setLength(0); - whenString = DateUtils.formatDateRange(context, mFormatter, begin, end, flags, tzString) - .toString(); - if (!allDay && !TextUtils.equals(tzString, eventTz)) { - String displayName; - // Figure out if this is in DST - Time date = new Time(tzString); - date.set(begin); - - TimeZone tz = TimeZone.getTimeZone(tzString); - if (tz == null || tz.getID().equals("GMT")) { - displayName = tzString; - } else { - displayName = tz.getDisplayName(date.isDst != 0, TimeZone.SHORT); - } - whenString += " (" + displayName + ")"; - } - when.setText(whenString); - - /* Recurring event icon is removed - String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE); - if (!TextUtils.isEmpty(rrule)) { - 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(AgendaWindowAdapter.INDEX_RRULE); - if (!TextUtils.isEmpty(rrule)) { - repeatContainer.setVisibility(View.VISIBLE); - } else { - repeatContainer.setVisibility(View.GONE); - } - */ - - /* - // Reminder - boolean hasAlarm = cursor.getInt(AgendaWindowAdapter.INDEX_HAS_ALARM) != 0; - if (hasAlarm) { - updateReminder(view, context, begin, cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID)); - } - */ - - // Where - String whereString = cursor.getString(AgendaWindowAdapter.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/agenda/AgendaByDayAdapter.java b/src/com/android/calendar/agenda/AgendaByDayAdapter.java deleted file mode 100644 index 1f03740a..00000000 --- a/src/com/android/calendar/agenda/AgendaByDayAdapter.java +++ /dev/null @@ -1,684 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.agenda; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Typeface; -import android.text.TextUtils; -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 com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; - -import java.util.ArrayList; -import java.util.Formatter; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; - -public class AgendaByDayAdapter extends BaseAdapter { - private static final int TYPE_DAY = 0; - private static final int TYPE_MEETING = 1; - 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 mTmpTime; - private String mTimeZone; - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private final Formatter mFormatter; - private final StringBuilder mStringBuilder; - - static class ViewHolder { - TextView dayView; - TextView dateView; - int julianDay; - boolean grayed; - } - - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - mTimeZone = Utils.getTimeZone(mContext, this); - mTmpTime = new Time(mTimeZone); - notifyDataSetChanged(); - } - }; - - public AgendaByDayAdapter(Context context) { - mContext = context; - mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - mTimeZone = Utils.getTimeZone(context, mTZUpdater); - mTmpTime = new Time(mTimeZone); - } - - public long getInstanceId(int position) { - if (mRowInfo == null || position >= mRowInfo.size()) { - return -1; - } - return mRowInfo.get(position).mInstanceId; - } - - public long getStartTime(int position) { - if (mRowInfo == null || position >= mRowInfo.size()) { - return -1; - } - return mRowInfo.get(position).mEventStartTimeMilli; - } - - - // Returns the position of a header of a specific item - public int getHeaderPosition(int position) { - if (mRowInfo == null || position >= mRowInfo.size()) { - return -1; - } - - for (int i = position; i >=0; i --) { - RowInfo row = mRowInfo.get(i); - if (row != null && row.mType == TYPE_DAY) - return i; - } - return -1; - } - - // Returns the number of items in a section defined by a specific header location - public int getHeaderItemsCount(int position) { - if (mRowInfo == null) { - return -1; - } - int count = 0; - for (int i = position +1; i < mRowInfo.size(); i++) { - if (mRowInfo.get(i).mType != TYPE_MEETING) { - return count; - } - count ++; - } - return count; - } - - @Override - public int getCount() { - if (mRowInfo != null) { - return mRowInfo.size(); - } - return mAgendaAdapter.getCount(); - } - - @Override - 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.mPosition); - } - } - return mAgendaAdapter.getItem(position); - } - - @Override - 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.mPosition); - } - } - 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; - } - - public boolean isDayHeaderView(int position) { - return (getItemViewType(position) == TYPE_DAY); - } - - @Override - 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 = null; - View agendaDayView = null; - if ((convertView != null) && (convertView.getTag() != null)) { - // Listview may get confused and pass in a different type of - // view since we keep shifting data around. Not a big problem. - Object tag = convertView.getTag(); - if (tag instanceof ViewHolder) { - agendaDayView = convertView; - holder = (ViewHolder) tag; - holder.julianDay = row.mDay; - } - } - - if (holder == 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.dayView = (TextView) agendaDayView.findViewById(R.id.day); - holder.dateView = (TextView) agendaDayView.findViewById(R.id.date); - holder.julianDay = row.mDay; - holder.grayed = false; - agendaDayView.setTag(holder); - } - - // Re-use the member variable "mTime" which is set to the local - // time zone. - // It's difficult to find and update all these adapters when the - // home tz changes so check it here and update if needed. - String tz = Utils.getTimeZone(mContext, mTZUpdater); - if (!TextUtils.equals(tz, mTmpTime.timezone)) { - mTimeZone = tz; - mTmpTime = new Time(tz); - } - - // Build the text for the day of the week. - // Should be yesterday/today/tomorrow (if applicable) + day of the week - - Time date = mTmpTime; - long millis = date.setJulianDay(row.mDay); - int flags = DateUtils.FORMAT_SHOW_WEEKDAY; - mStringBuilder.setLength(0); - - String dayViewText = Utils.getDayOfWeekString(row.mDay, mTodayJulianDay, millis, - mContext); - - // Build text for the date - // Format should be month day - - mStringBuilder.setLength(0); - flags = DateUtils.FORMAT_SHOW_DATE; - String dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis, - flags, mTimeZone).toString(); - - if (AgendaWindowAdapter.BASICLOG) { - dayViewText += " P:" + position; - dateViewText += " P:" + position; - } - holder.dayView.setText(dayViewText); - holder.dateView.setText(dateViewText); - - // Set the background of the view, it is grayed for day that are in the past and today - if (row.mDay > mTodayJulianDay) { - agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_primary); - holder.grayed = false; - } else { - agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_secondary); - holder.grayed = true; - } - return agendaDayView; - } else if (row.mType == TYPE_MEETING) { - View itemView = mAgendaAdapter.getView(row.mPosition, convertView, parent); - AgendaAdapter.ViewHolder holder = ((AgendaAdapter.ViewHolder) itemView.getTag()); - TextView title = holder.title; - // The holder in the view stores information from the cursor, but the cursor has no - // notion of multi-day event and the start time of each instance of a multi-day event - // is the same. RowInfo has the correct info , so take it from there. - holder.startTimeMilli = row.mEventStartTimeMilli; - boolean allDay = holder.allDay; - if (AgendaWindowAdapter.BASICLOG) { - title.setText(title.getText() + " P:" + position); - } else { - title.setText(title.getText()); - } - - // if event in the past or started already, un-bold the title and set the background - if ((!allDay && row.mEventStartTimeMilli <= System.currentTimeMillis()) || - (allDay && row.mDay <= mTodayJulianDay)) { - itemView.setBackgroundResource(R.drawable.agenda_item_bg_secondary); - title.setTypeface(Typeface.DEFAULT); - holder.grayed = true; - } else { - itemView.setBackgroundResource(R.drawable.agenda_item_bg_primary); - title.setTypeface(Typeface.DEFAULT_BOLD); - holder.grayed = false; - } - holder.julianDay = row.mDay; - return itemView; - } else { - // Error - throw new IllegalStateException("Unknown event type:" + row.mType); - } - } - - public void clearDayHeaderInfo() { - mRowInfo = null; - } - - public void changeCursor(DayAdapterInfo info) { - calculateDays(info); - mAgendaAdapter.changeCursor(info.cursor); - } - - public void calculateDays(DayAdapterInfo dayAdapterInfo) { - Cursor cursor = dayAdapterInfo.cursor; - ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>(); - int prevStartDay = -1; - - Time tempTime = new Time(mTimeZone); - long now = System.currentTimeMillis(); - tempTime.set(now); - mTodayJulianDay = Time.getJulianDay(now, tempTime.gmtoff); - - LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>(); - for (int position = 0; cursor.moveToNext(); position++) { - int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); - long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); - long startTime = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - long endTime = cursor.getLong(AgendaWindowAdapter.INDEX_END); - long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID); - boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - if (allDay) { - startTime = Utils.convertAlldayUtcToLocal(tempTime, startTime, mTimeZone); - endTime = Utils.convertAlldayUtcToLocal(tempTime, endTime, mTimeZone); - } - // Skip over the days outside of the adapter's range - startDay = Math.max(startDay, dayAdapterInfo.start); - // Make sure event's start time is not before the start of the day - // (setJulianDay sets the time to 12:00am) - long adapterStartTime = tempTime.setJulianDay(startDay); - startTime = Math.max(startTime, adapterStartTime); - - 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; - } - long nextMidnight = Utils.getNextMidnight(tempTime, - info.mEventStartTimeMilli, mTimeZone); - - long infoEndTime = (info.mEndDay == currentDay) ? - info.mEventEndTimeMilli : nextMidnight; - rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition, - info.mEventId, info.mEventStartTimeMilli, - infoEndTime, info.mInstanceId, info.mAllDay)); - - info.mEventStartTimeMilli = nextMidnight; - } - } - - // 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; - } - - // If this event spans multiple days, then add it to the multipleDay - // list. - int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); - - // Skip over the days outside of the adapter's range - endDay = Math.min(endDay, dayAdapterInfo.end); - if (endDay > startDay) { - long nextMidnight = Utils.getNextMidnight(tempTime, startTime, mTimeZone); - multipleDayList.add(new MultipleDayInfo(position, endDay, id, nextMidnight, - endTime, instanceId, allDay)); - // Add in the event for this cursor position - since it is the start of a multi-day - // event, the end time is midnight - rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, - nextMidnight, instanceId, allDay)); - } else { - // Add in the event for this cursor position - rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, endTime, - instanceId, allDay)); - } - } - - // 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) { - for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end; - 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; - } - long nextMidnight = Utils.getNextMidnight(tempTime, info.mEventStartTimeMilli, - mTimeZone); - long infoEndTime = - (info.mEndDay == currentDay) ? info.mEventEndTimeMilli : nextMidnight; - rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition, - info.mEventId, info.mEventStartTimeMilli, infoEndTime, - info.mInstanceId, info.mAllDay)); - - info.mEventStartTimeMilli = nextMidnight; - } - } - } - mRowInfo = rowInfo; - } - - private static class RowInfo { - // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING) - final int mType; - - final int mDay; // Julian day - final int mPosition; // cursor position (not used for TYPE_DAY) - // This is used to mark a day header as the first day with events that is "today" - // or later. This flag is used by the adapter to create a view with a visual separator - // between the past and the present/future - boolean mFirstDayAfterYesterday; - final long mEventId; - final long mEventStartTimeMilli; - final long mEventEndTimeMilli; - final long mInstanceId; - final boolean mAllDay; - - RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime, - long instanceId, boolean allDay) { - mType = type; - mDay = julianDay; - mPosition = position; - mEventId = id; - mEventStartTimeMilli = startTime; - mEventEndTimeMilli = endTime; - mFirstDayAfterYesterday = false; - mInstanceId = instanceId; - mAllDay = allDay; - } - - RowInfo(int type, int julianDay) { - mType = type; - mDay = julianDay; - mPosition = 0; - mEventId = 0; - mEventStartTimeMilli = 0; - mEventEndTimeMilli = 0; - mFirstDayAfterYesterday = false; - mInstanceId = -1; - mAllDay = false; - } - } - - private static class MultipleDayInfo { - final int mPosition; - final int mEndDay; - final long mEventId; - long mEventStartTimeMilli; - long mEventEndTimeMilli; - final long mInstanceId; - final boolean mAllDay; - - MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime, - long instanceId, boolean allDay) { - mPosition = position; - mEndDay = endDay; - mEventId = id; - mEventStartTimeMilli = startTime; - mEventEndTimeMilli = endTime; - mInstanceId = instanceId; - mAllDay = allDay; - } - } - - /** - * Finds the position in the cursor of the event that best matches the time and Id. - * It will try to find the event that has the specified id and start time, if such event - * doesn't exist, it will return the event with a matching id that is closest to the start time. - * If the id doesn't exist, it will return the event with start time closest to the specified - * time. - * @param time - start of event in milliseconds (or any arbitrary time if event id is unknown) - * @param id - Event id (-1 if unknown). - * @return Position of event (if found) or position of nearest event according to the time. - * Zero if no event found - */ - public int findEventPositionNearestTime(Time time, long id) { - if (mRowInfo == null) { - return 0; - } - long millis = time.toMillis(false /* use isDst */); - long minDistance = Integer.MAX_VALUE; // some big number - long idFoundMinDistance = Integer.MAX_VALUE; // some big number - int minIndex = 0; - int idFoundMinIndex = 0; - int eventInTimeIndex = -1; - int allDayEventInTimeIndex = -1; - int allDayEventDay = 0; - int minDay = 0; - boolean idFound = false; - int len = mRowInfo.size(); - - // Loop through the events and find the best match - // 1. Event id and start time matches requested id and time - // 2. Event id matches and closest time - // 3. No event id match , time matches a all day event (midnight) - // 4. No event id match , time is between event start and end - // 5. No event id match , all day event - // 6. The closest event to the requested time - - for (int index = 0; index < len; index++) { - RowInfo row = mRowInfo.get(index); - if (row.mType == TYPE_DAY) { - continue; - } - - // Found exact match - done - if (row.mEventId == id) { - if (row.mEventStartTimeMilli == millis) { - return index; - } - - // Not an exact match, Save event index if it is the closest to time so far - long distance = Math.abs(millis - row.mEventStartTimeMilli); - if (distance < idFoundMinDistance) { - idFoundMinDistance = distance; - idFoundMinIndex = index; - } - idFound = true; - } - if (!idFound) { - // Found an event that contains the requested time - if (millis >= row.mEventStartTimeMilli && millis <= row.mEventEndTimeMilli) { - if (row.mAllDay) { - if (allDayEventInTimeIndex == -1) { - allDayEventInTimeIndex = index; - allDayEventDay = row.mDay; - } - } else if (eventInTimeIndex == -1){ - eventInTimeIndex = index; - } - } else if (eventInTimeIndex == -1){ - // Save event index if it is the closest to time so far - long distance = Math.abs(millis - row.mEventStartTimeMilli); - if (distance < minDistance) { - minDistance = distance; - minIndex = index; - minDay = row.mDay; - } - } - } - } - // We didn't find an exact match so take the best matching event - // Closest event with the same id - if (idFound) { - return idFoundMinIndex; - } - // Event which occurs at the searched time - if (eventInTimeIndex != -1) { - return eventInTimeIndex; - // All day event which occurs at the same day of the searched time as long as there is - // no regular event at the same day - } else if (allDayEventInTimeIndex != -1 && minDay != allDayEventDay) { - return allDayEventInTimeIndex; - } - // Closest event - return minIndex; - } - - - /** - * Returns a flag indicating if this position is the first day after "yesterday" that has - * events in it. - * - * @return a flag indicating if this is the "first day after yesterday" - */ - public boolean isFirstDayAfterYesterday(int position) { - int headerPos = getHeaderPosition(position); - RowInfo row = mRowInfo.get(headerPos); - if (row != null) { - return row.mFirstDayAfterYesterday; - } - return false; - } - - /** - * 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.mDay; - } - } - return 0; - } - - /** - * Marks the current row as the first day that has events after "yesterday". - * Used to mark the separation between the past and the present/future - * - * @param position in the adapter - */ - public void setAsFirstDayAfterYesterday(int position) { - if (mRowInfo == null || position < 0 || position > mRowInfo.size()) { - return; - } - RowInfo row = mRowInfo.get(position); - row.mFirstDayAfterYesterday = true; - } - - /** - * 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 - * if the position point to day header , it will give the position of the next event - * negated. - */ - public int getCursorPosition(int listPos) { - if (mRowInfo != null && listPos >= 0) { - RowInfo row = mRowInfo.get(listPos); - if (row.mType == TYPE_MEETING) { - return row.mPosition; - } else { - int nextPos = listPos + 1; - if (nextPos < mRowInfo.size()) { - nextPos = getCursorPosition(nextPos); - if (nextPos >= 0) { - return -nextPos; - } - } - } - } - return Integer.MIN_VALUE; - } - - @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/agenda/AgendaFragment.java b/src/com/android/calendar/agenda/AgendaFragment.java deleted file mode 100644 index ff5c47cc..00000000 --- a/src/com/android/calendar/agenda/AgendaFragment.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.agenda; - - -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.provider.CalendarContract.Attendees; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.Adapter; -import android.widget.HeaderViewListAdapter; - -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.EventInfoFragment; -import com.android.calendar.GeneralPreferences; -import com.android.calendar.R; -import com.android.calendar.StickyHeaderListView; -import com.android.calendar.Utils; - -import java.util.Date; - -public class AgendaFragment extends Fragment implements CalendarController.EventHandler, - OnScrollListener { - - private static final String TAG = AgendaFragment.class.getSimpleName(); - private static boolean DEBUG = false; - - protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; - protected static final String BUNDLE_KEY_RESTORE_INSTANCE_ID = "key_restore_instance_id"; - - private AgendaListView mAgendaListView; - private Activity mActivity; - private final Time mTime; - private String mTimeZone; - private final long mInitialTimeMillis; - private boolean mShowEventDetailsWithAgenda; - private CalendarController mController; - private EventInfoFragment mEventFragment; - private String mQuery; - private boolean mUsedForSearch = false; - private boolean mIsTabletConfig; - private EventInfo mOnAttachedInfo = null; - private boolean mOnAttachAllDay = false; - private AgendaWindowAdapter mAdapter = null; - private boolean mForceReplace = true; - private long mLastShownEventId = -1; - - - - // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action - // bar. - int mJulianDayOnTop = -1; - - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - mTimeZone = Utils.getTimeZone(getActivity(), this); - mTime.switchTimezone(mTimeZone); - } - }; - - public AgendaFragment() { - this(0, false); - } - - - // timeMillis - time of first event to show - // usedForSearch - indicates if this fragment is used in the search fragment - public AgendaFragment(long timeMillis, boolean usedForSearch) { - mInitialTimeMillis = timeMillis; - mTime = new Time(); - mLastHandledEventTime = new Time(); - - if (mInitialTimeMillis == 0) { - mTime.setToNow(); - } else { - mTime.set(mInitialTimeMillis); - } - mLastHandledEventTime.set(mTime); - mUsedForSearch = usedForSearch; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mTimeZone = Utils.getTimeZone(activity, mTZUpdater); - mTime.switchTimezone(mTimeZone); - mActivity = activity; - if (mOnAttachedInfo != null) { - showEventInfo(mOnAttachedInfo, mOnAttachAllDay, true); - mOnAttachedInfo = null; - } - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - mController = CalendarController.getInstance(mActivity); - mShowEventDetailsWithAgenda = - Utils.getConfigBool(mActivity, R.bool.show_event_details_with_agenda); - mIsTabletConfig = - Utils.getConfigBool(mActivity, R.bool.tablet_config); - if (icicle != null) { - long prevTime = icicle.getLong(BUNDLE_KEY_RESTORE_TIME, -1); - if (prevTime != -1) { - mTime.set(prevTime); - if (DEBUG) { - Log.d(TAG, "Restoring time to " + mTime.toString()); - } - } - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - - int screenWidth = mActivity.getResources().getDisplayMetrics().widthPixels; - View v = inflater.inflate(R.layout.agenda_fragment, null); - - mAgendaListView = (AgendaListView)v.findViewById(R.id.agenda_events_list); - mAgendaListView.setClickable(true); - - if (savedInstanceState != null) { - long instanceId = savedInstanceState.getLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, -1); - if (instanceId != -1) { - mAgendaListView.setSelectedInstanceId(instanceId); - } - } - - View eventView = v.findViewById(R.id.agenda_event_info); - if (!mShowEventDetailsWithAgenda) { - eventView.setVisibility(View.GONE); - } - - View topListView; - // Set adapter & HeaderIndexer for StickyHeaderListView - StickyHeaderListView lv = - (StickyHeaderListView)v.findViewById(R.id.agenda_sticky_header_list); - if (lv != null) { - Adapter a = mAgendaListView.getAdapter(); - lv.setAdapter(a); - if (a instanceof HeaderViewListAdapter) { - mAdapter = (AgendaWindowAdapter) ((HeaderViewListAdapter)a).getWrappedAdapter(); - lv.setIndexer(mAdapter); - lv.setHeaderHeightListener(mAdapter); - } else if (a instanceof AgendaWindowAdapter) { - mAdapter = (AgendaWindowAdapter)a; - lv.setIndexer(mAdapter); - lv.setHeaderHeightListener(mAdapter); - } else { - Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView"); - } - - // Set scroll listener so that the date on the ActionBar can be set while - // the user scrolls the view - lv.setOnScrollListener(this); - lv.setHeaderSeparator(getResources().getColor(R.color.agenda_list_separator_color), 1); - topListView = lv; - } else { - topListView = mAgendaListView; - } - - // Since using weight for sizing the two panes of the agenda fragment causes the whole - // fragment to re-measure when the sticky header is replaced, calculate the weighted - // size of each pane here and set it - - if (!mShowEventDetailsWithAgenda) { - ViewGroup.LayoutParams params = topListView.getLayoutParams(); - params.width = screenWidth; - topListView.setLayoutParams(params); - } else { - ViewGroup.LayoutParams listParams = topListView.getLayoutParams(); - listParams.width = screenWidth * 4 / 10; - topListView.setLayoutParams(listParams); - ViewGroup.LayoutParams detailsParams = eventView.getLayoutParams(); - detailsParams.width = screenWidth - listParams.width; - eventView.setLayoutParams(detailsParams); - } - return v; - } - - @Override - public void onResume() { - super.onResume(); - if (DEBUG) { - Log.v(TAG, "OnResume to " + mTime.toString()); - } - - SharedPreferences prefs = GeneralPreferences.getSharedPreferences( - getActivity()); - boolean hideDeclined = prefs.getBoolean( - GeneralPreferences.KEY_HIDE_DECLINED, false); - - mAgendaListView.setHideDeclinedEvents(hideDeclined); - if (mLastHandledEventId != -1) { - mAgendaListView.goTo(mLastHandledEventTime, mLastHandledEventId, mQuery, true, false); - mLastHandledEventTime = null; - mLastHandledEventId = -1; - } else { - mAgendaListView.goTo(mTime, -1, mQuery, true, false); - } - mAgendaListView.onResume(); - -// // Register for Intent broadcasts -// IntentFilter filter = new IntentFilter(); -// filter.addAction(Intent.ACTION_TIME_CHANGED); -// filter.addAction(Intent.ACTION_DATE_CHANGED); -// filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); -// registerReceiver(mIntentReceiver, filter); -// -// mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (mAgendaListView == null) { - return; - } - if (mShowEventDetailsWithAgenda) { - long timeToSave; - if (mLastHandledEventTime != null) { - timeToSave = mLastHandledEventTime.toMillis(true); - mTime.set(mLastHandledEventTime); - } else { - timeToSave = System.currentTimeMillis(); - mTime.set(timeToSave); - } - outState.putLong(BUNDLE_KEY_RESTORE_TIME, timeToSave); - mController.setTime(timeToSave); - } else { - AgendaWindowAdapter.AgendaItem item = mAgendaListView.getFirstVisibleAgendaItem(); - if (item != null) { - long firstVisibleTime = mAgendaListView.getFirstVisibleTime(item); - if (firstVisibleTime > 0) { - mTime.set(firstVisibleTime); - mController.setTime(firstVisibleTime); - outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); - } - // Tell AllInOne the event id of the first visible event in the list. The id will be - // used in the GOTO when AllInOne is restored so that Agenda Fragment can select a - // specific event and not just the time. - mLastShownEventId = item.id; - } - } - if (DEBUG) { - Log.v(TAG, "onSaveInstanceState " + mTime.toString()); - } - - long selectedInstance = mAgendaListView.getSelectedInstanceId(); - if (selectedInstance >= 0) { - outState.putLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, selectedInstance); - } - } - - /** - * This cleans up the event info fragment since the FragmentManager doesn't - * handle nested fragments. Without this, the action bar buttons added by - * the info fragment can come back on a rotation. - * - * @param fragmentManager - */ - public void removeFragments(FragmentManager fragmentManager) { - if (getActivity().isFinishing()) { - return; - } - FragmentTransaction ft = fragmentManager.beginTransaction(); - Fragment f = fragmentManager.findFragmentById(R.id.agenda_event_info); - if (f != null) { - ft.remove(f); - } - ft.commit(); - } - - @Override - public void onPause() { - super.onPause(); - - mAgendaListView.onPause(); - -// mContentResolver.unregisterContentObserver(mObserver); -// unregisterReceiver(mIntentReceiver); - - // Record Agenda View as the (new) default detailed view. -// Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); - } - - private void goTo(EventInfo event, boolean animate) { - if (event.selectedTime != null) { - mTime.set(event.selectedTime); - } else if (event.startTime != null) { - mTime.set(event.startTime); - } - if (mAgendaListView == null) { - // The view hasn't been set yet. Just save the time and use it - // later. - return; - } - mAgendaListView.goTo(mTime, event.id, mQuery, false, - ((event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0 && - mShowEventDetailsWithAgenda) ? true : false); - AgendaAdapter.ViewHolder vh = mAgendaListView.getSelectedViewHolder(); - // Make sure that on the first time the event info is shown to recreate it - Log.d(TAG, "selected viewholder is null: " + (vh == null)); - showEventInfo(event, vh != null ? vh.allDay : false, mForceReplace); - mForceReplace = false; - } - - private void search(String query, Time time) { - mQuery = query; - if (time != null) { - mTime.set(time); - } - if (mAgendaListView == null) { - // The view hasn't been set yet. Just return. - return; - } - mAgendaListView.goTo(time, -1, mQuery, true, false); - } - - @Override - public void eventsChanged() { - if (mAgendaListView != null) { - mAgendaListView.refresh(true); - } - } - - @Override - public long getSupportedEventTypes() { - return EventType.GO_TO | EventType.EVENTS_CHANGED | ((mUsedForSearch)?EventType.SEARCH:0); - } - - private long mLastHandledEventId = -1; - private Time mLastHandledEventTime = null; - @Override - public void handleEvent(EventInfo event) { - if (event.eventType == EventType.GO_TO) { - // TODO support a range of time - // TODO support event_id - // TODO figure out the animate bit - mLastHandledEventId = event.id; - mLastHandledEventTime = - (event.selectedTime != null) ? event.selectedTime : event.startTime; - goTo(event, true); - } else if (event.eventType == EventType.SEARCH) { - search(event.query, event.startTime); - } else if (event.eventType == EventType.EVENTS_CHANGED) { - eventsChanged(); - } - } - - public long getLastShowEventId() { - return mLastShownEventId; - } - - // Shows the selected event in the Agenda view - private void showEventInfo(EventInfo event, boolean allDay, boolean replaceFragment) { - - // Ignore unknown events - if (event.id == -1) { - Log.e(TAG, "showEventInfo, event ID = " + event.id); - return; - } - - mLastShownEventId = event.id; - - // Create a fragment to show the event to the side of the agenda list - if (mShowEventDetailsWithAgenda) { - FragmentManager fragmentManager = getFragmentManager(); - if (fragmentManager == null) { - // Got a goto event before the fragment finished attaching, - // stash the event and handle it later. - mOnAttachedInfo = event; - mOnAttachAllDay = allDay; - return; - } - FragmentTransaction ft = fragmentManager.beginTransaction(); - - if (allDay) { - event.startTime.timezone = Time.TIMEZONE_UTC; - event.endTime.timezone = Time.TIMEZONE_UTC; - } - - if (DEBUG) { - Log.d(TAG, "***"); - Log.d(TAG, "showEventInfo: start: " + new Date(event.startTime.toMillis(true))); - Log.d(TAG, "showEventInfo: end: " + new Date(event.endTime.toMillis(true))); - Log.d(TAG, "showEventInfo: all day: " + allDay); - Log.d(TAG, "***"); - } - - long startMillis = event.startTime.toMillis(true); - long endMillis = event.endTime.toMillis(true); - EventInfoFragment fOld = - (EventInfoFragment)fragmentManager.findFragmentById(R.id.agenda_event_info); - if (fOld == null || replaceFragment || fOld.getStartMillis() != startMillis || - fOld.getEndMillis() != endMillis || fOld.getEventId() != event.id) { - mEventFragment = new EventInfoFragment(mActivity, event.id, - startMillis, endMillis, - Attendees.ATTENDEE_STATUS_NONE, false, - EventInfoFragment.DIALOG_WINDOW_STYLE, null); - ft.replace(R.id.agenda_event_info, mEventFragment); - ft.commit(); - } else { - fOld.reloadEvents(); - } - } - } - - // OnScrollListener implementation to update the date on the pull-down menu of the app - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // Save scroll state so that the adapter can stop the scroll when the - // agenda list is fling state and it needs to set the agenda list to a new position - if (mAdapter != null) { - mAdapter.setScrollState(scrollState); - } - } - - // Gets the time of the first visible view. If it is a new time, send a message to update - // the time on the ActionBar - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - int julianDay = mAgendaListView.getJulianDayFromPosition(firstVisibleItem - - mAgendaListView.getHeaderViewsCount()); - // On error - leave the old view - if (julianDay == 0) { - return; - } - // If the day changed, update the ActionBar - if (mJulianDayOnTop != julianDay) { - mJulianDayOnTop = julianDay; - Time t = new Time(mTimeZone); - t.setJulianDay(mJulianDayOnTop); - mController.setTime(t.toMillis(true)); - // Cannot sent a message that eventually may change the layout of the views - // so instead post a runnable that will run when the layout is done - if (!mIsTabletConfig) { - view.post(new Runnable() { - @Override - public void run() { - Time t = new Time(mTimeZone); - t.setJulianDay(mJulianDayOnTop); - mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, null, -1, - ViewType.CURRENT, 0, null, null); - } - }); - } - } - } -} diff --git a/src/com/android/calendar/agenda/AgendaListView.java b/src/com/android/calendar/agenda/AgendaListView.java deleted file mode 100644 index 6cfc7e5b..00000000 --- a/src/com/android/calendar/agenda/AgendaListView.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * 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.agenda; - -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.DeleteEventHelper; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.agenda.AgendaAdapter.ViewHolder; -import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; -import com.android.calendar.agenda.AgendaWindowAdapter.AgendaItem; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.provider.CalendarContract.Attendees; -import android.text.format.Time; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; -import android.widget.TextView; - -public class AgendaListView extends ListView implements OnItemClickListener { - - private static final String TAG = "AgendaListView"; - private static final boolean DEBUG = false; - private static final int EVENT_UPDATE_TIME = 300000; // 5 minutes - - private AgendaWindowAdapter mWindowAdapter; - private DeleteEventHelper mDeleteEventHelper; - private Context mContext; - private String mTimeZone; - private Time mTime; - private boolean mShowEventDetailsWithAgenda; - private Handler mHandler = null; - - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - mTimeZone = Utils.getTimeZone(mContext, this); - mTime.switchTimezone(mTimeZone); - } - }; - - // runs every midnight and refreshes the view in order to update the past/present - // separator - private final Runnable mMidnightUpdater = new Runnable() { - @Override - public void run() { - refresh(true); - Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone); - } - }; - - // Runs every EVENT_UPDATE_TIME to gray out past events - private final Runnable mPastEventUpdater = new Runnable() { - @Override - public void run() { - if (updatePastEvents() == true) { - refresh(true); - } - setPastEventsUpdater(); - } - }; - - public AgendaListView(Context context, AttributeSet attrs) { - super(context, attrs); - initView(context); - } - - private void initView(Context context) { - mContext = context; - mTimeZone = Utils.getTimeZone(context, mTZUpdater); - mTime = new Time(mTimeZone); - setOnItemClickListener(this); - setVerticalScrollBarEnabled(false); - mWindowAdapter = new AgendaWindowAdapter(context, this, - Utils.getConfigBool(context, R.bool.show_event_details_with_agenda)); - mWindowAdapter.setSelectedInstanceId(-1/* TODO:instanceId */); - setAdapter(mWindowAdapter); - setCacheColorHint(context.getResources().getColor(R.color.agenda_item_not_selected)); - mDeleteEventHelper = - new DeleteEventHelper(context, null, false /* don't exit when done */); - mShowEventDetailsWithAgenda = Utils.getConfigBool(mContext, - R.bool.show_event_details_with_agenda); - // Hide ListView dividers, they are done in the item views themselves - setDivider(null); - setDividerHeight(0); - - mHandler = new Handler(); - } - - // Sets a thread to run every EVENT_UPDATE_TIME in order to update the list - // with grayed out past events - private void setPastEventsUpdater() { - - // Run the thread in the nearest rounded EVENT_UPDATE_TIME - long now = System.currentTimeMillis(); - long roundedTime = (now / EVENT_UPDATE_TIME) * EVENT_UPDATE_TIME; - mHandler.removeCallbacks(mPastEventUpdater); - mHandler.postDelayed(mPastEventUpdater, EVENT_UPDATE_TIME - (now - roundedTime)); - } - - // Stop the past events thread - private void resetPastEventsUpdater() { - mHandler.removeCallbacks(mPastEventUpdater); - } - - // Go over all visible views and checks if all past events are grayed out. - // Returns true is there is at least one event that ended and it is not - // grayed out. - private boolean updatePastEvents() { - - int childCount = getChildCount(); - boolean needUpdate = false; - long now = System.currentTimeMillis(); - Time time = new Time(mTimeZone); - time.set(now); - int todayJulianDay = Time.getJulianDay(now, time.gmtoff); - - // Go over views in list - for (int i = 0; i < childCount; ++i) { - View listItem = getChildAt(i); - Object o = listItem.getTag(); - if (o instanceof AgendaByDayAdapter.ViewHolder) { - // day view - check if day in the past and not grayed yet - AgendaByDayAdapter.ViewHolder holder = (AgendaByDayAdapter.ViewHolder) o; - if (holder.julianDay <= todayJulianDay && !holder.grayed) { - needUpdate = true; - break; - } - } else if (o instanceof AgendaAdapter.ViewHolder) { - // meeting view - check if event in the past or started already and not grayed yet - // All day meetings for a day are grayed out - AgendaAdapter.ViewHolder holder = (AgendaAdapter.ViewHolder) o; - if (!holder.grayed && ((!holder.allDay && holder.startTimeMilli <= now) || - (holder.allDay && holder.julianDay <= todayJulianDay))) { - needUpdate = true; - break; - } - } - } - return needUpdate; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mWindowAdapter.close(); - } - - // Implementation of the interface OnItemClickListener - @Override - public void onItemClick(AdapterView<?> a, View v, int position, long id) { - if (id != -1) { - // Switch to the EventInfo view - AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position); - long oldInstanceId = mWindowAdapter.getSelectedInstanceId(); - mWindowAdapter.setSelectedView(v); - - // If events are shown to the side of the agenda list , do nothing - // when the same event is selected , otherwise show the selected event. - - if (item != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() || - !mShowEventDetailsWithAgenda)) { - long startTime = item.begin; - long endTime = item.end; - // Holder in view holds the start of the specific part of a multi-day event , - // use it for the goto - long holderStartTime; - Object holder = v.getTag(); - if (holder instanceof AgendaAdapter.ViewHolder) { - holderStartTime = ((AgendaAdapter.ViewHolder) holder).startTimeMilli; - } else { - holderStartTime = startTime; - } - if (item.allDay) { - startTime = Utils.convertAlldayLocalToUTC(mTime, startTime, mTimeZone); - endTime = Utils.convertAlldayLocalToUTC(mTime, endTime, mTimeZone); - } - mTime.set(startTime); - CalendarController controller = CalendarController.getInstance(mContext); - controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id, - startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong( - Attendees.ATTENDEE_STATUS_NONE, item.allDay), holderStartTime); - } - } - } - - public void goTo(Time time, long id, String searchQuery, boolean forced, - boolean refreshEventInfo) { - if (time == null) { - time = mTime; - long goToTime = getFirstVisibleTime(null); - if (goToTime <= 0) { - goToTime = System.currentTimeMillis(); - } - time.set(goToTime); - } - mTime.set(time); - mTime.switchTimezone(mTimeZone); - mTime.normalize(true); - if (DEBUG) { - Log.d(TAG, "Goto with time " + mTime.toString()); - } - mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo); - } - - public void refresh(boolean forced) { - mWindowAdapter.refresh(mTime, -1, null, forced, false); - } - - public void deleteSelectedEvent() { - int position = getSelectedItemPosition(); - AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(position); - if (agendaItem != null) { - mDeleteEventHelper.delete(agendaItem.begin, agendaItem.end, agendaItem.id, -1); - } - } - - public View getFirstVisibleView() { - Rect r = new Rect(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; ++i) { - View listItem = getChildAt(i); - listItem.getLocalVisibleRect(r); - if (r.top >= 0) { // if visible - return listItem; - } - } - return null; - } - - public long getSelectedTime() { - int position = getSelectedItemPosition(); - if (position >= 0) { - AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position); - if (item != null) { - return item.begin; - } - } - return getFirstVisibleTime(null); - } - - public AgendaAdapter.ViewHolder getSelectedViewHolder() { - return mWindowAdapter.getSelectedViewHolder(); - } - - public long getFirstVisibleTime(AgendaItem item) { - AgendaItem agendaItem = item; - if (item == null) { - agendaItem = getFirstVisibleAgendaItem(); - } - if (agendaItem != null) { - Time t = new Time(mTimeZone); - t.set(agendaItem.begin); - // Save and restore the time since setJulianDay sets the time to 00:00:00 - int hour = t.hour; - int minute = t.minute; - int second = t.second; - t.setJulianDay(agendaItem.startDay); - t.hour = hour; - t.minute = minute; - t.second = second; - if (DEBUG) { - t.normalize(true); - Log.d(TAG, "first position had time " + t.toString()); - } - return t.normalize(false); - } - return 0; - } - - public AgendaItem getFirstVisibleAgendaItem() { - int position = getFirstVisiblePosition(); - if (DEBUG) { - Log.v(TAG, "getFirstVisiblePosition = " + position); - } - - // mShowEventDetailsWithAgenda == true implies we have a sticky header. In that case - // we may need to take the second visible position, since the first one maybe the one - // under the sticky header. - if (mShowEventDetailsWithAgenda) { - View v = getFirstVisibleView (); - if (v != null) { - Rect r = new Rect (); - v.getLocalVisibleRect(r); - if (r.bottom - r.top <= mWindowAdapter.getStickyHeaderHeight()) { - position ++; - } - } - } - - return mWindowAdapter.getAgendaItemByPosition(position, - false /* startDay = date separator date instead of actual event startday */); - - } - - public int getJulianDayFromPosition(int position) { - DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.findJulianDayFromPosition(position - info.offset); - } - return 0; - } - - // Finds is a specific event (defined by start time and id) is visible - public boolean isAgendaItemVisible(Time startTime, long id) { - - if (id == -1 || startTime == null) { - return false; - } - - View child = getChildAt(0); - // View not set yet, so not child - return - if (child == null) { - return false; - } - int start = getPositionForView(child); - long milliTime = startTime.toMillis(true); - int childCount = getChildCount(); - int eventsInAdapter = mWindowAdapter.getCount(); - - for (int i = 0; i < childCount; i++) { - if (i + start >= eventsInAdapter) { - break; - } - AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(i + start); - if (agendaItem == null) { - continue; - } - if (agendaItem.id == id && agendaItem.begin == milliTime) { - View listItem = getChildAt(i); - if (listItem.getTop() <= getHeight() && - listItem.getTop() >= mWindowAdapter.getStickyHeaderHeight()) { - return true; - } - } - } - return false; - } - - public long getSelectedInstanceId() { - return mWindowAdapter.getSelectedInstanceId(); - } - - public void setSelectedInstanceId(long id) { - mWindowAdapter.setSelectedInstanceId(id); - } - - // Move the currently selected or visible focus down by offset amount. - // offset could be negative. - public void shiftSelection(int offset) { - shiftPosition(offset); - int position = getSelectedItemPosition(); - if (position != INVALID_POSITION) { - setSelectionFromTop(position + offset, 0); - } - } - - private void shiftPosition(int offset) { - if (DEBUG) { - Log.v(TAG, "Shifting position " + offset); - } - - View firstVisibleItem = getFirstVisibleView(); - - if (firstVisibleItem != null) { - Rect r = new Rect(); - firstVisibleItem.getLocalVisibleRect(r); - // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is - // returning an item above the first visible item. - int position = getPositionForView(firstVisibleItem); - setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); - if (DEBUG) { - if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { - ViewHolder viewHolder = (AgendaAdapter.ViewHolder) firstVisibleItem.getTag(); - Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " - + viewHolder.title.getText()); - } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { - AgendaByDayAdapter.ViewHolder viewHolder = - (AgendaByDayAdapter.ViewHolder) firstVisibleItem.getTag(); - Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " - + viewHolder.dateView.getText()); - } else if (firstVisibleItem instanceof TextView) { - Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); - } - } - } else if (getSelectedItemPosition() >= 0) { - if (DEBUG) { - Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + - " by " + offset); - } - setSelection(getSelectedItemPosition() + offset); - } - } - - public void setHideDeclinedEvents(boolean hideDeclined) { - mWindowAdapter.setHideDeclinedEvents(hideDeclined); - } - - public void onResume() { - mTZUpdater.run(); - Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone); - setPastEventsUpdater(); - mWindowAdapter.onResume(); - } - - public void onPause() { - Utils.resetMidnightUpdater(mHandler, mMidnightUpdater); - resetPastEventsUpdater(); - } -} diff --git a/src/com/android/calendar/agenda/AgendaWindowAdapter.java b/src/com/android/calendar/agenda/AgendaWindowAdapter.java deleted file mode 100644 index 9fd59f0f..00000000 --- a/src/com/android/calendar/agenda/AgendaWindowAdapter.java +++ /dev/null @@ -1,1412 +0,0 @@ -/* - * 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.agenda; - -import android.app.Activity; -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Handler; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Instances; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AbsListView.OnScrollListener; -import android.widget.BaseAdapter; -import android.widget.GridLayout; -import android.widget.TextView; - -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.R; -import com.android.calendar.StickyHeaderListView; -import com.android.calendar.Utils; - -import java.util.Date; -import java.util.Formatter; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; -import java.util.concurrent.ConcurrentLinkedQueue; - -/* -Bugs Bugs Bugs: -- At rotation and launch time, the initial position is not set properly. This code is calling - listview.setSelection() in 2 rapid secessions but it dropped or didn't process the first one. -- Scroll using trackball isn't repositioning properly after a new adapter is added. -- Track ball clicks at the header/footer doesn't work. -- Potential ping pong effect if the prefetch window is big and data is limited -- Add index in calendar provider - -ToDo ToDo ToDo: -Get design of header and footer from designer - -Make scrolling smoother. -Test for correctness -Loading speed -Check for leaks and excessive allocations - */ - -public class AgendaWindowAdapter extends BaseAdapter - implements StickyHeaderListView.HeaderIndexer, StickyHeaderListView.HeaderHeightListener{ - - static final boolean BASICLOG = false; - static final boolean DEBUGLOG = false; - private static final String TAG = "AgendaWindowAdapter"; - - private static final String AGENDA_SORT_ORDER = - CalendarContract.Instances.START_DAY + " ASC, " + - CalendarContract.Instances.BEGIN + " ASC, " + - CalendarContract.Events.TITLE + " ASC"; - - public static final int INDEX_INSTANCE_ID = 0; - 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 int INDEX_ORGANIZER = 13; - public static final int INDEX_OWNER_ACCOUNT = 14; - public static final int INDEX_CAN_ORGANIZER_RESPOND= 15; - public static final int INDEX_TIME_ZONE = 16; - - private static final String[] PROJECTION = new String[] { - Instances._ID, // 0 - Instances.TITLE, // 1 - Instances.EVENT_LOCATION, // 2 - Instances.ALL_DAY, // 3 - Instances.HAS_ALARM, // 4 - Instances.DISPLAY_COLOR, // 5 If SDK < 16, set to Instances.CALENDAR_COLOR. - 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 - Instances.ORGANIZER, // 13 - Instances.OWNER_ACCOUNT, // 14 - Instances.CAN_ORGANIZER_RESPOND, // 15 - Instances.EVENT_TIMEZONE, // 16 - }; - - static { - if (!Utils.isJellybeanOrLater()) { - PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR; - } - } - - // Listview may have a bug where the index/position is not consistent when there's a header. - // position == positionInListView - OFF_BY_ONE_BUG - // TODO Need to look into this. - private static final int OFF_BY_ONE_BUG = 1; - private static final int MAX_NUM_OF_ADAPTERS = 5; - private static final int IDEAL_NUM_OF_EVENTS = 50; - private static final int MIN_QUERY_DURATION = 7; // days - private static final int MAX_QUERY_DURATION = 60; // days - private static final int PREFETCH_BOUNDARY = 1; - - /** Times to auto-expand/retry query after getting no data */ - private static final int RETRIES_ON_NO_DATA = 1; - - private final Context mContext; - private final Resources mResources; - private final QueryHandler mQueryHandler; - private final AgendaListView mAgendaListView; - - /** The sum of the rows in all the adapters */ - private int mRowCount; - - /** The number of times we have queried and gotten no results back */ - private int mEmptyCursorCount; - - /** Cached value of the last used adapter */ - private DayAdapterInfo mLastUsedInfo; - - private final LinkedList<DayAdapterInfo> mAdapterInfos = - new LinkedList<DayAdapterInfo>(); - private final ConcurrentLinkedQueue<QuerySpec> mQueryQueue = - new ConcurrentLinkedQueue<QuerySpec>(); - private final TextView mHeaderView; - private final TextView mFooterView; - private boolean mDoneSettingUpHeaderFooter = false; - - private final boolean mIsTabletConfig; - - boolean mCleanQueryInitiated = false; - private int mStickyHeaderSize = 44; // Initial size big enough for it to work - - /** - * When the user scrolled to the top, a query will be made for older events - * and this will be incremented. Don't make more requests if - * mOlderRequests > mOlderRequestsProcessed. - */ - private int mOlderRequests; - - /** Number of "older" query that has been processed. */ - private int mOlderRequestsProcessed; - - /** - * When the user scrolled to the bottom, a query will be made for newer - * events and this will be incremented. Don't make more requests if - * mNewerRequests > mNewerRequestsProcessed. - */ - private int mNewerRequests; - - /** Number of "newer" query that has been processed. */ - private int mNewerRequestsProcessed; - - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private final Formatter mFormatter; - private final StringBuilder mStringBuilder; - private String mTimeZone; - - // defines if to pop-up the current event when the agenda is first shown - private final boolean mShowEventOnStart; - - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - mTimeZone = Utils.getTimeZone(mContext, this); - notifyDataSetChanged(); - } - }; - - private final Handler mDataChangedHandler = new Handler(); - private final Runnable mDataChangedRunnable = new Runnable() { - @Override - public void run() { - notifyDataSetChanged(); - } - }; - - private boolean mShuttingDown; - private boolean mHideDeclined; - - // Used to stop a fling motion if the ListView is set to a specific position - int mListViewScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** The current search query, or null if none */ - private String mSearchQuery; - - private long mSelectedInstanceId = -1; - - private final int mSelectedItemBackgroundColor; - private final int mSelectedItemTextColor; - private final float mItemRightMargin; - - // Types of Query - private static final int QUERY_TYPE_OLDER = 0; // Query for older events - private static final int QUERY_TYPE_NEWER = 1; // Query for newer events - private static final int QUERY_TYPE_CLEAN = 2; // Delete everything and query around a date - - private static class QuerySpec { - long queryStartMillis; - Time goToTime; - int start; - int end; - String searchQuery; - int queryType; - long id; - - public QuerySpec(int queryType) { - this.queryType = queryType; - id = -1; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + end; - result = prime * result + (int) (queryStartMillis ^ (queryStartMillis >>> 32)); - result = prime * result + queryType; - result = prime * result + start; - if (searchQuery != null) { - result = prime * result + searchQuery.hashCode(); - } - if (goToTime != null) { - long goToTimeMillis = goToTime.toMillis(false); - result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32)); - } - result = prime * result + (int)id; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - QuerySpec other = (QuerySpec) obj; - if (end != other.end || queryStartMillis != other.queryStartMillis - || queryType != other.queryType || start != other.start - || Utils.equals(searchQuery, other.searchQuery) || id != other.id) { - return false; - } - - if (goToTime != null) { - if (goToTime.toMillis(false) != other.goToTime.toMillis(false)) { - return false; - } - } else { - if (other.goToTime != null) { - return false; - } - } - return true; - } - } - - /** - * Class representing a list item within the Agenda view. Could be either an instance of an - * event, or a header marking the specific day. - * - * The begin and end times of an AgendaItem should always be in local time, even if the event - * is all day. buildAgendaItemFromCursor() converts each event to local time. - */ - static class AgendaItem { - long begin; - long end; - long id; - int startDay; - boolean allDay; - } - - static class DayAdapterInfo { - Cursor cursor; - AgendaByDayAdapter dayAdapter; - int start; // start day of the cursor's coverage - int end; // end day of the cursor's coverage - int offset; // offset in position in the list view - int size; // dayAdapter.getCount() - - public DayAdapterInfo(Context context) { - dayAdapter = new AgendaByDayAdapter(context); - } - - @Override - public String toString() { - // Static class, so the time in this toString will not reflect the - // home tz settings. This should only affect debugging. - Time time = new Time(); - StringBuilder sb = new StringBuilder(); - time.setJulianDay(start); - time.normalize(false); - sb.append("Start:").append(time.toString()); - time.setJulianDay(end); - time.normalize(false); - sb.append(" End:").append(time.toString()); - sb.append(" Offset:").append(offset); - sb.append(" Size:").append(size); - return sb.toString(); - } - } - - public AgendaWindowAdapter(Context context, - AgendaListView agendaListView, boolean showEventOnStart) { - mContext = context; - mResources = context.getResources(); - mSelectedItemBackgroundColor = mResources - .getColor(R.color.agenda_selected_background_color); - mSelectedItemTextColor = mResources.getColor(R.color.agenda_selected_text_color); - mItemRightMargin = mResources.getDimension(R.dimen.agenda_item_right_margin); - mIsTabletConfig = Utils.getConfigBool(mContext, R.bool.tablet_config); - - mTimeZone = Utils.getTimeZone(context, mTZUpdater); - mAgendaListView = agendaListView; - mQueryHandler = new QueryHandler(context.getContentResolver()); - - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - - mShowEventOnStart = showEventOnStart; - - // Implies there is no sticky header - if (!mShowEventOnStart) { - mStickyHeaderSize = 0; - } - mSearchQuery = null; - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mHeaderView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); - mFooterView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); - mHeaderView.setText(R.string.loading); - mAgendaListView.addHeaderView(mHeaderView); - } - - // Method in Adapter - @Override - public int getViewTypeCount() { - return AgendaByDayAdapter.TYPE_LAST; - } - - // Method in BaseAdapter - @Override - public boolean areAllItemsEnabled() { - return false; - } - - // Method in Adapter - @Override - public int getItemViewType(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getItemViewType(position - info.offset); - } else { - return -1; - } - } - - // Method in BaseAdapter - @Override - public boolean isEnabled(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.isEnabled(position - info.offset); - } else { - return false; - } - } - - // Abstract Method in BaseAdapter - public int getCount() { - return mRowCount; - } - - // Abstract Method in BaseAdapter - public Object getItem(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getItem(position - info.offset); - } else { - return null; - } - } - - // Method in BaseAdapter - @Override - public boolean hasStableIds() { - return true; - } - - // Abstract Method in BaseAdapter - @Override - public long getItemId(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - int curPos = info.dayAdapter.getCursorPosition(position - info.offset); - if (curPos == Integer.MIN_VALUE) { - return -1; - } - // Regular event - if (curPos >= 0) { - info.cursor.moveToPosition(curPos); - return info.cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID) << 20 + - info.cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - } - // Day Header - return info.dayAdapter.findJulianDayFromPosition(position); - - } else { - return -1; - } - } - - // Abstract Method in BaseAdapter - public View getView(int position, View convertView, ViewGroup parent) { - if (position >= (mRowCount - PREFETCH_BOUNDARY) - && mNewerRequests <= mNewerRequestsProcessed) { - if (DEBUGLOG) Log.e(TAG, "queryForNewerEvents: "); - mNewerRequests++; - queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); - } - - if (position < PREFETCH_BOUNDARY - && mOlderRequests <= mOlderRequestsProcessed) { - if (DEBUGLOG) Log.e(TAG, "queryForOlderEvents: "); - mOlderRequests++; - queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); - } - - final View v; - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - int offset = position - info.offset; - v = info.dayAdapter.getView(offset, convertView, - parent); - - // Turn on the past/present separator if the view is a day header - // and it is the first day with events after yesterday. - if (info.dayAdapter.isDayHeaderView(offset)) { - View simpleDivider = v.findViewById(R.id.top_divider_simple); - View pastPresentDivider = v.findViewById(R.id.top_divider_past_present); - if (info.dayAdapter.isFirstDayAfterYesterday(offset)) { - if (simpleDivider != null && pastPresentDivider != null) { - simpleDivider.setVisibility(View.GONE); - pastPresentDivider.setVisibility(View.VISIBLE); - } - } else if (simpleDivider != null && pastPresentDivider != null) { - simpleDivider.setVisibility(View.VISIBLE); - pastPresentDivider.setVisibility(View.GONE); - } - } - } else { - // TODO - Log.e(TAG, "BUG: getAdapterInfoByPosition returned null!!! " + position); - TextView tv = new TextView(mContext); - tv.setText("Bug! " + position); - v = tv; - } - - // If this is not a tablet config don't do selection highlighting - if (!mIsTabletConfig) { - return v; - } - // Show selected marker if this is item is selected - boolean selected = false; - Object yy = v.getTag(); - if (yy instanceof AgendaAdapter.ViewHolder) { - AgendaAdapter.ViewHolder vh = (AgendaAdapter.ViewHolder) yy; - selected = mSelectedInstanceId == vh.instanceId; - vh.selectedMarker.setVisibility((selected && mShowEventOnStart) ? - View.VISIBLE : View.GONE); - if (mShowEventOnStart) { - GridLayout.LayoutParams lp = - (GridLayout.LayoutParams)vh.textContainer.getLayoutParams(); - if (selected) { - mSelectedVH = vh; - v.setBackgroundColor(mSelectedItemBackgroundColor); - vh.title.setTextColor(mSelectedItemTextColor); - vh.when.setTextColor(mSelectedItemTextColor); - vh.where.setTextColor(mSelectedItemTextColor); - lp.setMargins(0, 0, 0, 0); - vh.textContainer.setLayoutParams(lp); - } else { - lp.setMargins(0, 0, (int)mItemRightMargin, 0); - vh.textContainer.setLayoutParams(lp); - } - } - } - - if (DEBUGLOG) { - Log.e(TAG, "getView " + position + " = " + getViewTitle(v)); - } - return v; - } - - private AgendaAdapter.ViewHolder mSelectedVH = null; - - private int findEventPositionNearestTime(Time time, long id) { - DayAdapterInfo info = getAdapterInfoByTime(time); - int pos = -1; - if (info != null) { - pos = info.offset + info.dayAdapter.findEventPositionNearestTime(time, id); - } - if (DEBUGLOG) Log.e(TAG, "findEventPositionNearestTime " + time + " id:" + id + " =" + pos); - return pos; - } - - protected DayAdapterInfo getAdapterInfoByPosition(int position) { - synchronized (mAdapterInfos) { - if (mLastUsedInfo != null && mLastUsedInfo.offset <= position - && position < (mLastUsedInfo.offset + mLastUsedInfo.size)) { - return mLastUsedInfo; - } - for (DayAdapterInfo info : mAdapterInfos) { - if (info.offset <= position - && position < (info.offset + info.size)) { - mLastUsedInfo = info; - return info; - } - } - } - return null; - } - - private DayAdapterInfo getAdapterInfoByTime(Time time) { - if (DEBUGLOG) Log.e(TAG, "getAdapterInfoByTime " + time.toString()); - - Time tmpTime = new Time(time); - long timeInMillis = tmpTime.normalize(true); - int day = Time.getJulianDay(timeInMillis, tmpTime.gmtoff); - synchronized (mAdapterInfos) { - for (DayAdapterInfo info : mAdapterInfos) { - if (info.start <= day && day <= info.end) { - return info; - } - } - } - return null; - } - - public AgendaItem getAgendaItemByPosition(final int positionInListView) { - return getAgendaItemByPosition(positionInListView, true); - } - - /** - * Return the event info for a given position in the adapter - * @param positionInListView - * @param returnEventStartDay If true, return actual event startday. Otherwise - * return agenda date-header date as the startDay. - * The two will differ for multi-day events after the first day. - * @return - */ - public AgendaItem getAgendaItemByPosition(final int positionInListView, - boolean returnEventStartDay) { - if (DEBUGLOG) Log.e(TAG, "getEventByPosition " + positionInListView); - if (positionInListView < 0) { - return null; - } - - final int positionInAdapter = positionInListView - OFF_BY_ONE_BUG; - DayAdapterInfo info = getAdapterInfoByPosition(positionInAdapter); - if (info == null) { - return null; - } - - int cursorPosition = info.dayAdapter.getCursorPosition(positionInAdapter - info.offset); - if (cursorPosition == Integer.MIN_VALUE) { - return null; - } - - boolean isDayHeader = false; - if (cursorPosition < 0) { - cursorPosition = -cursorPosition; - isDayHeader = true; - } - - if (cursorPosition < info.cursor.getCount()) { - AgendaItem item = buildAgendaItemFromCursor(info.cursor, cursorPosition, isDayHeader); - if (!returnEventStartDay && !isDayHeader) { - item.startDay = info.dayAdapter.findJulianDayFromPosition(positionInAdapter - - info.offset); - } - return item; - } - return null; - } - - private AgendaItem buildAgendaItemFromCursor(final Cursor cursor, int cursorPosition, - boolean isDayHeader) { - if (cursorPosition == -1) { - cursor.moveToFirst(); - } else { - cursor.moveToPosition(cursorPosition); - } - AgendaItem agendaItem = new AgendaItem(); - agendaItem.begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - agendaItem.end = cursor.getLong(AgendaWindowAdapter.INDEX_END); - agendaItem.startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); - agendaItem.allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - if (agendaItem.allDay) { // UTC to Local time conversion - Time time = new Time(mTimeZone); - time.setJulianDay(Time.getJulianDay(agendaItem.begin, 0)); - agendaItem.begin = time.toMillis(false /* use isDst */); - } else if (isDayHeader) { // Trim to midnight. - Time time = new Time(mTimeZone); - time.set(agendaItem.begin); - time.hour = 0; - time.minute = 0; - time.second = 0; - agendaItem.begin = time.toMillis(false /* use isDst */); - } - - // If this is not a day header, then it's an event. - if (!isDayHeader) { - agendaItem.id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); - if (agendaItem.allDay) { - Time time = new Time(mTimeZone); - time.setJulianDay(Time.getJulianDay(agendaItem.end, 0)); - agendaItem.end = time.toMillis(false /* use isDst */); - } - } - return agendaItem; - } - - /** - * Ensures that any all day events are converted to UTC before a VIEW_EVENT command is sent. - */ - private void sendViewEvent(AgendaItem item, long selectedTime) { - long startTime; - long endTime; - if (item.allDay) { - startTime = Utils.convertAlldayLocalToUTC(null, item.begin, mTimeZone); - endTime = Utils.convertAlldayLocalToUTC(null, item.end, mTimeZone); - } else { - startTime = item.begin; - endTime = item.end; - } - if (DEBUGLOG) { - Log.d(TAG, "Sent (AgendaWindowAdapter): VIEW EVENT: " + new Date(startTime)); - } - CalendarController.getInstance(mContext) - .sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, - item.id, startTime, endTime, 0, - 0, CalendarController.EventInfo.buildViewExtraLong( - Attendees.ATTENDEE_STATUS_NONE, - item.allDay), selectedTime); - } - - public void refresh(Time goToTime, long id, String searchQuery, boolean forced, - boolean refreshEventInfo) { - if (searchQuery != null) { - mSearchQuery = searchQuery; - } - - if (DEBUGLOG) { - Log.e(TAG, this + ": refresh " + goToTime.toString() + " id " + id - + ((searchQuery != null) ? searchQuery : "") - + (forced ? " forced" : " not forced") - + (refreshEventInfo ? " refresh event info" : "")); - } - - int startDay = Time.getJulianDay(goToTime.toMillis(false), goToTime.gmtoff); - - if (!forced && isInRange(startDay, startDay)) { - // No need to re-query - if (!mAgendaListView.isAgendaItemVisible(goToTime, id)) { - int gotoPosition = findEventPositionNearestTime(goToTime, id); - if (gotoPosition > 0) { - mAgendaListView.setSelectionFromTop(gotoPosition + - OFF_BY_ONE_BUG, mStickyHeaderSize); - if (mListViewScrollState == OnScrollListener.SCROLL_STATE_FLING) { - mAgendaListView.smoothScrollBy(0, 0); - } - if (refreshEventInfo) { - long newInstanceId = findInstanceIdFromPosition(gotoPosition); - if (newInstanceId != getSelectedInstanceId()) { - setSelectedInstanceId(newInstanceId); - mDataChangedHandler.post(mDataChangedRunnable); - Cursor tempCursor = getCursorByPosition(gotoPosition); - if (tempCursor != null) { - int tempCursorPosition = getCursorPositionByPosition(gotoPosition); - AgendaItem item = - buildAgendaItemFromCursor(tempCursor, tempCursorPosition, - false); - mSelectedVH = new AgendaAdapter.ViewHolder(); - mSelectedVH.allDay = item.allDay; - sendViewEvent(item, goToTime.toMillis(false)); - } - } - } - } - - Time actualTime = new Time(mTimeZone); - actualTime.set(goToTime); - CalendarController.getInstance(mContext).sendEvent(this, EventType.UPDATE_TITLE, - actualTime, actualTime, -1, ViewType.CURRENT); - } - return; - } - - // If AllInOneActivity is sending a second GOTO event(in OnResume), ignore it. - if (!mCleanQueryInitiated || searchQuery != null) { - // Query for a total of MIN_QUERY_DURATION days - int endDay = startDay + MIN_QUERY_DURATION; - - mSelectedInstanceId = -1; - mCleanQueryInitiated = true; - queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN, id); - - // Pre-fetch more data to overcome a race condition in AgendaListView.shiftSelection - // Queuing more data with the goToTime set to the selected time skips the call to - // shiftSelection on refresh. - mOlderRequests++; - queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_OLDER, id); - mNewerRequests++; - queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_NEWER, id); - } - } - - public void close() { - mShuttingDown = true; - pruneAdapterInfo(QUERY_TYPE_CLEAN); - if (mQueryHandler != null) { - mQueryHandler.cancelOperation(0); - } - } - - private DayAdapterInfo pruneAdapterInfo(int queryType) { - synchronized (mAdapterInfos) { - DayAdapterInfo recycleMe = null; - if (!mAdapterInfos.isEmpty()) { - if (mAdapterInfos.size() >= MAX_NUM_OF_ADAPTERS) { - if (queryType == QUERY_TYPE_NEWER) { - recycleMe = mAdapterInfos.removeFirst(); - } else if (queryType == QUERY_TYPE_OLDER) { - recycleMe = mAdapterInfos.removeLast(); - // Keep the size only if the oldest items are removed. - recycleMe.size = 0; - } - if (recycleMe != null) { - if (recycleMe.cursor != null) { - recycleMe.cursor.close(); - } - return recycleMe; - } - } - - if (mRowCount == 0 || queryType == QUERY_TYPE_CLEAN) { - mRowCount = 0; - int deletedRows = 0; - DayAdapterInfo info; - do { - info = mAdapterInfos.poll(); - if (info != null) { - // TODO the following causes ANR's. Do this in a thread. - info.cursor.close(); - deletedRows += info.size; - recycleMe = info; - } - } while (info != null); - - if (recycleMe != null) { - recycleMe.cursor = null; - recycleMe.size = deletedRows; - } - } - } - return recycleMe; - } - } - - private String buildQuerySelection() { - // Respect the preference to show/hide declined events - - if (mHideDeclined) { - return Calendars.VISIBLE + "=1 AND " - + Instances.SELF_ATTENDEE_STATUS + "!=" - + Attendees.ATTENDEE_STATUS_DECLINED; - } else { - return Calendars.VISIBLE + "=1"; - } - } - - private Uri buildQueryUri(int start, int end, String searchQuery) { - Uri rootUri = searchQuery == null ? - Instances.CONTENT_BY_DAY_URI : - Instances.CONTENT_SEARCH_BY_DAY_URI; - Uri.Builder builder = rootUri.buildUpon(); - ContentUris.appendId(builder, start); - ContentUris.appendId(builder, end); - if (searchQuery != null) { - builder.appendPath(searchQuery); - } - return builder.build(); - } - - private boolean isInRange(int start, int end) { - synchronized (mAdapterInfos) { - if (mAdapterInfos.isEmpty()) { - return false; - } - return mAdapterInfos.getFirst().start <= start && end <= mAdapterInfos.getLast().end; - } - } - - private int calculateQueryDuration(int start, int end) { - int queryDuration = MAX_QUERY_DURATION; - if (mRowCount != 0) { - queryDuration = IDEAL_NUM_OF_EVENTS * (end - start + 1) / mRowCount; - } - - if (queryDuration > MAX_QUERY_DURATION) { - queryDuration = MAX_QUERY_DURATION; - } else if (queryDuration < MIN_QUERY_DURATION) { - queryDuration = MIN_QUERY_DURATION; - } - - return queryDuration; - } - - private boolean queueQuery(int start, int end, Time goToTime, - String searchQuery, int queryType, long id) { - QuerySpec queryData = new QuerySpec(queryType); - queryData.goToTime = new Time(goToTime); // Creates a new time reference per QuerySpec. - queryData.start = start; - queryData.end = end; - queryData.searchQuery = searchQuery; - queryData.id = id; - return queueQuery(queryData); - } - - private boolean queueQuery(QuerySpec queryData) { - queryData.searchQuery = mSearchQuery; - Boolean queuedQuery; - synchronized (mQueryQueue) { - queuedQuery = false; - Boolean doQueryNow = mQueryQueue.isEmpty(); - mQueryQueue.add(queryData); - queuedQuery = true; - if (doQueryNow) { - doQuery(queryData); - } - } - return queuedQuery; - } - - private void doQuery(QuerySpec queryData) { - if (!mAdapterInfos.isEmpty()) { - int start = mAdapterInfos.getFirst().start; - int end = mAdapterInfos.getLast().end; - int queryDuration = calculateQueryDuration(start, end); - switch(queryData.queryType) { - case QUERY_TYPE_OLDER: - queryData.end = start - 1; - queryData.start = queryData.end - queryDuration; - break; - case QUERY_TYPE_NEWER: - queryData.start = end + 1; - queryData.end = queryData.start + queryDuration; - break; - } - - // By "compacting" cursors, this fixes the disco/ping-pong problem - // b/5311977 - if (mRowCount < 20 && queryData.queryType != QUERY_TYPE_CLEAN) { - if (DEBUGLOG) { - Log.e(TAG, "Compacting cursor: mRowCount=" + mRowCount - + " totalStart:" + start - + " totalEnd:" + end - + " query.start:" + queryData.start - + " query.end:" + queryData.end); - } - - queryData.queryType = QUERY_TYPE_CLEAN; - - if (queryData.start > start) { - queryData.start = start; - } - if (queryData.end < end) { - queryData.end = end; - } - } - } - - if (BASICLOG) { - Time time = new Time(mTimeZone); - time.setJulianDay(queryData.start); - Time time2 = new Time(mTimeZone); - time2.setJulianDay(queryData.end); - Log.v(TAG, "startQuery: " + time.toString() + " to " - + time2.toString() + " then go to " + queryData.goToTime); - } - - mQueryHandler.cancelOperation(0); - if (BASICLOG) queryData.queryStartMillis = System.nanoTime(); - - Uri queryUri = buildQueryUri( - queryData.start, queryData.end, queryData.searchQuery); - mQueryHandler.startQuery(0, queryData, queryUri, - PROJECTION, buildQuerySelection(), null, - AGENDA_SORT_ORDER); - } - - private String formatDateString(int julianDay) { - Time time = new Time(mTimeZone); - time.setJulianDay(julianDay); - long millis = time.toMillis(false); - mStringBuilder.setLength(0); - return DateUtils.formatDateRange(mContext, mFormatter, millis, millis, - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_ABBREV_MONTH, mTimeZone).toString(); - } - - private void updateHeaderFooter(final int start, final int end) { - mHeaderView.setText(mContext.getString(R.string.show_older_events, - formatDateString(start))); - mFooterView.setText(mContext.getString(R.string.show_newer_events, - formatDateString(end))); - } - - private class QueryHandler extends AsyncQueryHandler { - - public QueryHandler(ContentResolver cr) { - super(cr); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (DEBUGLOG) { - Log.d(TAG, "(+)onQueryComplete"); - } - QuerySpec data = (QuerySpec)cookie; - - if (cursor == null) { - if (mAgendaListView != null && mAgendaListView.getContext() instanceof Activity) { - ((Activity) mAgendaListView.getContext()).finish(); - } - return; - } - - if (BASICLOG) { - long queryEndMillis = System.nanoTime(); - Log.e(TAG, "Query time(ms): " - + (queryEndMillis - data.queryStartMillis) / 1000000 - + " Count: " + cursor.getCount()); - } - - if (data.queryType == QUERY_TYPE_CLEAN) { - mCleanQueryInitiated = false; - } - - if (mShuttingDown) { - cursor.close(); - return; - } - - // Notify Listview of changes and update position - int cursorSize = cursor.getCount(); - if (cursorSize > 0 || mAdapterInfos.isEmpty() || data.queryType == QUERY_TYPE_CLEAN) { - final int listPositionOffset = processNewCursor(data, cursor); - int newPosition = -1; - if (data.goToTime == null) { // Typical Scrolling type query - notifyDataSetChanged(); - if (listPositionOffset != 0) { - mAgendaListView.shiftSelection(listPositionOffset); - } - } else { // refresh() called. Go to the designated position - final Time goToTime = data.goToTime; - notifyDataSetChanged(); - newPosition = findEventPositionNearestTime(goToTime, data.id); - if (newPosition >= 0) { - if (mListViewScrollState == OnScrollListener.SCROLL_STATE_FLING) { - mAgendaListView.smoothScrollBy(0, 0); - } - mAgendaListView.setSelectionFromTop(newPosition + OFF_BY_ONE_BUG, - mStickyHeaderSize); - Time actualTime = new Time(mTimeZone); - actualTime.set(goToTime); - if (DEBUGLOG) { - Log.d(TAG, "onQueryComplete: Updating title..."); - } - CalendarController.getInstance(mContext).sendEvent(this, - EventType.UPDATE_TITLE, actualTime, actualTime, -1, - ViewType.CURRENT); - } - if (DEBUGLOG) { - Log.e(TAG, "Setting listview to " + - "findEventPositionNearestTime: " + (newPosition + OFF_BY_ONE_BUG)); - } - } - - // Make sure we change the selected instance Id only on a clean query and we - // do not have one set already - if (mSelectedInstanceId == -1 && newPosition != -1 && - data.queryType == QUERY_TYPE_CLEAN) { - if (data.id != -1 || data.goToTime != null) { - mSelectedInstanceId = findInstanceIdFromPosition(newPosition); - } - } - - // size == 1 means a fresh query. Possibly after the data changed. - // Let's check whether mSelectedInstanceId is still valid. - if (mAdapterInfos.size() == 1 && mSelectedInstanceId != -1) { - boolean found = false; - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - if (mSelectedInstanceId == cursor - .getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID)) { - found = true; - break; - } - }; - - if (!found) { - mSelectedInstanceId = -1; - } - } - - // Show the requested event - if (mShowEventOnStart && data.queryType == QUERY_TYPE_CLEAN) { - Cursor tempCursor = null; - int tempCursorPosition = -1; - - // If no valid event is selected , just pick the first one - if (mSelectedInstanceId == -1) { - if (cursor.moveToFirst()) { - mSelectedInstanceId = cursor - .getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID); - // Set up a dummy view holder so we have the right all day - // info when the view is created. - // TODO determine the full set of what might be useful to - // know about the selected view and fill it in. - mSelectedVH = new AgendaAdapter.ViewHolder(); - mSelectedVH.allDay = - cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - tempCursor = cursor; - } - } else if (newPosition != -1) { - tempCursor = getCursorByPosition(newPosition); - tempCursorPosition = getCursorPositionByPosition(newPosition); - } - if (tempCursor != null) { - AgendaItem item = buildAgendaItemFromCursor(tempCursor, tempCursorPosition, - false); - long selectedTime = findStartTimeFromPosition(newPosition); - if (DEBUGLOG) { - Log.d(TAG, "onQueryComplete: Sending View Event..."); - } - sendViewEvent(item, selectedTime); - } - } - } else { - cursor.close(); - } - - // Update header and footer - if (!mDoneSettingUpHeaderFooter) { - OnClickListener headerFooterOnClickListener = new OnClickListener() { - public void onClick(View v) { - if (v == mHeaderView) { - queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); - } else { - queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); - } - }}; - mHeaderView.setOnClickListener(headerFooterOnClickListener); - mFooterView.setOnClickListener(headerFooterOnClickListener); - mAgendaListView.addFooterView(mFooterView); - mDoneSettingUpHeaderFooter = true; - } - synchronized (mQueryQueue) { - int totalAgendaRangeStart = -1; - int totalAgendaRangeEnd = -1; - - if (cursorSize != 0) { - // Remove the query that just completed - QuerySpec x = mQueryQueue.poll(); - if (BASICLOG && !x.equals(data)) { - Log.e(TAG, "onQueryComplete - cookie != head of queue"); - } - mEmptyCursorCount = 0; - if (data.queryType == QUERY_TYPE_NEWER) { - mNewerRequestsProcessed++; - } else if (data.queryType == QUERY_TYPE_OLDER) { - mOlderRequestsProcessed++; - } - - totalAgendaRangeStart = mAdapterInfos.getFirst().start; - totalAgendaRangeEnd = mAdapterInfos.getLast().end; - } else { // CursorSize == 0 - QuerySpec querySpec = mQueryQueue.peek(); - - // Update Adapter Info with new start and end date range - if (!mAdapterInfos.isEmpty()) { - DayAdapterInfo first = mAdapterInfos.getFirst(); - DayAdapterInfo last = mAdapterInfos.getLast(); - - if (first.start - 1 <= querySpec.end && querySpec.start < first.start) { - first.start = querySpec.start; - } - - if (querySpec.start <= last.end + 1 && last.end < querySpec.end) { - last.end = querySpec.end; - } - - totalAgendaRangeStart = first.start; - totalAgendaRangeEnd = last.end; - } else { - totalAgendaRangeStart = querySpec.start; - totalAgendaRangeEnd = querySpec.end; - } - - // Update query specification with expanded search range - // and maybe rerun query - switch (querySpec.queryType) { - case QUERY_TYPE_OLDER: - totalAgendaRangeStart = querySpec.start; - querySpec.start -= MAX_QUERY_DURATION; - break; - case QUERY_TYPE_NEWER: - totalAgendaRangeEnd = querySpec.end; - querySpec.end += MAX_QUERY_DURATION; - break; - case QUERY_TYPE_CLEAN: - totalAgendaRangeStart = querySpec.start; - totalAgendaRangeEnd = querySpec.end; - querySpec.start -= MAX_QUERY_DURATION / 2; - querySpec.end += MAX_QUERY_DURATION / 2; - break; - } - - if (++mEmptyCursorCount > RETRIES_ON_NO_DATA) { - // Nothing in the cursor again. Dropping query - mQueryQueue.poll(); - } - } - - updateHeaderFooter(totalAgendaRangeStart, totalAgendaRangeEnd); - - // Go over the events and mark the first day after yesterday - // that has events in it - // If the range of adapters doesn't include yesterday, skip marking it since it will - // mark the first day in the adapters. - synchronized (mAdapterInfos) { - DayAdapterInfo info = mAdapterInfos.getFirst(); - Time time = new Time(mTimeZone); - long now = System.currentTimeMillis(); - time.set(now); - int JulianToday = Time.getJulianDay(now, time.gmtoff); - if (info != null && JulianToday >= info.start && JulianToday - <= mAdapterInfos.getLast().end) { - Iterator<DayAdapterInfo> iter = mAdapterInfos.iterator(); - boolean foundDay = false; - while (iter.hasNext() && !foundDay) { - info = iter.next(); - for (int i = 0; i < info.size; i++) { - if (info.dayAdapter.findJulianDayFromPosition(i) >= JulianToday) { - info.dayAdapter.setAsFirstDayAfterYesterday(i); - foundDay = true; - break; - } - } - } - } - } - - // Fire off the next query if any - Iterator<QuerySpec> it = mQueryQueue.iterator(); - while (it.hasNext()) { - QuerySpec queryData = it.next(); - if (queryData.queryType == QUERY_TYPE_CLEAN - || !isInRange(queryData.start, queryData.end)) { - // Query accepted - if (DEBUGLOG) Log.e(TAG, "Query accepted. QueueSize:" + mQueryQueue.size()); - doQuery(queryData); - break; - } else { - // Query rejected - it.remove(); - if (DEBUGLOG) Log.e(TAG, "Query rejected. QueueSize:" + mQueryQueue.size()); - } - } - } - if (BASICLOG) { - for (DayAdapterInfo info3 : mAdapterInfos) { - Log.e(TAG, "> " + info3.toString()); - } - } - } - - /* - * Update the adapter info array with a the new cursor. Close out old - * cursors as needed. - * - * @return number of rows removed from the beginning - */ - private int processNewCursor(QuerySpec data, Cursor cursor) { - synchronized (mAdapterInfos) { - // Remove adapter info's from adapterInfos as needed - DayAdapterInfo info = pruneAdapterInfo(data.queryType); - int listPositionOffset = 0; - if (info == null) { - info = new DayAdapterInfo(mContext); - } else { - if (DEBUGLOG) - Log.e(TAG, "processNewCursor listPositionOffsetA=" - + -info.size); - listPositionOffset = -info.size; - } - - // Setup adapter info - info.start = data.start; - info.end = data.end; - info.cursor = cursor; - info.dayAdapter.changeCursor(info); - info.size = info.dayAdapter.getCount(); - - // Insert into adapterInfos - if (mAdapterInfos.isEmpty() - || data.end <= mAdapterInfos.getFirst().start) { - mAdapterInfos.addFirst(info); - listPositionOffset += info.size; - } else if (BASICLOG && data.start < mAdapterInfos.getLast().end) { - mAdapterInfos.addLast(info); - for (DayAdapterInfo info2 : mAdapterInfos) { - Log.e("========== BUG ==", info2.toString()); - } - } else { - mAdapterInfos.addLast(info); - } - - // Update offsets in adapterInfos - mRowCount = 0; - for (DayAdapterInfo info3 : mAdapterInfos) { - info3.offset = mRowCount; - mRowCount += info3.size; - } - mLastUsedInfo = null; - - return listPositionOffset; - } - } - } - - static String getViewTitle(View x) { - String title = ""; - if (x != null) { - Object yy = x.getTag(); - if (yy instanceof AgendaAdapter.ViewHolder) { - TextView tv = ((AgendaAdapter.ViewHolder) yy).title; - if (tv != null) { - title = (String) tv.getText(); - } - } else if (yy != null) { - TextView dateView = ((AgendaByDayAdapter.ViewHolder) yy).dateView; - if (dateView != null) { - title = (String) dateView.getText(); - } - } - } - return title; - } - - public void onResume() { - mTZUpdater.run(); - } - - public void setHideDeclinedEvents(boolean hideDeclined) { - mHideDeclined = hideDeclined; - } - - public void setSelectedView(View v) { - if (v != null) { - Object vh = v.getTag(); - if (vh instanceof AgendaAdapter.ViewHolder) { - mSelectedVH = (AgendaAdapter.ViewHolder) vh; - if (mSelectedInstanceId != mSelectedVH.instanceId) { - mSelectedInstanceId = mSelectedVH.instanceId; - notifyDataSetChanged(); - } - } - } - } - - public AgendaAdapter.ViewHolder getSelectedViewHolder() { - return mSelectedVH; - } - - public long getSelectedInstanceId() { - return mSelectedInstanceId; - } - - public void setSelectedInstanceId(long selectedInstanceId) { - mSelectedInstanceId = selectedInstanceId; - mSelectedVH = null; - } - - private long findInstanceIdFromPosition(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getInstanceId(position - info.offset); - } - return -1; - } - - private long findStartTimeFromPosition(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getStartTime(position - info.offset); - } - return -1; - } - - - private Cursor getCursorByPosition(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.cursor; - } - return null; - } - - private int getCursorPositionByPosition(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getCursorPosition(position - info.offset); - } - return -1; - } - - // Implementation of HeaderIndexer interface for StickyHeeaderListView - - // Returns the location of the day header of a specific event specified in the position - // in the adapter - @Override - public int getHeaderPositionFromItemPosition(int position) { - - // For phone configuration, return -1 so there will be no sticky header - if (!mIsTabletConfig) { - return -1; - } - - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - int pos = info.dayAdapter.getHeaderPosition(position - info.offset); - return (pos != -1)?(pos + info.offset):-1; - } - return -1; - } - - // Returns the number of events for a specific day header - @Override - public int getHeaderItemsNumber(int headerPosition) { - if (headerPosition < 0 || !mIsTabletConfig) { - return -1; - } - DayAdapterInfo info = getAdapterInfoByPosition(headerPosition); - if (info != null) { - return info.dayAdapter.getHeaderItemsCount(headerPosition - info.offset); - } - return -1; - } - - @Override - public void OnHeaderHeightChanged(int height) { - mStickyHeaderSize = height; - } - - public int getStickyHeaderHeight() { - return mStickyHeaderSize; - } - - public void setScrollState(int state) { - mListViewScrollState = state; - } -} diff --git a/src/com/android/calendar/alerts/AlertActivity.java b/src/com/android/calendar/alerts/AlertActivity.java deleted file mode 100644 index 78733bd4..00000000 --- a/src/com/android/calendar/alerts/AlertActivity.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.alerts; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.NotificationManager; -import android.app.TaskStackBuilder; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.CalendarContract.CalendarAlerts; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.Button; -import android.widget.ListView; - -import com.android.calendar.AsyncQueryService; -import com.android.calendar.EventInfoActivity; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.alerts.GlobalDismissManager.AlarmId; - -import java.util.LinkedList; -import java.util.List; - -/** - * 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 implements OnClickListener { - private static final String TAG = "AlertActivity"; - - 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.CALENDAR_COLOR, // 7 - CalendarAlerts.RRULE, // 8 - CalendarAlerts.HAS_ALARM, // 9 - CalendarAlerts.STATE, // 10 - CalendarAlerts.ALARM_TIME, // 11 - }; - - public static final int INDEX_ROW_ID = 0; - 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; - - private static final String SELECTION = CalendarAlerts.STATE + "=?"; - private static final String[] SELECTIONARG = new String[] { - Integer.toString(CalendarAlerts.STATE_FIRED) - }; - - private AlertAdapter mAdapter; - private QueryHandler mQueryHandler; - private Cursor mCursor; - private ListView mListView; - private Button mDismissAllButton; - - - private void dismissFiredAlarms() { - ContentValues values = new ContentValues(1 /* size */); - values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED); - String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED; - mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values, - selection, null /* selectionArgs */, Utils.UNDO_DELAY); - - if (mCursor == null) { - Log.e(TAG, "Unable to globally dismiss all notifications because cursor was null."); - return; - } - if (mCursor.isClosed()) { - Log.e(TAG, "Unable to globally dismiss all notifications because cursor was closed."); - return; - } - if (!mCursor.moveToFirst()) { - Log.e(TAG, "Unable to globally dismiss all notifications because cursor was empty."); - return; - } - - List<AlarmId> alarmIds = new LinkedList<AlarmId>(); - do { - long eventId = mCursor.getLong(INDEX_EVENT_ID); - long eventStart = mCursor.getLong(INDEX_BEGIN); - alarmIds.add(new AlarmId(eventId, eventStart)); - } while (mCursor.moveToNext()); - initiateGlobalDismiss(alarmIds); - } - - private void dismissAlarm(long id, long eventId, long startTime) { - ContentValues values = new ContentValues(1 /* size */); - values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED); - String selection = CalendarAlerts._ID + "=" + id; - mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values, - selection, null /* selectionArgs */, Utils.UNDO_DELAY); - - List<AlarmId> alarmIds = new LinkedList<AlarmId>(); - alarmIds.add(new AlarmId(eventId, startTime)); - initiateGlobalDismiss(alarmIds); - } - - @SuppressWarnings("unchecked") - private void initiateGlobalDismiss(List<AlarmId> alarmIds) { - new AsyncTask<List<AlarmId>, Void, Void>() { - @Override - protected Void doInBackground(List<AlarmId>... params) { - GlobalDismissManager.dismissGlobally(getApplicationContext(), params[0]); - return null; - } - }.execute(alarmIds); - } - - private class QueryHandler extends AsyncQueryService { - public QueryHandler(Context context) { - super(context); - } - - @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); - mListView.setSelection(cursor.getCount() - 1); - - // The results are in, enable the buttons - mDismissAllButton.setEnabled(true); - } else { - cursor.close(); - } - } - - @Override - protected void onUpdateComplete(int token, Object cookie, int result) { - // Ignore - } - } - - private final OnItemClickListener mViewListener = new OnItemClickListener() { - - @SuppressLint("NewApi") - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, - long i) { - AlertActivity alertActivity = AlertActivity.this; - Cursor cursor = alertActivity.getItemForView(view); - - long alarmId = cursor.getLong(INDEX_ROW_ID); - long eventId = cursor.getLong(AlertActivity.INDEX_EVENT_ID); - long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN); - - // Mark this alarm as DISMISSED - dismissAlarm(alarmId, eventId, startMillis); - - // build an intent and task stack to start EventInfoActivity with AllInOneActivity - // as the parent activity rooted to home. - long endMillis = cursor.getLong(AlertActivity.INDEX_END); - Intent eventIntent = AlertUtils.buildEventViewIntent(AlertActivity.this, eventId, - startMillis, endMillis); - - if (Utils.isJellybeanOrLater()) { - TaskStackBuilder.create(AlertActivity.this).addParentStack(EventInfoActivity.class) - .addNextIntent(eventIntent).startActivities(); - } else { - alertActivity.startActivity(eventIntent); - } - - alertActivity.finish(); - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - setContentView(R.layout.alert_activity); - setTitle(R.string.alert_title); - - mQueryHandler = new QueryHandler(this); - mAdapter = new AlertAdapter(this, R.layout.alert_item); - - mListView = (ListView) findViewById(R.id.alert_container); - mListView.setItemsCanFocus(true); - mListView.setAdapter(mAdapter); - mListView.setOnItemClickListener(mViewListener); - - mDismissAllButton = (Button) findViewById(R.id.dismiss_all); - mDismissAllButton.setOnClickListener(this); - - // Disable the buttons, since they need mCursor, which is created asynchronously - 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; - mQueryHandler.startQuery(0, null, uri, PROJECTION, SELECTION, SELECTIONARG, - CalendarContract.CalendarAlerts.DEFAULT_SORT_ORDER); - } else { - if (!mCursor.requery()) { - Log.w(TAG, "Cursor#requery() failed."); - mCursor.close(); - mCursor = null; - } - } - } - - void closeActivityIfEmpty() { - if (mCursor != null && !mCursor.isClosed() && mCursor.getCount() == 0) { - AlertActivity.this.finish(); - } - } - - @Override - protected void onStop() { - super.onStop(); - // Can't run updateAlertNotification in main thread - AsyncTask task = new AsyncTask<Context, Void, Void>() { - @Override - protected Void doInBackground(Context ... params) { - AlertService.updateAlertNotification(params[0]); - return null; - } - }.execute(this); - - - if (mCursor != null) { - mCursor.deactivate(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mCursor != null) { - mCursor.close(); - } - } - - @Override - public void onClick(View v) { - if (v == mDismissAllButton) { - NotificationManager nm = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancelAll(); - - dismissFiredAlarms(); - - finish(); - } - } - - public boolean isEmpty() { - return mCursor != null ? (mCursor.getCount() == 0) : true; - } - - public Cursor getItemForView(View view) { - final int index = mListView.getPositionForView(view); - if (index < 0) { - return null; - } - return (Cursor) mListView.getAdapter().getItem(index); - } -} diff --git a/src/com/android/calendar/alerts/AlertAdapter.java b/src/com/android/calendar/alerts/AlertAdapter.java deleted file mode 100644 index 49e3aa0a..00000000 --- a/src/com/android/calendar/alerts/AlertAdapter.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.alerts; - -import com.android.calendar.R; -import com.android.calendar.Utils; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.view.View; -import android.widget.ResourceCursorAdapter; -import android.widget.TextView; - -import java.util.Locale; -import java.util.TimeZone; - -public class AlertAdapter extends ResourceCursorAdapter { - - private static AlertActivity alertActivity; - private static boolean mFirstTime = true; - private static int mTitleColor; - private static int mOtherColor; // non-title fields - private static int mPastEventColor; - - public AlertAdapter(AlertActivity activity, int resource) { - super(activity, resource, null); - alertActivity = activity; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - View square = view.findViewById(R.id.color_square); - int color = Utils.getDisplayColorFromColor(cursor.getInt(AlertActivity.INDEX_COLOR)); - square.setBackgroundColor(color); - - // Repeating info - View repeatContainer = view.findViewById(R.id.repeat_icon); - String rrule = cursor.getString(AlertActivity.INDEX_RRULE); - if (!TextUtils.isEmpty(rrule)) { - 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 titleView = (TextView) view.findViewById(R.id.event_title); - TextView whenView = (TextView) view.findViewById(R.id.when); - TextView whereView = (TextView) view.findViewById(R.id.where); - if (mFirstTime) { - mPastEventColor = res.getColor(R.color.alert_past_event); - mTitleColor = res.getColor(R.color.alert_event_title); - mOtherColor = res.getColor(R.color.alert_event_other); - mFirstTime = false; - } - - if (endMillis < System.currentTimeMillis()) { - titleView.setTextColor(mPastEventColor); - whenView.setTextColor(mPastEventColor); - whereView.setTextColor(mPastEventColor); - } else { - titleView.setTextColor(mTitleColor); - whenView.setTextColor(mOtherColor); - whereView.setTextColor(mOtherColor); - } - - // What - if (eventName == null || eventName.length() == 0) { - eventName = res.getString(R.string.no_title_label); - } - titleView.setText(eventName); - - // When - String when; - int flags; - String tz = Utils.getTimeZone(context, null); - if (allDay) { - flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY | - DateUtils.FORMAT_SHOW_DATE; - tz = Time.TIMEZONE_UTC; - } else { - flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE; - } - if (DateFormat.is24HourFormat(context)) { - flags |= DateUtils.FORMAT_24HOUR; - } - - Time time = new Time(tz); - time.set(startMillis); - boolean isDST = time.isDst != 0; - StringBuilder sb = new StringBuilder( - Utils.formatDateRange(context, startMillis, endMillis, flags)); - if (!allDay && tz != Time.getCurrentTimezone()) { - sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName( - isDST, TimeZone.SHORT, Locale.getDefault())); - } - - when = sb.toString(); - whenView.setText(when); - - // Where - if (location == null || location.length() == 0) { - whereView.setVisibility(View.GONE); - } else { - whereView.setText(location); - whereView.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onContentChanged () { - super.onContentChanged(); - - // Prevent empty popup notification. - alertActivity.closeActivityIfEmpty(); - } -} diff --git a/src/com/android/calendar/alerts/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java index 83dcc9fc..ce80cae1 100644 --- a/src/com/android/calendar/alerts/AlertReceiver.java +++ b/src/com/android/calendar/alerts/AlertReceiver.java @@ -68,192 +68,19 @@ import java.util.regex.Pattern; public class AlertReceiver extends BroadcastReceiver { private static final String TAG = "AlertReceiver"; - private static final String MAP_ACTION = "com.android.calendar.MAP"; - private static final String CALL_ACTION = "com.android.calendar.CALL"; - private static final String MAIL_ACTION = "com.android.calendar.MAIL"; - private static final String EXTRA_EVENT_ID = "eventid"; - // The broadcast for notification refreshes scheduled by the app. This is to // distinguish the EVENT_REMINDER broadcast sent by the provider. public static final String EVENT_REMINDER_APP_ACTION = "com.android.calendar.EVENT_REMINDER_APP"; - static final Object mStartingServiceSync = new Object(); - static PowerManager.WakeLock mStartingService; - private static final Pattern mBlankLinePattern = Pattern.compile("^\\s*$[\n\r]", - Pattern.MULTILINE); - public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"; - private static final int NOTIFICATION_DIGEST_MAX_LENGTH = 3; - - private static final String GEO_PREFIX = "geo:"; - private static final String TEL_PREFIX = "tel:"; - private static final int MAX_NOTIF_ACTIONS = 3; - - private static Handler sAsyncHandler; - static { - HandlerThread thr = new HandlerThread("AlertReceiver async"); - thr.start(); - sAsyncHandler = new Handler(thr.getLooper()); - } @Override public void onReceive(final Context context, final Intent intent) { if (AlertService.DEBUG) { Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); } - if (MAP_ACTION.equals(intent.getAction())) { - // Try starting the map action. - // If no map location is found (something changed since the notification was originally - // fired), update the notifications to express this change. - final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1); - if (eventId != -1) { - URLSpan[] urlSpans = getURLSpans(context, eventId); - Intent geoIntent = createMapActivityIntent(context, urlSpans); - if (geoIntent != null) { - // Location was successfully found, so dismiss the shade and start maps. - context.startActivity(geoIntent); - closeNotificationShade(context); - } else { - // No location was found, so update all notifications. - // Our alert service does not currently allow us to specify only one - // specific notification to refresh. - AlertService.updateAlertNotification(context); - } - } - } else if (CALL_ACTION.equals(intent.getAction())) { - // Try starting the call action. - // If no call location is found (something changed since the notification was originally - // fired), update the notifications to express this change. - final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1); - if (eventId != -1) { - URLSpan[] urlSpans = getURLSpans(context, eventId); - Intent callIntent = createCallActivityIntent(context, urlSpans); - if (callIntent != null) { - // Call location was successfully found, so dismiss the shade and start dialer. - context.startActivity(callIntent); - closeNotificationShade(context); - } else { - // No call location was found, so update all notifications. - // Our alert service does not currently allow us to specify only one - // specific notification to refresh. - AlertService.updateAlertNotification(context); - } - } - } else if (MAIL_ACTION.equals(intent.getAction())) { - closeNotificationShade(context); - - // Now start the email intent. - final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1); - if (eventId != -1) { - Intent i = new Intent(context, QuickResponseActivity.class); - i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, eventId); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } else { - Intent i = new Intent(); - i.setClass(context, AlertService.class); - i.putExtras(intent); - i.putExtra("action", intent.getAction()); - Uri uri = intent.getData(); - - // This intent might be a BOOT_COMPLETED so it might not have a Uri. - if (uri != null) { - i.putExtra("uri", uri.toString()); - } - beginStartingService(context, i); - } - } - - /** - * 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(); - } - } - } - } - - private static PendingIntent createClickEventIntent(Context context, long eventId, - long startMillis, long endMillis, int notificationId) { - return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId, - DismissAlarmsService.SHOW_ACTION); - } - - private static PendingIntent createDeleteEventIntent(Context context, long eventId, - long startMillis, long endMillis, int notificationId) { - return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId, - DismissAlarmsService.DISMISS_ACTION); - } - - private static PendingIntent createDismissAlarmsIntent(Context context, long eventId, - long startMillis, long endMillis, int notificationId, String action) { - Intent intent = new Intent(); - intent.setClass(context, DismissAlarmsService.class); - intent.setAction(action); - intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId); - intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); - intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); - intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId); - - // Must set a field that affects Intent.filterEquals so that the resulting - // PendingIntent will be a unique instance (the 'extras' don't achieve this). - // This must be unique for the click event across all reminders (so using - // event ID + startTime should be unique). This also must be unique from - // the delete event (which also uses DismissAlarmsService). - Uri.Builder builder = Events.CONTENT_URI.buildUpon(); - ContentUris.appendId(builder, eventId); - ContentUris.appendId(builder, startMillis); - intent.setData(builder.build()); - return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - private static PendingIntent createSnoozeIntent(Context context, long eventId, - long startMillis, long endMillis, int notificationId) { - Intent intent = new Intent(); - intent.setClass(context, SnoozeAlarmsService.class); - intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId); - intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); - intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); - intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId); - - Uri.Builder builder = Events.CONTENT_URI.buildUpon(); - ContentUris.appendId(builder, eventId); - ContentUris.appendId(builder, startMillis); - intent.setData(builder.build()); - return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - private static PendingIntent createAlertActivityIntent(Context context) { - Intent clickIntent = new Intent(); - clickIntent.setClass(context, AlertActivity.class); - clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return PendingIntent.getActivity(context, 0, clickIntent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + closeNotificationShade(context); } public static NotificationWrapper makeBasicNotification(Context context, String title, @@ -274,45 +101,10 @@ public class AlertReceiver extends BroadcastReceiver { title = resources.getString(R.string.no_title_label); } - // Create an intent triggered by clicking on the status icon, that dismisses the - // notification and shows the event. - PendingIntent clickIntent = createClickEventIntent(context, eventId, startMillis, - endMillis, notificationId); - - // Create a delete intent triggered by dismissing the notification. - PendingIntent deleteIntent = createDeleteEventIntent(context, eventId, startMillis, - endMillis, notificationId); - // Create the base notification. notificationBuilder.setContentTitle(title); notificationBuilder.setContentText(summaryText); notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar); - notificationBuilder.setContentIntent(clickIntent); - notificationBuilder.setDeleteIntent(deleteIntent); - if (doPopup) { - notificationBuilder.setFullScreenIntent(createAlertActivityIntent(context), true); - } - - PendingIntent mapIntent = null, callIntent = null, snoozeIntent = null, emailIntent = null; - if (addActionButtons) { - // Send map, call, and email intent back to ourself first for a couple reasons: - // 1) Workaround issue where clicking action button in notification does - // not automatically close the notification shade. - // 2) Event information will always be up to date. - - // Create map and/or call intents. - URLSpan[] urlSpans = getURLSpans(context, eventId); - mapIntent = createMapBroadcastIntent(context, urlSpans, eventId); - callIntent = createCallBroadcastIntent(context, urlSpans, eventId); - - // Create email intent for emailing attendees. - emailIntent = createBroadcastMailIntent(context, eventId, title); - - // Create snooze intent. TODO: change snooze to 10 minutes. - snoozeIntent = createSnoozeIntent(context, eventId, startMillis, endMillis, - notificationId); - } - if (Utils.isJellybeanOrLater()) { // Turn off timestamp. notificationBuilder.setWhen(0); @@ -320,554 +112,12 @@ public class AlertReceiver extends BroadcastReceiver { // Should be one of the values in Notification (ie. Notification.PRIORITY_HIGH, etc). // A higher priority will encourage notification manager to expand it. notificationBuilder.setPriority(priority); - - // Add action buttons. Show at most three, using the following priority ordering: - // 1. Map - // 2. Call - // 3. Email - // 4. Snooze - // Actions will only be shown if they are applicable; i.e. with no location, map will - // not be shown, and with no recipients, snooze will not be shown. - // TODO: Get icons, get strings. Maybe show preview of actual location/number? - int numActions = 0; - if (mapIntent != null && numActions < MAX_NOTIF_ACTIONS) { - notificationBuilder.addAction(R.drawable.ic_map, - resources.getString(R.string.map_label), mapIntent); - numActions++; - } - if (callIntent != null && numActions < MAX_NOTIF_ACTIONS) { - notificationBuilder.addAction(R.drawable.ic_call, - resources.getString(R.string.call_label), callIntent); - numActions++; - } - if (emailIntent != null && numActions < MAX_NOTIF_ACTIONS) { - notificationBuilder.addAction(R.drawable.ic_menu_email_holo_dark, - resources.getString(R.string.email_guests_label), emailIntent); - numActions++; - } - if (snoozeIntent != null && numActions < MAX_NOTIF_ACTIONS) { - notificationBuilder.addAction(R.drawable.ic_alarm_holo_dark, - resources.getString(R.string.snooze_label), snoozeIntent); - numActions++; - } - return notificationBuilder.getNotification(); - - } else { - // Old-style notification (pre-JB). Use custom view with buttons to provide - // JB-like functionality (snooze/email). - Notification n = notificationBuilder.getNotification(); - - // Use custom view with buttons to provide JB-like functionality (snooze/email). - RemoteViews contentView = new RemoteViews(context.getPackageName(), - R.layout.notification); - contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar); - contentView.setTextViewText(R.id.title, title); - contentView.setTextViewText(R.id.text, summaryText); - - int numActions = 0; - if (mapIntent == null || numActions >= MAX_NOTIF_ACTIONS) { - contentView.setViewVisibility(R.id.map_button, View.GONE); - } else { - contentView.setViewVisibility(R.id.map_button, View.VISIBLE); - contentView.setOnClickPendingIntent(R.id.map_button, mapIntent); - contentView.setViewVisibility(R.id.end_padding, View.GONE); - numActions++; - } - if (callIntent == null || numActions >= MAX_NOTIF_ACTIONS) { - contentView.setViewVisibility(R.id.call_button, View.GONE); - } else { - contentView.setViewVisibility(R.id.call_button, View.VISIBLE); - contentView.setOnClickPendingIntent(R.id.call_button, callIntent); - contentView.setViewVisibility(R.id.end_padding, View.GONE); - numActions++; - } - if (emailIntent == null || numActions >= MAX_NOTIF_ACTIONS) { - contentView.setViewVisibility(R.id.email_button, View.GONE); - } else { - contentView.setViewVisibility(R.id.email_button, View.VISIBLE); - contentView.setOnClickPendingIntent(R.id.email_button, emailIntent); - contentView.setViewVisibility(R.id.end_padding, View.GONE); - numActions++; - } - if (snoozeIntent == null || numActions >= MAX_NOTIF_ACTIONS) { - contentView.setViewVisibility(R.id.snooze_button, View.GONE); - } else { - contentView.setViewVisibility(R.id.snooze_button, View.VISIBLE); - contentView.setOnClickPendingIntent(R.id.snooze_button, snoozeIntent); - contentView.setViewVisibility(R.id.end_padding, View.GONE); - numActions++; - } - - n.contentView = contentView; - - return n; - } - } - - /** - * Creates an expanding notification. The initial expanded state is decided by - * the notification manager based on the priority. - */ - public static NotificationWrapper makeExpandingNotification(Context context, String title, - String summaryText, String description, long startMillis, long endMillis, long eventId, - int notificationId, boolean doPopup, int priority) { - Notification.Builder basicBuilder = new Notification.Builder(context); - Notification notification = buildBasicNotification(basicBuilder, context, title, - summaryText, startMillis, endMillis, eventId, notificationId, doPopup, - priority, true); - if (Utils.isJellybeanOrLater()) { - // Create a new-style expanded notification - Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle(); - if (description != null) { - description = mBlankLinePattern.matcher(description).replaceAll(""); - description = description.trim(); - } - CharSequence text; - if (TextUtils.isEmpty(description)) { - text = summaryText; - } else { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - stringBuilder.append(summaryText); - stringBuilder.append("\n\n"); - stringBuilder.setSpan(new RelativeSizeSpan(0.5f), summaryText.length(), - stringBuilder.length(), 0); - stringBuilder.append(description); - text = stringBuilder; - } - expandedBuilder.bigText(text); - basicBuilder.setStyle(expandedBuilder); - notification = basicBuilder.build(); - } - return new NotificationWrapper(notification, notificationId, eventId, startMillis, - endMillis, doPopup); - } - - /** - * Creates an expanding digest notification for expired events. - */ - public static NotificationWrapper makeDigestNotification(Context context, - ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle, - boolean expandable) { - if (notificationInfos == null || notificationInfos.size() < 1) { - return null; - } - - Resources res = context.getResources(); - int numEvents = notificationInfos.size(); - long[] eventIds = new long[notificationInfos.size()]; - long[] startMillis = new long[notificationInfos.size()]; - for (int i = 0; i < notificationInfos.size(); i++) { - eventIds[i] = notificationInfos.get(i).eventId; - startMillis[i] = notificationInfos.get(i).startMillis; - } - - // Create an intent triggered by clicking on the status icon that shows the alerts list. - PendingIntent pendingClickIntent = createAlertActivityIntent(context); - - // Create an intent triggered by dismissing the digest notification that clears all - // expired events. - Intent deleteIntent = new Intent(); - deleteIntent.setClass(context, DismissAlarmsService.class); - deleteIntent.setAction(DismissAlarmsService.DISMISS_ACTION); - deleteIntent.putExtra(AlertUtils.EVENT_IDS_KEY, eventIds); - deleteIntent.putExtra(AlertUtils.EVENT_STARTS_KEY, startMillis); - PendingIntent pendingDeleteIntent = PendingIntent.getService(context, 0, deleteIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - if (digestTitle == null || digestTitle.length() == 0) { - digestTitle = res.getString(R.string.no_title_label); - } - - Notification.Builder notificationBuilder = new Notification.Builder(context); - notificationBuilder.setContentText(digestTitle); - notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_multiple); - notificationBuilder.setContentIntent(pendingClickIntent); - notificationBuilder.setDeleteIntent(pendingDeleteIntent); - String nEventsStr = res.getQuantityString(R.plurals.Nevents, numEvents, numEvents); - notificationBuilder.setContentTitle(nEventsStr); - - Notification n; - if (Utils.isJellybeanOrLater()) { - // New-style notification... - - // Set to min priority to encourage the notification manager to collapse it. - notificationBuilder.setPriority(Notification.PRIORITY_MIN); - - if (expandable) { - // Multiple reminders. Combine into an expanded digest notification. - Notification.InboxStyle expandedBuilder = new Notification.InboxStyle(); - int i = 0; - for (AlertService.NotificationInfo info : notificationInfos) { - if (i < NOTIFICATION_DIGEST_MAX_LENGTH) { - String name = info.eventName; - if (TextUtils.isEmpty(name)) { - name = context.getResources().getString(R.string.no_title_label); - } - String timeLocation = AlertUtils.formatTimeLocation(context, - info.startMillis, info.allDay, info.location); - - TextAppearanceSpan primaryTextSpan = new TextAppearanceSpan(context, - R.style.NotificationPrimaryText); - TextAppearanceSpan secondaryTextSpan = new TextAppearanceSpan(context, - R.style.NotificationSecondaryText); - - // Event title in bold. - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - stringBuilder.append(name); - stringBuilder.setSpan(primaryTextSpan, 0, stringBuilder.length(), 0); - stringBuilder.append(" "); - - // Followed by time and location. - int secondaryIndex = stringBuilder.length(); - stringBuilder.append(timeLocation); - stringBuilder.setSpan(secondaryTextSpan, secondaryIndex, - stringBuilder.length(), 0); - expandedBuilder.addLine(stringBuilder); - i++; - } else { - break; - } - } - - // If there are too many to display, add "+X missed events" for the last line. - int remaining = numEvents - i; - if (remaining > 0) { - String nMoreEventsStr = res.getQuantityString(R.plurals.N_remaining_events, - remaining, remaining); - // TODO: Add highlighting and icon to this last entry once framework allows it. - expandedBuilder.setSummaryText(nMoreEventsStr); - } - - // Remove the title in the expanded form (redundant with the listed items). - expandedBuilder.setBigContentTitle(""); - notificationBuilder.setStyle(expandedBuilder); - } - - n = notificationBuilder.build(); - } else { - // Old-style notification (pre-JB). We only need a standard notification (no - // buttons) but use a custom view so it is consistent with the others. - n = notificationBuilder.getNotification(); - - // Use custom view with buttons to provide JB-like functionality (snooze/email). - RemoteViews contentView = new RemoteViews(context.getPackageName(), - R.layout.notification); - contentView.setImageViewResource(R.id.image, R.drawable.stat_notify_calendar_multiple); - contentView.setTextViewText(R.id.title, nEventsStr); - contentView.setTextViewText(R.id.text, digestTitle); - contentView.setViewVisibility(R.id.time, View.VISIBLE); - contentView.setViewVisibility(R.id.map_button, View.GONE); - contentView.setViewVisibility(R.id.call_button, View.GONE); - contentView.setViewVisibility(R.id.email_button, View.GONE); - contentView.setViewVisibility(R.id.snooze_button, View.GONE); - contentView.setViewVisibility(R.id.end_padding, View.VISIBLE); - n.contentView = contentView; - - // Use timestamp to force expired digest notification to the bottom (there is no - // priority setting before JB release). This is hidden by the custom view. - n.when = 1; - } - - NotificationWrapper nw = new NotificationWrapper(n); - if (AlertService.DEBUG) { - for (AlertService.NotificationInfo info : notificationInfos) { - nw.add(new NotificationWrapper(null, 0, info.eventId, info.startMillis, - info.endMillis, false)); - } } - return nw; + return notificationBuilder.getNotification(); } private void closeNotificationShade(Context context) { Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.sendBroadcast(closeNotificationShadeIntent); } - - private static final String[] ATTENDEES_PROJECTION = new String[] { - Attendees.ATTENDEE_EMAIL, // 0 - Attendees.ATTENDEE_STATUS, // 1 - }; - private static final int ATTENDEES_INDEX_EMAIL = 0; - private static final int ATTENDEES_INDEX_STATUS = 1; - private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; - private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " - + Attendees.ATTENDEE_EMAIL + " ASC"; - - private static final String[] EVENT_PROJECTION = new String[] { - Calendars.OWNER_ACCOUNT, // 0 - Calendars.ACCOUNT_NAME, // 1 - Events.TITLE, // 2 - Events.ORGANIZER, // 3 - }; - private static final int EVENT_INDEX_OWNER_ACCOUNT = 0; - private static final int EVENT_INDEX_ACCOUNT_NAME = 1; - private static final int EVENT_INDEX_TITLE = 2; - private static final int EVENT_INDEX_ORGANIZER = 3; - - private static Cursor getEventCursor(Context context, long eventId) { - return context.getContentResolver().query( - ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION, - null, null, null); - } - - private static Cursor getAttendeesCursor(Context context, long eventId) { - return context.getContentResolver().query(Attendees.CONTENT_URI, - ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) }, - ATTENDEES_SORT_ORDER); - } - - private static Cursor getLocationCursor(Context context, long eventId) { - return context.getContentResolver().query( - ContentUris.withAppendedId(Events.CONTENT_URI, eventId), - new String[] { Events.EVENT_LOCATION }, null, null, null); - } - - /** - * Creates a broadcast pending intent that fires to AlertReceiver when the email button - * is clicked. - */ - private static PendingIntent createBroadcastMailIntent(Context context, long eventId, - String eventTitle) { - // Query for viewer account. - String syncAccount = null; - Cursor eventCursor = getEventCursor(context, eventId); - try { - if (eventCursor != null && eventCursor.moveToFirst()) { - syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); - } - } finally { - if (eventCursor != null) { - eventCursor.close(); - } - } - - // Query attendees to see if there are any to email. - Cursor attendeesCursor = getAttendeesCursor(context, eventId); - try { - if (attendeesCursor != null && attendeesCursor.moveToFirst()) { - do { - String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); - if (Utils.isEmailableFrom(email, syncAccount)) { - Intent broadcastIntent = new Intent(MAIL_ACTION); - broadcastIntent.setClass(context, AlertReceiver.class); - broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); - return PendingIntent.getBroadcast(context, - Long.valueOf(eventId).hashCode(), broadcastIntent, - PendingIntent.FLAG_CANCEL_CURRENT); - } - } while (attendeesCursor.moveToNext()); - } - return null; - - } finally { - if (attendeesCursor != null) { - attendeesCursor.close(); - } - } - } - - /** - * Creates an Intent for emailing the attendees of the event. Returns null if there - * are no emailable attendees. - */ - static Intent createEmailIntent(Context context, long eventId, String body) { - // TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to - // be shared with EventInfoFragment. - - // Query for the owner account(s). - String ownerAccount = null; - String syncAccount = null; - String eventTitle = null; - String eventOrganizer = null; - Cursor eventCursor = getEventCursor(context, eventId); - try { - if (eventCursor != null && eventCursor.moveToFirst()) { - ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT); - syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); - eventTitle = eventCursor.getString(EVENT_INDEX_TITLE); - eventOrganizer = eventCursor.getString(EVENT_INDEX_ORGANIZER); - } - } finally { - if (eventCursor != null) { - eventCursor.close(); - } - } - if (TextUtils.isEmpty(eventTitle)) { - eventTitle = context.getResources().getString(R.string.no_title_label); - } - - // Query for the attendees. - List<String> toEmails = new ArrayList<String>(); - List<String> ccEmails = new ArrayList<String>(); - Cursor attendeesCursor = getAttendeesCursor(context, eventId); - try { - if (attendeesCursor != null && attendeesCursor.moveToFirst()) { - do { - int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS); - String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); - switch(status) { - case Attendees.ATTENDEE_STATUS_DECLINED: - addIfEmailable(ccEmails, email, syncAccount); - break; - default: - addIfEmailable(toEmails, email, syncAccount); - } - } while (attendeesCursor.moveToNext()); - } - } finally { - if (attendeesCursor != null) { - attendeesCursor.close(); - } - } - - // Add organizer only if no attendees to email (the case when too many attendees - // in the event to sync or show). - if (toEmails.size() == 0 && ccEmails.size() == 0 && eventOrganizer != null) { - addIfEmailable(toEmails, eventOrganizer, syncAccount); - } - - Intent intent = null; - if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) { - intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body, - toEmails, ccEmails, ownerAccount); - } - - if (intent == null) { - return null; - } - else { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return intent; - } - } - - private static void addIfEmailable(List<String> emailList, String email, String syncAccount) { - if (Utils.isEmailableFrom(email, syncAccount)) { - emailList.add(email); - } - } - - /** - * Using the linkify magic, get a list of URLs from the event's location. If no such links - * are found, we should end up with a single geo link of the entire string. - */ - private static URLSpan[] getURLSpans(Context context, long eventId) { - Cursor locationCursor = getLocationCursor(context, eventId); - - // Default to empty list - URLSpan[] urlSpans = new URLSpan[0]; - if (locationCursor != null && locationCursor.moveToFirst()) { - String location = locationCursor.getString(0); // Only one item in this cursor. - if (location != null && !location.isEmpty()) { - Spannable text = Utils.extendedLinkify(location, true); - // The linkify method should have found at least one link, at the very least. - // If no smart links were found, it should have set the whole string as a geo link. - urlSpans = text.getSpans(0, text.length(), URLSpan.class); - } - locationCursor.close(); - } - - return urlSpans; - } - - /** - * Create a pending intent to send ourself a broadcast to start maps, using the first map - * link available. - * If no links are found, return null. - */ - private static PendingIntent createMapBroadcastIntent(Context context, URLSpan[] urlSpans, - long eventId) { - for (int span_i = 0; span_i < urlSpans.length; span_i++) { - URLSpan urlSpan = urlSpans[span_i]; - String urlString = urlSpan.getURL(); - if (urlString.startsWith(GEO_PREFIX)) { - Intent broadcastIntent = new Intent(MAP_ACTION); - broadcastIntent.setClass(context, AlertReceiver.class); - broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); - return PendingIntent.getBroadcast(context, - Long.valueOf(eventId).hashCode(), broadcastIntent, - PendingIntent.FLAG_CANCEL_CURRENT); - } - } - - // No geo link was found, so return null; - return null; - } - - /** - * Create an intent to take the user to maps, using the first map link available. - * If no links are found, return null. - */ - private static Intent createMapActivityIntent(Context context, URLSpan[] urlSpans) { - for (int span_i = 0; span_i < urlSpans.length; span_i++) { - URLSpan urlSpan = urlSpans[span_i]; - String urlString = urlSpan.getURL(); - if (urlString.startsWith(GEO_PREFIX)) { - Intent geoIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlString)); - geoIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return geoIntent; - } - } - - // No geo link was found, so return null; - return null; - } - - /** - * Create a pending intent to send ourself a broadcast to take the user to dialer, or any other - * app capable of making phone calls. Use the first phone number available. If no phone number - * is found, or if the device is not capable of making phone calls (i.e. a tablet), return null. - */ - private static PendingIntent createCallBroadcastIntent(Context context, URLSpan[] urlSpans, - long eventId) { - // Return null if the device is unable to make phone calls. - TelephonyManager tm = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { - return null; - } - - for (int span_i = 0; span_i < urlSpans.length; span_i++) { - URLSpan urlSpan = urlSpans[span_i]; - String urlString = urlSpan.getURL(); - if (urlString.startsWith(TEL_PREFIX)) { - Intent broadcastIntent = new Intent(CALL_ACTION); - broadcastIntent.setClass(context, AlertReceiver.class); - broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); - return PendingIntent.getBroadcast(context, - Long.valueOf(eventId).hashCode(), broadcastIntent, - PendingIntent.FLAG_CANCEL_CURRENT); - } - } - - // No tel link was found, so return null; - return null; - } - - /** - * Create an intent to take the user to dialer, or any other app capable of making phone calls. - * Use the first phone number available. If no phone number is found, or if the device is - * not capable of making phone calls (i.e. a tablet), return null. - */ - private static Intent createCallActivityIntent(Context context, URLSpan[] urlSpans) { - // Return null if the device is unable to make phone calls. - TelephonyManager tm = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { - return null; - } - - for (int span_i = 0; span_i < urlSpans.length; span_i++) { - URLSpan urlSpan = urlSpans[span_i]; - String urlString = urlSpan.getURL(); - if (urlString.startsWith(TEL_PREFIX)) { - Intent callIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(urlString)); - callIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return callIntent; - } - } - - // No tel link was found, so return null; - return null; - } } diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java index ecd85723..d2c994da 100644 --- a/src/com/android/calendar/alerts/AlertService.java +++ b/src/com/android/calendar/alerts/AlertService.java @@ -42,8 +42,6 @@ import android.text.format.DateUtils; import android.text.format.Time; import android.util.Log; -import com.android.calendar.GeneralPreferences; -import com.android.calendar.OtherPreferences; import com.android.calendar.R; import com.android.calendar.Utils; @@ -60,7 +58,6 @@ public class AlertService extends Service { private static final String TAG = "AlertService"; private volatile Looper mServiceLooper; - private volatile ServiceHandler mServiceHandler; static final String[] ALERT_PROJECTION = new String[] { CalendarAlerts._ID, // 0 @@ -111,15 +108,6 @@ public class AlertService extends Service { // Hard limit to the number of notifications displayed. public static final int MAX_NOTIFICATIONS = 20; - // Shared prefs key for storing whether the EVENT_REMINDER event from the provider - // was ever received. Some OEMs modified this provider broadcast, so we had to - // do the alarm scheduling here in the app, for the unbundled app's reminders to work. - // If the EVENT_REMINDER event was ever received, we know we can skip our secondary - // alarm scheduling. - private static final String PROVIDER_REMINDER_PREF_KEY = - "preference_received_provider_reminder_broadcast"; - private static Boolean sReceivedProviderReminderBroadcast = null; - // Added wrapper for testing public static class NotificationWrapper { Notification mNotification; @@ -170,740 +158,6 @@ public class AlertService extends Service { } } - 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 (DEBUG) { - Log.d(TAG, bundle.getLong(android.provider.CalendarContract.CalendarAlerts.ALARM_TIME) - + " Action = " + action); - } - - // Some OEMs had changed the provider's EVENT_REMINDER broadcast to their own event, - // which broke our unbundled app's reminders. So we added backup alarm scheduling to the - // app, but we know we can turn it off if we ever receive the EVENT_REMINDER broadcast. - boolean providerReminder = action.equals( - android.provider.CalendarContract.ACTION_EVENT_REMINDER); - if (providerReminder) { - if (sReceivedProviderReminderBroadcast == null) { - sReceivedProviderReminderBroadcast = Utils.getSharedPreference(this, - PROVIDER_REMINDER_PREF_KEY, false); - } - - if (!sReceivedProviderReminderBroadcast) { - sReceivedProviderReminderBroadcast = true; - Log.d(TAG, "Setting key " + PROVIDER_REMINDER_PREF_KEY + " to: true"); - Utils.setSharedPreference(this, PROVIDER_REMINDER_PREF_KEY, true); - } - } - - if (providerReminder || - action.equals(Intent.ACTION_PROVIDER_CHANGED) || - action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER) || - action.equals(AlertReceiver.EVENT_REMINDER_APP_ACTION) || - action.equals(Intent.ACTION_LOCALE_CHANGED)) { - - // b/7652098: Add a delay after the provider-changed event before refreshing - // notifications to help issue with the unbundled app installed on HTC having - // stale notifications. - if (action.equals(Intent.ACTION_PROVIDER_CHANGED)) { - try { - Thread.sleep(5000); - } catch (Exception e) { - // Ignore. - } - } - - // If we dismissed a notification for a new event, then we need to sync the cache when - // an ACTION_PROVIDER_CHANGED event has been sent. Unfortunately, the data provider - // has a delay of CalendarProvider2.SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS (ie. 30 sec.) - // until it notifies us that the sync adapter has finished. - // TODO(psliwowski): Find a quicker way to be notified when the data provider has the - // syncId for event. - GlobalDismissManager.syncSenderDismissCache(this); - updateAlertNotification(this); - } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - // The provider usually initiates this setting up of alarms on startup, - // but there was a bug (b/7221716) where a race condition caused this step to be - // skipped, resulting in missed alarms. This is a stopgap to minimize this bug - // for devices that don't have the provider fix, by initiating this a 2nd time here. - // However, it would still theoretically be possible to hit the race condition - // the 2nd time and still miss alarms. - // - // TODO: Remove this when the provider fix is rolled out everywhere. - Intent intent = new Intent(); - intent.setClass(this, InitAlarmsService.class); - startService(intent); - } else if (action.equals(Intent.ACTION_TIME_CHANGED)) { - doTimeChanged(); - } else if (action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) { - dismissOldAlerts(this); - } else { - Log.w(TAG, "Invalid action: " + action); - } - - // Schedule the alarm for the next upcoming reminder, if not done by the provider. - if (sReceivedProviderReminderBroadcast == null || !sReceivedProviderReminderBroadcast) { - Log.d(TAG, "Scheduling next alarm with AlarmScheduler. " - + "sEventReminderReceived: " + sReceivedProviderReminderBroadcast); - AlarmScheduler.scheduleNextAlarm(this); - } - } - - static void dismissOldAlerts(Context context) { - ContentResolver cr = context.getContentResolver(); - final long currentTime = System.currentTimeMillis(); - ContentValues vals = new ContentValues(); - vals.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED); - cr.update(CalendarAlerts.CONTENT_URI, vals, DISMISS_OLD_SELECTION, new String[] { - Long.toString(currentTime), Integer.toString(CalendarAlerts.STATE_SCHEDULED) - }); - } - - static boolean updateAlertNotification(Context context) { - ContentResolver cr = context.getContentResolver(); - NotificationMgr nm = new NotificationMgrWrapper( - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); - final long currentTime = System.currentTimeMillis(); - SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); - - if (DEBUG) { - Log.d(TAG, "Beginning updateAlertNotification"); - } - - if (!prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true)) { - if (DEBUG) { - Log.d(TAG, "alert preference is OFF"); - } - - // If we shouldn't be showing notifications cancel any existing ones - // and return. - nm.cancelAll(); - return true; - } - - // Sync CalendarAlerts with global dismiss cache before query it - GlobalDismissManager.syncReceiverDismissCache(context); - Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION, - (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS, - ACTIVE_ALERTS_SORT); - - if (alertCursor == null || alertCursor.getCount() == 0) { - if (alertCursor != null) { - alertCursor.close(); - } - - if (DEBUG) Log.d(TAG, "No fired or scheduled alerts"); - nm.cancelAll(); - return false; - } - - return generateAlerts(context, nm, AlertUtils.createAlarmManager(context), prefs, - alertCursor, currentTime, MAX_NOTIFICATIONS); - } - - public static boolean generateAlerts(Context context, NotificationMgr nm, - AlarmManagerInterface alarmMgr, SharedPreferences prefs, Cursor alertCursor, - final long currentTime, final int maxNotifications) { - if (DEBUG) { - Log.d(TAG, "alertCursor count:" + alertCursor.getCount()); - } - - // Process the query results and bucketize events. - ArrayList<NotificationInfo> highPriorityEvents = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> mediumPriorityEvents = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> lowPriorityEvents = new ArrayList<NotificationInfo>(); - int numFired = processQuery(alertCursor, context, currentTime, highPriorityEvents, - mediumPriorityEvents, lowPriorityEvents); - - if (highPriorityEvents.size() + mediumPriorityEvents.size() - + lowPriorityEvents.size() == 0) { - nm.cancelAll(); - return true; - } - - long nextRefreshTime = Long.MAX_VALUE; - int currentNotificationId = 1; - NotificationPrefs notificationPrefs = new NotificationPrefs(context, prefs, - (numFired == 0)); - - // If there are more high/medium priority events than we can show, bump some to - // the low priority digest. - redistributeBuckets(highPriorityEvents, mediumPriorityEvents, lowPriorityEvents, - maxNotifications); - - // Post the individual higher priority events (future and recently started - // concurrent events). Order these so that earlier start times appear higher in - // the notification list. - for (int i = 0; i < highPriorityEvents.size(); i++) { - NotificationInfo info = highPriorityEvents.get(i); - String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis, - info.allDay, info.location); - postNotification(info, summaryText, context, true, notificationPrefs, nm, - currentNotificationId++); - - // Keep concurrent events high priority (to appear higher in the notification list) - // until 15 minutes into the event. - nextRefreshTime = Math.min(nextRefreshTime, getNextRefreshTime(info, currentTime)); - } - - // Post the medium priority events (concurrent events that started a while ago). - // Order these so more recent start times appear higher in the notification list. - // - // TODO: Post these with the same notification priority level as the higher priority - // events, so that all notifications will be co-located together. - for (int i = mediumPriorityEvents.size() - 1; i >= 0; i--) { - NotificationInfo info = mediumPriorityEvents.get(i); - // TODO: Change to a relative time description like: "Started 40 minutes ago". - // This requires constant refreshing to the message as time goes. - String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis, - info.allDay, info.location); - postNotification(info, summaryText, context, false, notificationPrefs, nm, - currentNotificationId++); - - // Refresh when concurrent event ends so it will drop into the expired digest. - nextRefreshTime = Math.min(nextRefreshTime, getNextRefreshTime(info, currentTime)); - } - - // Post the low priority events as 1 combined notification. - int numLowPriority = lowPriorityEvents.size(); - if (numLowPriority > 0) { - String expiredDigestTitle = getDigestTitle(lowPriorityEvents); - NotificationWrapper notification; - if (numLowPriority == 1) { - // If only 1 expired event, display an "old-style" basic alert. - NotificationInfo info = lowPriorityEvents.get(0); - String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis, - info.allDay, info.location); - notification = AlertReceiver.makeBasicNotification(context, info.eventName, - summaryText, info.startMillis, info.endMillis, info.eventId, - AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, false, - Notification.PRIORITY_MIN); - } else { - // Multiple expired events are listed in a digest. - notification = AlertReceiver.makeDigestNotification(context, - lowPriorityEvents, expiredDigestTitle, false); - } - - // Add options for a quiet update. - addNotificationOptions(notification, true, expiredDigestTitle, - notificationPrefs.getDefaultVibrate(), - notificationPrefs.getRingtoneAndSilence(), - false); /* Do not show the LED for the expired events. */ - - if (DEBUG) { - Log.d(TAG, "Quietly posting digest alarm notification, numEvents:" + numLowPriority - + ", notificationId:" + AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID); - } - - // Post the new notification for the group. - nm.notify(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, notification); - } else { - nm.cancel(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID); - if (DEBUG) { - Log.d(TAG, "No low priority events, canceling the digest notification."); - } - } - - // Remove the notifications that are hanging around from the previous refresh. - if (currentNotificationId <= maxNotifications) { - nm.cancelAllBetween(currentNotificationId, maxNotifications); - if (DEBUG) { - Log.d(TAG, "Canceling leftover notification IDs " + currentNotificationId + "-" - + maxNotifications); - } - } - - // Schedule the next silent refresh time so notifications will change - // buckets (eg. drop into expired digest, etc). - if (nextRefreshTime < Long.MAX_VALUE && nextRefreshTime > currentTime) { - AlertUtils.scheduleNextNotificationRefresh(context, alarmMgr, nextRefreshTime); - if (DEBUG) { - long minutesBeforeRefresh = (nextRefreshTime - currentTime) / MINUTE_MS; - Time time = new Time(); - time.set(nextRefreshTime); - String msg = String.format("Scheduling next notification refresh in %d min at: " - + "%d:%02d", minutesBeforeRefresh, time.hour, time.minute); - Log.d(TAG, msg); - } - } else if (nextRefreshTime < currentTime) { - Log.e(TAG, "Illegal state: next notification refresh time found to be in the past."); - } - - // Flushes old fired alerts from internal storage, if needed. - AlertUtils.flushOldAlertsFromInternalStorage(context); - - return true; - } - - /** - * Redistributes events in the priority lists based on the max # of notifications we - * can show. - */ - static void redistributeBuckets(ArrayList<NotificationInfo> highPriorityEvents, - ArrayList<NotificationInfo> mediumPriorityEvents, - ArrayList<NotificationInfo> lowPriorityEvents, int maxNotifications) { - - // If too many high priority alerts, shift the remaining high priority and all the - // medium priority ones to the low priority bucket. Note that order is important - // here; these lists are sorted by descending start time. Maintain that ordering - // so posted notifications are in the expected order. - if (highPriorityEvents.size() > maxNotifications) { - // Move mid-priority to the digest. - lowPriorityEvents.addAll(0, mediumPriorityEvents); - - // Move the rest of the high priority ones (latest ones) to the digest. - List<NotificationInfo> itemsToMoveSublist = highPriorityEvents.subList( - 0, highPriorityEvents.size() - maxNotifications); - // TODO: What order for high priority in the digest? - lowPriorityEvents.addAll(0, itemsToMoveSublist); - if (DEBUG) { - logEventIdsBumped(mediumPriorityEvents, itemsToMoveSublist); - } - mediumPriorityEvents.clear(); - // Clearing the sublist view removes the items from the highPriorityEvents list. - itemsToMoveSublist.clear(); - } - - // Bump the medium priority events if necessary. - if (mediumPriorityEvents.size() + highPriorityEvents.size() > maxNotifications) { - int spaceRemaining = maxNotifications - highPriorityEvents.size(); - - // Reached our max, move the rest to the digest. Since these are concurrent - // events, we move the ones with the earlier start time first since they are - // further in the past and less important. - List<NotificationInfo> itemsToMoveSublist = mediumPriorityEvents.subList( - spaceRemaining, mediumPriorityEvents.size()); - lowPriorityEvents.addAll(0, itemsToMoveSublist); - if (DEBUG) { - logEventIdsBumped(itemsToMoveSublist, null); - } - - // Clearing the sublist view removes the items from the mediumPriorityEvents list. - itemsToMoveSublist.clear(); - } - } - - private static void logEventIdsBumped(List<NotificationInfo> list1, - List<NotificationInfo> list2) { - StringBuilder ids = new StringBuilder(); - if (list1 != null) { - for (NotificationInfo info : list1) { - ids.append(info.eventId); - ids.append(","); - } - } - if (list2 != null) { - for (NotificationInfo info : list2) { - ids.append(info.eventId); - ids.append(","); - } - } - if (ids.length() > 0 && ids.charAt(ids.length() - 1) == ',') { - ids.setLength(ids.length() - 1); - } - if (ids.length() > 0) { - Log.d(TAG, "Reached max postings, bumping event IDs {" + ids.toString() - + "} to digest."); - } - } - - private static long getNextRefreshTime(NotificationInfo info, long currentTime) { - long startAdjustedForAllDay = info.startMillis; - long endAdjustedForAllDay = info.endMillis; - if (info.allDay) { - Time t = new Time(); - startAdjustedForAllDay = Utils.convertAlldayUtcToLocal(t, info.startMillis, - Time.getCurrentTimezone()); - endAdjustedForAllDay = Utils.convertAlldayUtcToLocal(t, info.startMillis, - Time.getCurrentTimezone()); - } - - // We change an event's priority bucket at 15 minutes into the event or 1/4 event duration. - long nextRefreshTime = Long.MAX_VALUE; - long gracePeriodCutoff = startAdjustedForAllDay + - getGracePeriodMs(startAdjustedForAllDay, endAdjustedForAllDay, info.allDay); - if (gracePeriodCutoff > currentTime) { - nextRefreshTime = Math.min(nextRefreshTime, gracePeriodCutoff); - } - - // ... and at the end (so expiring ones drop into a digest). - if (endAdjustedForAllDay > currentTime && endAdjustedForAllDay > gracePeriodCutoff) { - nextRefreshTime = Math.min(nextRefreshTime, endAdjustedForAllDay); - } - return nextRefreshTime; - } - - /** - * Processes the query results and bucketizes the alerts. - * - * @param highPriorityEvents This will contain future events, and concurrent events - * that started recently (less than the interval DEPRIORITIZE_GRACE_PERIOD_MS). - * @param mediumPriorityEvents This will contain concurrent events that started - * more than DEPRIORITIZE_GRACE_PERIOD_MS ago. - * @param lowPriorityEvents Will contain events that have ended. - * @return Returns the number of new alerts to fire. If this is 0, it implies - * a quiet update. - */ - static int processQuery(final Cursor alertCursor, final Context context, - final long currentTime, ArrayList<NotificationInfo> highPriorityEvents, - ArrayList<NotificationInfo> mediumPriorityEvents, - ArrayList<NotificationInfo> lowPriorityEvents) { - // Experimental reminder setting to only remind for events that have - // been responded to with "yes" or "maybe". - String skipRemindersPref = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_REMINDERS_RESPONDED, ""); - // Skip no-response events if the "Skip Reminders" preference has the second option, - // "If declined or not responded", is selected. - // Note that by default, the first option will be selected, so this will be false. - boolean remindRespondedOnly = skipRemindersPref.equals(context.getResources(). - getStringArray(R.array.preferences_skip_reminders_values)[1]); - // Experimental reminder setting to silence reminders when they are - // during the pre-defined quiet hours. - boolean useQuietHours = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_QUIET_HOURS, false); - // Note that the start time may be either before or after the end time, - // depending on whether quiet hours cross through midnight. - int quietHoursStartHour = - OtherPreferences.QUIET_HOURS_DEFAULT_START_HOUR; - int quietHoursStartMinute = - OtherPreferences.QUIET_HOURS_DEFAULT_START_MINUTE; - int quietHoursEndHour = - OtherPreferences.QUIET_HOURS_DEFAULT_END_HOUR; - int quietHoursEndMinute = - OtherPreferences.QUIET_HOURS_DEFAULT_END_MINUTE; - if (useQuietHours) { - quietHoursStartHour = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_QUIET_HOURS_START_HOUR, - OtherPreferences.QUIET_HOURS_DEFAULT_START_HOUR); - quietHoursStartMinute = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_QUIET_HOURS_START_MINUTE, - OtherPreferences.QUIET_HOURS_DEFAULT_START_MINUTE); - quietHoursEndHour = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_QUIET_HOURS_END_HOUR, - OtherPreferences.QUIET_HOURS_DEFAULT_END_HOUR); - quietHoursEndMinute = Utils.getSharedPreference(context, - OtherPreferences.KEY_OTHER_QUIET_HOURS_END_MINUTE, - OtherPreferences.QUIET_HOURS_DEFAULT_END_MINUTE); - } - Time time = new Time(); - - ContentResolver cr = context.getContentResolver(); - HashMap<Long, NotificationInfo> eventIds = new HashMap<Long, NotificationInfo>(); - int numFired = 0; - try { - while (alertCursor.moveToNext()) { - final long alertId = alertCursor.getLong(ALERT_INDEX_ID); - final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID); - final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES); - final String eventName = alertCursor.getString(ALERT_INDEX_TITLE); - final String description = alertCursor.getString(ALERT_INDEX_DESCRIPTION); - final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION); - final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS); - final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED; - final boolean responded = status != Attendees.ATTENDEE_STATUS_NONE - && status != Attendees.ATTENDEE_STATUS_INVITED; - final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN); - final long endTime = alertCursor.getLong(ALERT_INDEX_END); - final Uri alertUri = ContentUris - .withAppendedId(CalendarAlerts.CONTENT_URI, alertId); - final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME); - boolean forceQuiet = false; - if (useQuietHours) { - // Quiet hours have been set. - time.set(alarmTime); - // Check whether the alarm will fire after the quiet hours - // start time and/or before the quiet hours end time. - boolean alarmAfterQuietHoursStart = - (time.hour > quietHoursStartHour || - (time.hour == quietHoursStartHour - && time.minute >= quietHoursStartMinute)); - boolean alarmBeforeQuietHoursEnd = - (time.hour < quietHoursEndHour || - (time.hour == quietHoursEndHour - && time.minute <= quietHoursEndMinute)); - // Check if quiet hours crosses through midnight, iff: - // start hour is after end hour, or - // start hour is equal to end hour, and start minute is - // after end minute. - // i.e. 22:30 - 06:45; 12:45 - 12:00 - // 01:05 - 10:30; 05:00 - 05:30 - boolean quietHoursCrossesMidnight = - quietHoursStartHour > quietHoursEndHour || - (quietHoursStartHour == quietHoursEndHour - && quietHoursStartMinute > quietHoursEndMinute); - if (quietHoursCrossesMidnight) { - // Quiet hours crosses midnight. Alarm should be quiet - // if it's after start time OR before end time. - if (alarmAfterQuietHoursStart || - alarmBeforeQuietHoursEnd) { - forceQuiet = true; - } - } else { - // Quiet hours doesn't cross midnight. Alarm should be - // quiet if it's after start time AND before end time. - if (alarmAfterQuietHoursStart && - alarmBeforeQuietHoursEnd) { - forceQuiet = true; - } - } - } - int state = alertCursor.getInt(ALERT_INDEX_STATE); - final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0; - - // Use app local storage to keep track of fired alerts to fix problem of multiple - // installed calendar apps potentially causing missed alarms. - boolean newAlertOverride = false; - if (AlertUtils.BYPASS_DB && ((currentTime - alarmTime) / MINUTE_MS < 1)) { - // To avoid re-firing alerts, only fire if alarmTime is very recent. Otherwise - // we can get refires for non-dismissed alerts after app installation, or if the - // SharedPrefs was cleared too early. This means alerts that were timed while - // the phone was off may show up silently in the notification bar. - boolean alreadyFired = AlertUtils.hasAlertFiredInSharedPrefs(context, eventId, - beginTime, alarmTime); - if (!alreadyFired) { - newAlertOverride = true; - } - } - - if (DEBUG) { - StringBuilder msgBuilder = new StringBuilder(); - msgBuilder.append("alertCursor result: alarmTime:").append(alarmTime) - .append(" alertId:").append(alertId) - .append(" eventId:").append(eventId) - .append(" state: ").append(state) - .append(" minutes:").append(minutes) - .append(" declined:").append(declined) - .append(" responded:").append(responded) - .append(" beginTime:").append(beginTime) - .append(" endTime:").append(endTime) - .append(" allDay:").append(allDay) - .append(" alarmTime:").append(alarmTime) - .append(" forceQuiet:").append(forceQuiet); - if (AlertUtils.BYPASS_DB) { - msgBuilder.append(" newAlertOverride: " + newAlertOverride); - } - Log.d(TAG, msgBuilder.toString()); - } - - ContentValues values = new ContentValues(); - int newState = -1; - boolean newAlert = false; - - // Uncomment for the behavior of clearing out alerts after the - // events ended. b/1880369 - // - // if (endTime < currentTime) { - // newState = CalendarAlerts.DISMISSED; - // } else - - // Remove declined events - boolean sendAlert = !declined; - // Check for experimental reminder settings. - if (remindRespondedOnly) { - // If the experimental setting is turned on, then only send - // the alert if you've responded to the event. - sendAlert = sendAlert && responded; - } - if (sendAlert) { - if (state == CalendarAlerts.STATE_SCHEDULED || newAlertOverride) { - newState = CalendarAlerts.STATE_FIRED; - numFired++; - // If quiet hours are forcing the alarm to be silent, - // keep newAlert as false so it will not make noise. - if (!forceQuiet) { - newAlert = true; - } - - // Record the received time in the CalendarAlerts table. - // This is useful for finding bugs that cause alarms to be - // missed or delayed. - values.put(CalendarAlerts.RECEIVED_TIME, currentTime); - } - } else { - newState = CalendarAlerts.STATE_DISMISSED; - } - - // Update row if state changed - if (newState != -1) { - values.put(CalendarAlerts.STATE, newState); - state = newState; - - if (AlertUtils.BYPASS_DB) { - AlertUtils.setAlertFiredInSharedPrefs(context, eventId, beginTime, - alarmTime); - } - } - - if (state == CalendarAlerts.STATE_FIRED) { - // Record the time posting to notification manager. - // This is used for debugging missed alarms. - values.put(CalendarAlerts.NOTIFY_TIME, currentTime); - } - - // Write row to if anything changed - if (values.size() > 0) cr.update(alertUri, values, null, null); - - if (state != CalendarAlerts.STATE_FIRED) { - continue; - } - - // TODO: Prefer accepted events in case of ties. - NotificationInfo newInfo = new NotificationInfo(eventName, location, - description, beginTime, endTime, eventId, allDay, newAlert); - - // Adjust for all day events to ensure the right bucket. Don't use the 1/4 event - // duration grace period for these. - long beginTimeAdjustedForAllDay = beginTime; - String tz = null; - if (allDay) { - tz = TimeZone.getDefault().getID(); - beginTimeAdjustedForAllDay = Utils.convertAlldayUtcToLocal(null, beginTime, - tz); - } - - // Handle multiple alerts for the same event ID. - if (eventIds.containsKey(eventId)) { - NotificationInfo oldInfo = eventIds.get(eventId); - long oldBeginTimeAdjustedForAllDay = oldInfo.startMillis; - if (allDay) { - oldBeginTimeAdjustedForAllDay = Utils.convertAlldayUtcToLocal(null, - oldInfo.startMillis, tz); - } - - // Determine whether to replace the previous reminder with this one. - // Query results are sorted so this one will always have a lower start time. - long oldStartInterval = oldBeginTimeAdjustedForAllDay - currentTime; - long newStartInterval = beginTimeAdjustedForAllDay - currentTime; - boolean dropOld; - if (newStartInterval < 0 && oldStartInterval > 0) { - // Use this reminder if this event started recently - dropOld = Math.abs(newStartInterval) < MIN_DEPRIORITIZE_GRACE_PERIOD_MS; - } else { - // ... or if this one has a closer start time. - dropOld = Math.abs(newStartInterval) < Math.abs(oldStartInterval); - } - - if (dropOld) { - // This is a recurring event that has a more relevant start time, - // drop other reminder in favor of this one. - // - // It will only be present in 1 of these buckets; just remove from - // multiple buckets since this occurrence is rare enough that the - // inefficiency of multiple removals shouldn't be a big deal to - // justify a more complicated data structure. Expired events don't - // have individual notifications so we don't need to clean that up. - highPriorityEvents.remove(oldInfo); - mediumPriorityEvents.remove(oldInfo); - if (DEBUG) { - Log.d(TAG, "Dropping alert for recurring event ID:" + oldInfo.eventId - + ", startTime:" + oldInfo.startMillis - + " in favor of startTime:" + newInfo.startMillis); - } - } else { - // Skip duplicate reminders for the same event instance. - continue; - } - } - - // TODO: Prioritize by "primary" calendar - eventIds.put(eventId, newInfo); - long highPriorityCutoff = currentTime - - getGracePeriodMs(beginTime, endTime, allDay); - - if (beginTimeAdjustedForAllDay > highPriorityCutoff) { - // High priority = future events or events that just started - highPriorityEvents.add(newInfo); - } else if (allDay && tz != null && DateUtils.isToday(beginTimeAdjustedForAllDay)) { - // Medium priority = in progress all day events - mediumPriorityEvents.add(newInfo); - } else { - lowPriorityEvents.add(newInfo); - } - } - // TODO(psliwowski): move this to account synchronization - GlobalDismissManager.processEventIds(context, eventIds.keySet()); - } finally { - if (alertCursor != null) { - alertCursor.close(); - } - } - return numFired; - } - - /** - * High priority cutoff should be 1/4 event duration or 15 min, whichever is longer. - */ - private static long getGracePeriodMs(long beginTime, long endTime, boolean allDay) { - if (allDay) { - // We don't want all day events to be high priority for hours, so automatically - // demote these after 15 min. - return MIN_DEPRIORITIZE_GRACE_PERIOD_MS; - } else { - return Math.max(MIN_DEPRIORITIZE_GRACE_PERIOD_MS, ((endTime - beginTime) / 4)); - } - } - - private static String getDigestTitle(ArrayList<NotificationInfo> events) { - StringBuilder digestTitle = new StringBuilder(); - for (NotificationInfo eventInfo : events) { - if (!TextUtils.isEmpty(eventInfo.eventName)) { - if (digestTitle.length() > 0) { - digestTitle.append(", "); - } - digestTitle.append(eventInfo.eventName); - } - } - return digestTitle.toString(); - } - - private static void postNotification(NotificationInfo info, String summaryText, - Context context, boolean highPriority, NotificationPrefs prefs, - NotificationMgr notificationMgr, int notificationId) { - int priorityVal = Notification.PRIORITY_DEFAULT; - if (highPriority) { - priorityVal = Notification.PRIORITY_HIGH; - } - - String tickerText = getTickerText(info.eventName, info.location); - NotificationWrapper notification = AlertReceiver.makeExpandingNotification(context, - info.eventName, summaryText, info.description, info.startMillis, - info.endMillis, info.eventId, notificationId, prefs.getDoPopup(), priorityVal); - - boolean quietUpdate = true; - String ringtone = NotificationPrefs.EMPTY_RINGTONE; - if (info.newAlert) { - quietUpdate = prefs.quietUpdate; - - // If we've already played a ringtone, don't play any more sounds so only - // 1 sound per group of notifications. - ringtone = prefs.getRingtoneAndSilence(); - } - addNotificationOptions(notification, quietUpdate, tickerText, - prefs.getDefaultVibrate(), ringtone, - true); /* Show the LED for these non-expired events */ - - // Post the notification. - notificationMgr.notify(notificationId, notification); - - if (DEBUG) { - Log.d(TAG, "Posting individual alarm notification, eventId:" + info.eventId - + ", notificationId:" + notificationId - + (TextUtils.isEmpty(ringtone) ? ", quiet" : ", LOUD") - + (highPriority ? ", high-priority" : "")); - } - } - - private static String getTickerText(String eventName, String location) { - String tickerText = eventName; - if (!TextUtils.isEmpty(location)) { - tickerText = eventName + " - " + location; - } - return tickerText; - } - static class NotificationInfo { String eventName; String location; @@ -927,196 +181,12 @@ public class AlertService extends Service { } } - private static void addNotificationOptions(NotificationWrapper nw, boolean quietUpdate, - String tickerText, boolean defaultVibrate, String reminderRingtone, - boolean showLights) { - Notification notification = nw.mNotification; - if (showLights) { - notification.flags |= Notification.FLAG_SHOW_LIGHTS; - notification.defaults |= Notification.DEFAULT_LIGHTS; - } - - // Quietly update notification bar. Nothing new. Maybe something just got deleted. - if (!quietUpdate) { - // Flash ticker in status bar - if (!TextUtils.isEmpty(tickerText)) { - notification.tickerText = tickerText; - } - - // 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. - if (defaultVibrate) { - 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); - } - } - - /* package */ static class NotificationPrefs { - boolean quietUpdate; - private Context context; - private SharedPreferences prefs; - - // These are lazily initialized, do not access any of the following directly; use getters. - private int doPopup = -1; - private int defaultVibrate = -1; - private String ringtone = null; - - private static final String EMPTY_RINGTONE = ""; - - NotificationPrefs(Context context, SharedPreferences prefs, boolean quietUpdate) { - this.context = context; - this.prefs = prefs; - this.quietUpdate = quietUpdate; - } - - private boolean getDoPopup() { - if (doPopup < 0) { - if (prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false)) { - doPopup = 1; - } else { - doPopup = 0; - } - } - return doPopup == 1; - } - - private boolean getDefaultVibrate() { - if (defaultVibrate < 0) { - defaultVibrate = Utils.getDefaultVibrate(context, prefs) ? 1 : 0; - } - return defaultVibrate == 1; - } - - private String getRingtoneAndSilence() { - if (ringtone == null) { - if (quietUpdate) { - ringtone = EMPTY_RINGTONE; - } else { - ringtone = Utils.getRingTonePreference(context); - } - } - String retVal = ringtone; - ringtone = EMPTY_RINGTONE; - return retVal; - } - } - - private void doTimeChanged() { - ContentResolver cr = getContentResolver(); - // TODO Move this into Provider - rescheduleMissedAlarms(cr, this, AlertUtils.createAlarmManager(this)); - updateAlertNotification(this); - } - - private static final String SORT_ORDER_ALARMTIME_ASC = - CalendarContract.CalendarAlerts.ALARM_TIME + " ASC"; - - private static final String WHERE_RESCHEDULE_MISSED_ALARMS = - CalendarContract.CalendarAlerts.STATE - + "=" - + CalendarContract.CalendarAlerts.STATE_SCHEDULED - + " AND " - + CalendarContract.CalendarAlerts.ALARM_TIME - + "<?" - + " AND " - + CalendarContract.CalendarAlerts.ALARM_TIME - + ">?" - + " AND " - + CalendarContract.CalendarAlerts.END + ">=?"; - - /** - * Searches the CalendarAlerts table for alarms that should have fired but - * have not and then reschedules them. This method can be called at boot - * time to restore alarms that may have been lost due to a phone reboot. - * - * @param cr the ContentResolver - * @param context the Context - * @param manager the AlarmManager - */ - private static final void rescheduleMissedAlarms(ContentResolver cr, Context context, - AlarmManagerInterface manager) { - // Get all the alerts that have been scheduled but have not fired - // and should have fired by now and are not too old. - long now = System.currentTimeMillis(); - long ancient = now - DateUtils.DAY_IN_MILLIS; - String[] projection = new String[] { - CalendarContract.CalendarAlerts.ALARM_TIME, - }; - - // TODO: construct an explicit SQL query so that we can add - // "GROUPBY" instead of doing a sort and de-dup - Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection, - WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] { - Long.toString(now), Long.toString(ancient), Long.toString(now) - }), SORT_ORDER_ALARMTIME_ASC); - if (cursor == null) { - return; - } - - if (DEBUG) { - Log.d(TAG, "missed alarms found: " + cursor.getCount()); - } - - try { - long alarmTime = -1; - - while (cursor.moveToNext()) { - long newAlarmTime = cursor.getLong(0); - if (alarmTime != newAlarmTime) { - if (DEBUG) { - Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime); - } - AlertUtils.scheduleAlarm(context, manager, newAlarmTime); - alarmTime = newAlarmTime; - } - } - } finally { - cursor.close(); - } - } - - 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); - - // Flushes old fired alerts from internal storage, if needed. - AlertUtils.flushOldAlertsFromInternalStorage(getApplication()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null) { - Message msg = mServiceHandler.obtainMessage(); - msg.arg1 = startId; - msg.obj = intent.getExtras(); - mServiceHandler.sendMessage(msg); - } return START_REDELIVER_INTENT; } diff --git a/src/com/android/calendar/alerts/AlertUtils.java b/src/com/android/calendar/alerts/AlertUtils.java index fec7b111..b9aaec29 100644 --- a/src/com/android/calendar/alerts/AlertUtils.java +++ b/src/com/android/calendar/alerts/AlertUtils.java @@ -62,23 +62,6 @@ public class AlertUtils { // alerts from other apps. static boolean BYPASS_DB = true; - // SharedPrefs table name for storing fired alerts. This prevents other installed - // Calendar apps from eating the alerts. - private static final String ALERTS_SHARED_PREFS_NAME = "calendar_alerts"; - - // Keyname prefix for the alerts data in SharedPrefs. The key will contain a combo - // of event ID, begin time, and alarm time. The value will be the fired time. - private static final String KEY_FIRED_ALERT_PREFIX = "preference_alert_"; - - // The last time the SharedPrefs was scanned and flushed of old alerts data. - private static final String KEY_LAST_FLUSH_TIME_MS = "preference_flushTimeMs"; - - // The # of days to save alert states in the shared prefs table, before flushing. This - // can be any value, since AlertService will also check for a recent alertTime before - // ringing the alert. - private static final int FLUSH_INTERVAL_DAYS = 1; - private static final int FLUSH_INTERVAL_MS = FLUSH_INTERVAL_DAYS * 24 * 60 * 60 * 1000; - /** * Creates an AlarmManagerInterface that wraps a real AlarmManager. The alarm code * was abstracted to an interface to make it testable. @@ -110,109 +93,6 @@ public class AlertUtils { */ public static void scheduleAlarm(Context context, AlarmManagerInterface manager, long alarmTime) { - scheduleAlarmHelper(context, manager, alarmTime, false); - } - - /** - * Schedules the next alarm to silently refresh the notifications. Note that if there - * is a pending silent refresh alarm, it will be replaced with this one. - */ - static void scheduleNextNotificationRefresh(Context context, AlarmManagerInterface manager, - long alarmTime) { - scheduleAlarmHelper(context, manager, alarmTime, true); - } - - private static void scheduleAlarmHelper(Context context, AlarmManagerInterface manager, - long alarmTime, boolean quietUpdate) { - int alarmType = AlarmManager.RTC_WAKEUP; - Intent intent = new Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION); - intent.setClass(context, AlertReceiver.class); - if (quietUpdate) { - alarmType = AlarmManager.RTC; - } else { - // Set data field so we get a unique PendingIntent instance per alarm or else alarms - // may be dropped. - Uri.Builder builder = CalendarAlerts.CONTENT_URI.buildUpon(); - ContentUris.appendId(builder, alarmTime); - intent.setData(builder.build()); - } - - intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime); - PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - manager.set(alarmType, alarmTime, pi); - } - - /** - * Format the second line which shows time and location for single alert or the - * number of events for multiple alerts - * 1) Show time only for non-all day events - * 2) No date for today - * 3) Show "tomorrow" for tomorrow - * 4) Show date for days beyond that - */ - static String formatTimeLocation(Context context, long startMillis, boolean allDay, - String location) { - String tz = Utils.getTimeZone(context, null); - Time time = new Time(tz); - time.setToNow(); - int today = Time.getJulianDay(time.toMillis(false), time.gmtoff); - time.set(startMillis); - int eventDay = Time.getJulianDay(time.toMillis(false), allDay ? 0 : time.gmtoff); - - int flags = DateUtils.FORMAT_ABBREV_ALL; - if (!allDay) { - flags |= DateUtils.FORMAT_SHOW_TIME; - if (DateFormat.is24HourFormat(context)) { - flags |= DateUtils.FORMAT_24HOUR; - } - } else { - flags |= DateUtils.FORMAT_UTC; - } - - if (eventDay < today || eventDay > today + 1) { - flags |= DateUtils.FORMAT_SHOW_DATE; - } - - StringBuilder sb = new StringBuilder(Utils.formatDateRange(context, startMillis, - startMillis, flags)); - - if (!allDay && tz != Time.getCurrentTimezone()) { - // Assumes time was set to the current tz - time.set(startMillis); - boolean isDST = time.isDst != 0; - sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName( - isDST, TimeZone.SHORT, Locale.getDefault())); - } - - if (eventDay == today + 1) { - // Tomorrow - sb.append(", "); - sb.append(context.getString(R.string.tomorrow)); - } - - String loc; - if (location != null && !TextUtils.isEmpty(loc = location.trim())) { - sb.append(", "); - sb.append(loc); - } - return sb.toString(); - } - - public static ContentValues makeContentValues(long eventId, long begin, long end, - long alarmTime, int minutes) { - ContentValues values = new ContentValues(); - values.put(CalendarAlerts.EVENT_ID, eventId); - values.put(CalendarAlerts.BEGIN, begin); - values.put(CalendarAlerts.END, end); - values.put(CalendarAlerts.ALARM_TIME, alarmTime); - long currentTime = System.currentTimeMillis(); - values.put(CalendarAlerts.CREATION_TIME, currentTime); - values.put(CalendarAlerts.RECEIVED_TIME, 0); - values.put(CalendarAlerts.NOTIFY_TIME, 0); - values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_SCHEDULED); - values.put(CalendarAlerts.MINUTES, minutes); - return values; } public static Intent buildEventViewIntent(Context c, long eventId, long begin, long end) { @@ -225,103 +105,4 @@ public class AlertUtils { i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end); return i; } - - public static SharedPreferences getFiredAlertsTable(Context context) { - return context.getSharedPreferences(ALERTS_SHARED_PREFS_NAME, Context.MODE_PRIVATE); - } - - private static String getFiredAlertsKey(long eventId, long beginTime, - long alarmTime) { - StringBuilder sb = new StringBuilder(KEY_FIRED_ALERT_PREFIX); - sb.append(eventId); - sb.append("_"); - sb.append(beginTime); - sb.append("_"); - sb.append(alarmTime); - return sb.toString(); - } - - /** - * Returns whether the SharedPrefs storage indicates we have fired the alert before. - */ - static boolean hasAlertFiredInSharedPrefs(Context context, long eventId, long beginTime, - long alarmTime) { - SharedPreferences prefs = getFiredAlertsTable(context); - return prefs.contains(getFiredAlertsKey(eventId, beginTime, alarmTime)); - } - - /** - * Store fired alert info in the SharedPrefs. - */ - static void setAlertFiredInSharedPrefs(Context context, long eventId, long beginTime, - long alarmTime) { - // Store alarm time as the value too so we don't have to parse all the keys to flush - // old alarms out of the table later. - SharedPreferences prefs = getFiredAlertsTable(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(getFiredAlertsKey(eventId, beginTime, alarmTime), alarmTime); - editor.apply(); - } - - /** - * Scans and flushes the internal storage of old alerts. Looks up the previous flush - * time in SharedPrefs, and performs the flush if overdue. Otherwise, no-op. - */ - static void flushOldAlertsFromInternalStorage(Context context) { - if (BYPASS_DB) { - SharedPreferences prefs = getFiredAlertsTable(context); - - // Only flush if it hasn't been done in a while. - long nowTime = System.currentTimeMillis(); - long lastFlushTimeMs = prefs.getLong(KEY_LAST_FLUSH_TIME_MS, 0); - if (nowTime - lastFlushTimeMs > FLUSH_INTERVAL_MS) { - if (DEBUG) { - Log.d(TAG, "Flushing old alerts from shared prefs table"); - } - - // Scan through all fired alert entries, removing old ones. - SharedPreferences.Editor editor = prefs.edit(); - Time timeObj = new Time(); - for (Map.Entry<String, ?> entry : prefs.getAll().entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - if (key.startsWith(KEY_FIRED_ALERT_PREFIX)) { - long alertTime; - if (value instanceof Long) { - alertTime = (Long) value; - } else { - // Should never occur. - Log.e(TAG,"SharedPrefs key " + key + " did not have Long value: " + - value); - continue; - } - - if (nowTime - alertTime >= FLUSH_INTERVAL_MS) { - editor.remove(key); - if (DEBUG) { - int ageInDays = getIntervalInDays(alertTime, nowTime, timeObj); - Log.d(TAG, "SharedPrefs key " + key + ": removed (" + ageInDays + - " days old)"); - } - } else { - if (DEBUG) { - int ageInDays = getIntervalInDays(alertTime, nowTime, timeObj); - Log.d(TAG, "SharedPrefs key " + key + ": keep (" + ageInDays + - " days old)"); - } - } - } - } - editor.putLong(KEY_LAST_FLUSH_TIME_MS, nowTime); - editor.apply(); - } - } - } - - private static int getIntervalInDays(long startMillis, long endMillis, Time timeObj) { - timeObj.set(startMillis); - int startDay = Time.getJulianDay(startMillis, timeObj.gmtoff); - timeObj.set(endMillis); - return Time.getJulianDay(endMillis, timeObj.gmtoff) - startDay; - } } diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.java b/src/com/android/calendar/alerts/GlobalDismissManager.java index ce3402ca..27b3e162 100644 --- a/src/com/android/calendar/alerts/GlobalDismissManager.java +++ b/src/com/android/calendar/alerts/GlobalDismissManager.java @@ -32,8 +32,6 @@ import android.provider.CalendarContract.Events; import android.util.Log; import android.util.Pair; -import com.android.calendar.CloudNotificationBackplane; -import com.android.calendar.ExtensionsFactory; import com.android.calendar.R; import java.io.IOException; @@ -49,113 +47,6 @@ import java.util.Set; * Utilities for managing notification dismissal across devices. */ public class GlobalDismissManager extends BroadcastReceiver { - private static class GlobalDismissId { - public final String mAccountName; - public final String mSyncId; - public final long mStartTime; - - private GlobalDismissId(String accountName, String syncId, long startTime) { - // TODO(psliwowski): Add guava library to use Preconditions class - if (accountName == null) { - throw new IllegalArgumentException("Account Name can not be set to null"); - } else if (syncId == null) { - throw new IllegalArgumentException("SyncId can not be set to null"); - } - mAccountName = accountName; - mSyncId = syncId; - mStartTime = startTime; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - GlobalDismissId that = (GlobalDismissId) o; - - if (mStartTime != that.mStartTime) { - return false; - } - if (!mAccountName.equals(that.mAccountName)) { - return false; - } - if (!mSyncId.equals(that.mSyncId)) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = mAccountName.hashCode(); - result = 31 * result + mSyncId.hashCode(); - result = 31 * result + (int) (mStartTime ^ (mStartTime >>> 32)); - return result; - } - } - - public static class LocalDismissId { - public final String mAccountType; - public final String mAccountName; - public final long mEventId; - public final long mStartTime; - - public LocalDismissId(String accountType, String accountName, long eventId, - long startTime) { - if (accountType == null) { - throw new IllegalArgumentException("Account Type can not be null"); - } else if (accountName == null) { - throw new IllegalArgumentException("Account Name can not be null"); - } - - mAccountType = accountType; - mAccountName = accountName; - mEventId = eventId; - mStartTime = startTime; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - LocalDismissId that = (LocalDismissId) o; - - if (mEventId != that.mEventId) { - return false; - } - if (mStartTime != that.mStartTime) { - return false; - } - if (!mAccountName.equals(that.mAccountName)) { - return false; - } - if (!mAccountType.equals(that.mAccountType)) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = mAccountType.hashCode(); - result = 31 * result + mAccountName.hashCode(); - result = 31 * result + (int) (mEventId ^ (mEventId >>> 32)); - result = 31 * result + (int) (mStartTime ^ (mStartTime >>> 32)); - return result; - } - } - public static class AlarmId { public long mEventId; public long mStart; @@ -166,175 +57,6 @@ public class GlobalDismissManager extends BroadcastReceiver { } } - private static final long TIME_TO_LIVE = 1 * 60 * 60 * 1000; // 1 hour - - private static final String TAG = "GlobalDismissManager"; - private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; - private static final String GLOBAL_DISMISS_MANAGER_PREFS = "com.android.calendar.alerts.GDM"; - private static final String ACCOUNT_KEY = "known_accounts"; - - static final String[] EVENT_PROJECTION = new String[] { - Events._ID, - Events.CALENDAR_ID - }; - static final String[] EVENT_SYNC_PROJECTION = new String[] { - Events._ID, - Events._SYNC_ID - }; - static final String[] CALENDARS_PROJECTION = new String[] { - Calendars._ID, - Calendars.ACCOUNT_NAME, - Calendars.ACCOUNT_TYPE - }; - - public static final String KEY_PREFIX = "com.android.calendar.alerts."; - public static final String SYNC_ID = KEY_PREFIX + "sync_id"; - public static final String START_TIME = KEY_PREFIX + "start_time"; - public static final String ACCOUNT_NAME = KEY_PREFIX + "account_name"; - public static final String DISMISS_INTENT = KEY_PREFIX + "DISMISS"; - - // TODO(psliwowski): Look into persisting these like AlertUtils.ALERTS_SHARED_PREFS_NAME - private static HashMap<GlobalDismissId, Long> sReceiverDismissCache = - new HashMap<GlobalDismissId, Long>(); - private static HashMap<LocalDismissId, Long> sSenderDismissCache = - new HashMap<LocalDismissId, Long>(); - - /** - * Look for unknown accounts in a set of events and associate with them. - * Must not be called on main thread. - * - * @param context application context - * @param eventIds IDs for events that have posted notifications that may be - * dismissed. - */ - public static void processEventIds(Context context, Set<Long> eventIds) { - final String senderId = context.getResources().getString(R.string.notification_sender_id); - if (senderId == null || senderId.isEmpty()) { - Log.i(TAG, "no sender configured"); - return; - } - Map<Long, Long> eventsToCalendars = lookupEventToCalendarMap(context, eventIds); - Set<Long> calendars = new LinkedHashSet<Long>(); - calendars.addAll(eventsToCalendars.values()); - if (calendars.isEmpty()) { - Log.d(TAG, "found no calendars for events"); - return; - } - - Map<Long, Pair<String, String>> calendarsToAccounts = - lookupCalendarToAccountMap(context, calendars); - - if (calendarsToAccounts.isEmpty()) { - Log.d(TAG, "found no accounts for calendars"); - return; - } - - // filter out non-google accounts (necessary?) - Set<String> accounts = new LinkedHashSet<String>(); - for (Pair<String, String> accountPair : calendarsToAccounts.values()) { - if (GOOGLE_ACCOUNT_TYPE.equals(accountPair.first)) { - accounts.add(accountPair.second); - } - } - - // filter out accounts we already know about - SharedPreferences prefs = - context.getSharedPreferences(GLOBAL_DISMISS_MANAGER_PREFS, - Context.MODE_PRIVATE); - Set<String> existingAccounts = prefs.getStringSet(ACCOUNT_KEY, - new HashSet<String>()); - accounts.removeAll(existingAccounts); - - if (accounts.isEmpty()) { - // nothing to do, we've already registered all the accounts. - return; - } - - // subscribe to remaining accounts - CloudNotificationBackplane cnb = - ExtensionsFactory.getCloudNotificationBackplane(); - if (cnb.open(context)) { - for (String account : accounts) { - try { - if (cnb.subscribeToGroup(senderId, account, account)) { - existingAccounts.add(account); - } - } catch (IOException e) { - // Try again, next time the account triggers and alert. - } - } - cnb.close(); - prefs.edit() - .putStringSet(ACCOUNT_KEY, existingAccounts) - .commit(); - } - } - - /** - * Some events don't have a global sync_id when they are dismissed. We need to wait - * until the data provider is updated before we can send the global dismiss message. - */ - public static void syncSenderDismissCache(Context context) { - final String senderId = context.getResources().getString(R.string.notification_sender_id); - if ("".equals(senderId)) { - Log.i(TAG, "no sender configured"); - return; - } - CloudNotificationBackplane cnb = ExtensionsFactory.getCloudNotificationBackplane(); - if (!cnb.open(context)) { - Log.i(TAG, "Unable to open cloud notification backplane"); - - } - - long currentTime = System.currentTimeMillis(); - ContentResolver resolver = context.getContentResolver(); - synchronized (sSenderDismissCache) { - Iterator<Map.Entry<LocalDismissId, Long>> it = - sSenderDismissCache.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<LocalDismissId, Long> entry = it.next(); - LocalDismissId dismissId = entry.getKey(); - - Uri uri = asSync(Events.CONTENT_URI, dismissId.mAccountType, - dismissId.mAccountName); - Cursor cursor = resolver.query(uri, EVENT_SYNC_PROJECTION, - Events._ID + " = " + dismissId.mEventId, null, null); - try { - cursor.moveToPosition(-1); - int sync_id_idx = cursor.getColumnIndex(Events._SYNC_ID); - if (sync_id_idx != -1) { - while (cursor.moveToNext()) { - String syncId = cursor.getString(sync_id_idx); - if (syncId != null) { - Bundle data = new Bundle(); - long startTime = dismissId.mStartTime; - String accountName = dismissId.mAccountName; - data.putString(SYNC_ID, syncId); - data.putString(START_TIME, Long.toString(startTime)); - data.putString(ACCOUNT_NAME, accountName); - try { - cnb.send(accountName, syncId + ":" + startTime, data); - it.remove(); - } catch (IOException e) { - // If we couldn't send, then leave dismissal in cache - } - } - } - } - } finally { - cursor.close(); - } - - // Remove old dismissals from cache after a certain time period - if (currentTime - entry.getValue() > TIME_TO_LIVE) { - it.remove(); - } - } - } - - cnb.close(); - } - /** * Globally dismiss notifications that are backed by the same events. * @@ -347,178 +69,6 @@ public class GlobalDismissManager extends BroadcastReceiver { for (AlarmId alarmId: alarmIds) { eventIds.add(alarmId.mEventId); } - // find the mapping between calendars and events - Map<Long, Long> eventsToCalendars = lookupEventToCalendarMap(context, eventIds); - if (eventsToCalendars.isEmpty()) { - Log.d(TAG, "found no calendars for events"); - return; - } - - Set<Long> calendars = new LinkedHashSet<Long>(); - calendars.addAll(eventsToCalendars.values()); - - // find the accounts associated with those calendars - Map<Long, Pair<String, String>> calendarsToAccounts = - lookupCalendarToAccountMap(context, calendars); - if (calendarsToAccounts.isEmpty()) { - Log.d(TAG, "found no accounts for calendars"); - return; - } - - long currentTime = System.currentTimeMillis(); - for (AlarmId alarmId : alarmIds) { - Long calendar = eventsToCalendars.get(alarmId.mEventId); - Pair<String, String> account = calendarsToAccounts.get(calendar); - if (GOOGLE_ACCOUNT_TYPE.equals(account.first)) { - LocalDismissId dismissId = new LocalDismissId(account.first, account.second, - alarmId.mEventId, alarmId.mStart); - synchronized (sSenderDismissCache) { - sSenderDismissCache.put(dismissId, currentTime); - } - } - } - syncSenderDismissCache(context); - } - - private static Uri asSync(Uri uri, String accountType, String account) { - return uri - .buildUpon() - .appendQueryParameter( - android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true") - .appendQueryParameter(Calendars.ACCOUNT_NAME, account) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); - } - - /** - * Build a selection over a set of row IDs - * - * @param ids row IDs to select - * @param key row name for the table - * @return a selection string suitable for a resolver query. - */ - private static String buildMultipleIdQuery(Set<Long> ids, String key) { - StringBuilder selection = new StringBuilder(); - boolean first = true; - for (Long id : ids) { - if (first) { - first = false; - } else { - selection.append(" OR "); - } - selection.append(key); - selection.append("="); - selection.append(id); - } - return selection.toString(); - } - - /** - * @param context application context - * @param eventIds Event row IDs to query. - * @return a map from event to calendar - */ - private static Map<Long, Long> lookupEventToCalendarMap(Context context, Set<Long> eventIds) { - Map<Long, Long> eventsToCalendars = new HashMap<Long, Long>(); - ContentResolver resolver = context.getContentResolver(); - String eventSelection = buildMultipleIdQuery(eventIds, Events._ID); - Cursor eventCursor = resolver.query(Events.CONTENT_URI, EVENT_PROJECTION, - eventSelection, null, null); - try { - eventCursor.moveToPosition(-1); - int calendar_id_idx = eventCursor.getColumnIndex(Events.CALENDAR_ID); - int event_id_idx = eventCursor.getColumnIndex(Events._ID); - if (calendar_id_idx != -1 && event_id_idx != -1) { - while (eventCursor.moveToNext()) { - eventsToCalendars.put(eventCursor.getLong(event_id_idx), - eventCursor.getLong(calendar_id_idx)); - } - } - } finally { - eventCursor.close(); - } - return eventsToCalendars; - } - - /** - * @param context application context - * @param calendars Calendar row IDs to query. - * @return a map from Calendar to a pair (account type, account name) - */ - private static Map<Long, Pair<String, String>> lookupCalendarToAccountMap(Context context, - Set<Long> calendars) { - Map<Long, Pair<String, String>> calendarsToAccounts = - new HashMap<Long, Pair<String, String>>(); - ContentResolver resolver = context.getContentResolver(); - String calendarSelection = buildMultipleIdQuery(calendars, Calendars._ID); - Cursor calendarCursor = resolver.query(Calendars.CONTENT_URI, CALENDARS_PROJECTION, - calendarSelection, null, null); - try { - calendarCursor.moveToPosition(-1); - int calendar_id_idx = calendarCursor.getColumnIndex(Calendars._ID); - int account_name_idx = calendarCursor.getColumnIndex(Calendars.ACCOUNT_NAME); - int account_type_idx = calendarCursor.getColumnIndex(Calendars.ACCOUNT_TYPE); - if (calendar_id_idx != -1 && account_name_idx != -1 && account_type_idx != -1) { - while (calendarCursor.moveToNext()) { - Long id = calendarCursor.getLong(calendar_id_idx); - String name = calendarCursor.getString(account_name_idx); - String type = calendarCursor.getString(account_type_idx); - if (name != null && type != null) { - calendarsToAccounts.put(id, new Pair<String, String>(type, name)); - } - } - } - } finally { - calendarCursor.close(); - } - return calendarsToAccounts; - } - - /** - * We can get global dismisses for events we don't know exists yet, so sync our cache - * with the data provider whenever it updates. - */ - public static void syncReceiverDismissCache(Context context) { - ContentResolver resolver = context.getContentResolver(); - long currentTime = System.currentTimeMillis(); - synchronized (sReceiverDismissCache) { - Iterator<Map.Entry<GlobalDismissId, Long>> it = - sReceiverDismissCache.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<GlobalDismissId, Long> entry = it.next(); - GlobalDismissId globalDismissId = entry.getKey(); - Uri uri = GlobalDismissManager.asSync(Events.CONTENT_URI, - GlobalDismissManager.GOOGLE_ACCOUNT_TYPE, globalDismissId.mAccountName); - Cursor cursor = resolver.query(uri, GlobalDismissManager.EVENT_SYNC_PROJECTION, - Events._SYNC_ID + " = '" + globalDismissId.mSyncId + "'", - null, null); - try { - int event_id_idx = cursor.getColumnIndex(Events._ID); - cursor.moveToFirst(); - if (event_id_idx != -1 && !cursor.isAfterLast()) { - long eventId = cursor.getLong(event_id_idx); - ContentValues values = new ContentValues(); - String selection = "(" + CalendarAlerts.STATE + "=" + - CalendarAlerts.STATE_FIRED + " OR " + - CalendarAlerts.STATE + "=" + - CalendarAlerts.STATE_SCHEDULED + ") AND " + - CalendarAlerts.EVENT_ID + "=" + eventId + " AND " + - CalendarAlerts.BEGIN + "=" + globalDismissId.mStartTime; - values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED); - int rows = resolver.update(CalendarAlerts.CONTENT_URI, values, - selection, null); - if (rows > 0) { - it.remove(); - } - } - } finally { - cursor.close(); - } - - if (currentTime - entry.getValue() > TIME_TO_LIVE) { - it.remove(); - } - } - } } @Override @@ -527,19 +77,6 @@ public class GlobalDismissManager extends BroadcastReceiver { new AsyncTask<Pair<Context, Intent>, Void, Void>() { @Override protected Void doInBackground(Pair<Context, Intent>... params) { - Context context = params[0].first; - Intent intent = params[0].second; - if (intent.hasExtra(SYNC_ID) && intent.hasExtra(ACCOUNT_NAME) - && intent.hasExtra(START_TIME)) { - synchronized (sReceiverDismissCache) { - sReceiverDismissCache.put(new GlobalDismissId( - intent.getStringExtra(ACCOUNT_NAME), - intent.getStringExtra(SYNC_ID), - Long.parseLong(intent.getStringExtra(START_TIME)) - ), System.currentTimeMillis()); - } - AlertService.updateAlertNotification(context); - } return null; } }.execute(new Pair<Context, Intent>(context, intent)); diff --git a/src/com/android/calendar/alerts/QuickResponseActivity.java b/src/com/android/calendar/alerts/QuickResponseActivity.java index f7e08883..3d291d02 100644 --- a/src/com/android/calendar/alerts/QuickResponseActivity.java +++ b/src/com/android/calendar/alerts/QuickResponseActivity.java @@ -103,23 +103,6 @@ public class QuickResponseActivity extends ListActivity implements OnItemClickLi @Override public void run() { - Intent emailIntent = AlertReceiver.createEmailIntent(QuickResponseActivity.this, - mEventId, mBody); - if (emailIntent != null) { - try { - startActivity(emailIntent); - finish(); - } catch (ActivityNotFoundException ex) { - QuickResponseActivity.this.getListView().post(new Runnable() { - @Override - public void run() { - Toast.makeText(QuickResponseActivity.this, - R.string.quick_response_email_failed, Toast.LENGTH_LONG); - finish(); - } - }); - } - } } } } diff --git a/src/com/android/calendar/alerts/SnoozeAlarmsService.java b/src/com/android/calendar/alerts/SnoozeAlarmsService.java deleted file mode 100644 index 6ef983d5..00000000 --- a/src/com/android/calendar/alerts/SnoozeAlarmsService.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.alerts; - -import android.app.IntentService; -import android.app.NotificationManager; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.IBinder; -import android.provider.CalendarContract.CalendarAlerts; - -/** - * Service for asynchronously marking a fired alarm as dismissed and scheduling - * a new alarm in the future. - */ -public class SnoozeAlarmsService extends IntentService { - private static final String[] PROJECTION = new String[] { - CalendarAlerts.STATE, - }; - private static final int COLUMN_INDEX_STATE = 0; - - public SnoozeAlarmsService() { - super("SnoozeAlarmsService"); - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onHandleIntent(Intent intent) { - - long eventId = intent.getLongExtra(AlertUtils.EVENT_ID_KEY, -1); - long eventStart = intent.getLongExtra(AlertUtils.EVENT_START_KEY, -1); - long eventEnd = intent.getLongExtra(AlertUtils.EVENT_END_KEY, -1); - - // The ID reserved for the expired notification digest should never be passed in - // here, so use that as a default. - int notificationId = intent.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, - AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID); - - if (eventId != -1) { - ContentResolver resolver = getContentResolver(); - - // Remove notification - if (notificationId != AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID) { - NotificationManager nm = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(notificationId); - } - - // Dismiss current alarm - Uri uri = CalendarAlerts.CONTENT_URI; - String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED + " AND " + - CalendarAlerts.EVENT_ID + "=" + eventId; - ContentValues dismissValues = new ContentValues(); - dismissValues.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED); - resolver.update(uri, dismissValues, selection, null); - - // Add a new alarm - long alarmTime = System.currentTimeMillis() + AlertUtils.SNOOZE_DELAY; - ContentValues values = AlertUtils.makeContentValues(eventId, eventStart, eventEnd, - alarmTime, 0); - resolver.insert(uri, values); - AlertUtils.scheduleAlarm(SnoozeAlarmsService.this, AlertUtils.createAlarmManager(this), - alarmTime); - } - AlertService.updateAlertNotification(this); - stopSelf(); - } -} diff --git a/src/com/android/calendar/event/AttendeesView.java b/src/com/android/calendar/event/AttendeesView.java deleted file mode 100644 index a54d40b6..00000000 --- a/src/com/android/calendar/event/AttendeesView.java +++ /dev/null @@ -1,486 +0,0 @@ - /* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import com.android.calendar.CalendarEventModel.Attendee; -import com.android.calendar.ContactsAsyncHelper; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.event.EditEventHelper.AttendeeItem; -import com.android.common.Rfc822Validator; - -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.CalendarContract.Attendees; -import android.provider.ContactsContract.CommonDataKinds.Email; -import android.provider.ContactsContract.CommonDataKinds.Identity; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.RawContacts; -import android.text.TextUtils; -import android.text.util.Rfc822Token; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.QuickContactBadge; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; - -public class AttendeesView extends LinearLayout implements View.OnClickListener { - private static final String TAG = "AttendeesView"; - private static final boolean DEBUG = false; - - private static final int EMAIL_PROJECTION_CONTACT_ID_INDEX = 0; - private static final int EMAIL_PROJECTION_CONTACT_LOOKUP_INDEX = 1; - private static final int EMAIL_PROJECTION_PHOTO_ID_INDEX = 2; - - private static final String[] PROJECTION = new String[] { - RawContacts.CONTACT_ID, // 0 - Contacts.LOOKUP_KEY, // 1 - Contacts.PHOTO_ID, // 2 - }; - - private final Context mContext; - private final LayoutInflater mInflater; - private final PresenceQueryHandler mPresenceQueryHandler; - private final Drawable mDefaultBadge; - private final ColorMatrixColorFilter mGrayscaleFilter; - - // TextView shown at the top of each type of attendees - // e.g. - // Yes <-- divider - // example_for_yes <exampleyes@example.com> - // No <-- divider - // example_for_no <exampleno@example.com> - private final CharSequence[] mEntries; - private final View mDividerForYes; - private final View mDividerForNo; - private final View mDividerForMaybe; - private final View mDividerForNoResponse; - private final int mNoResponsePhotoAlpha; - private final int mDefaultPhotoAlpha; - private Rfc822Validator mValidator; - - // Number of attendees responding or not responding. - private int mYes; - private int mNo; - private int mMaybe; - private int mNoResponse; - - // Cache for loaded photos - HashMap<String, Drawable> mRecycledPhotos; - - public AttendeesView(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mPresenceQueryHandler = new PresenceQueryHandler(context.getContentResolver()); - - final Resources resources = context.getResources(); - mDefaultBadge = resources.getDrawable(R.drawable.ic_contact_picture); - mNoResponsePhotoAlpha = - resources.getInteger(R.integer.noresponse_attendee_photo_alpha_level); - mDefaultPhotoAlpha = resources.getInteger(R.integer.default_attendee_photo_alpha_level); - - // Create dividers between groups of attendees (accepted, declined, etc...) - mEntries = resources.getTextArray(R.array.response_labels1); - mDividerForYes = constructDividerView(mEntries[1]); - mDividerForNo = constructDividerView(mEntries[3]); - mDividerForMaybe = constructDividerView(mEntries[2]); - mDividerForNoResponse = constructDividerView(mEntries[0]); - - // Create a filter to convert photos of declined attendees to grayscale. - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0); - mGrayscaleFilter = new ColorMatrixColorFilter(matrix); - - } - - // Disable/enable removal of attendings - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - int visibility = isEnabled() ? View.VISIBLE : View.GONE; - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - View minusButton = child.findViewById(R.id.contact_remove); - if (minusButton != null) { - minusButton.setVisibility(visibility); - } - } - } - - public void setRfc822Validator(Rfc822Validator validator) { - mValidator = validator; - } - - private View constructDividerView(CharSequence label) { - final TextView textView = - (TextView)mInflater.inflate(R.layout.event_info_label, this, false); - textView.setText(label); - textView.setClickable(false); - return textView; - } - - // Add the number of attendees in the specific status (corresponding to the divider) in - // parenthesis next to the label - private void updateDividerViewLabel(View divider, CharSequence label, int count) { - if (count <= 0) { - ((TextView)divider).setText(label); - } - else { - ((TextView)divider).setText(label + " (" + count + ")"); - } - } - - - /** - * Inflates a layout for a given attendee view and set up each element in it, and returns - * the constructed View object. The object is also stored in {@link AttendeeItem#mView}. - */ - private View constructAttendeeView(AttendeeItem item) { - item.mView = mInflater.inflate(R.layout.contact_item, null); - return updateAttendeeView(item); - } - - /** - * Set up each element in {@link AttendeeItem#mView} using the latest information. View - * object is reused. - */ - private View updateAttendeeView(AttendeeItem item) { - final Attendee attendee = item.mAttendee; - final View view = item.mView; - final TextView nameView = (TextView) view.findViewById(R.id.name); - nameView.setText(TextUtils.isEmpty(attendee.mName) ? attendee.mEmail : attendee.mName); - if (item.mRemoved) { - nameView.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG | nameView.getPaintFlags()); - } else { - nameView.setPaintFlags((~Paint.STRIKE_THRU_TEXT_FLAG) & nameView.getPaintFlags()); - } - - // Set up the Image button even if the view is disabled - // Everything will be ready when the view is enabled later - final ImageButton button = (ImageButton) view.findViewById(R.id.contact_remove); - button.setVisibility(isEnabled() ? View.VISIBLE : View.GONE); - button.setTag(item); - if (item.mRemoved) { - button.setImageResource(R.drawable.ic_menu_add_field_holo_light); - button.setContentDescription(mContext.getString(R.string.accessibility_add_attendee)); - } else { - button.setImageResource(R.drawable.ic_menu_remove_field_holo_light); - button.setContentDescription(mContext. - getString(R.string.accessibility_remove_attendee)); - } - button.setOnClickListener(this); - - final QuickContactBadge badgeView = (QuickContactBadge) view.findViewById(R.id.badge); - - Drawable badge = null; - // Search for photo in recycled photos - if (mRecycledPhotos != null) { - badge = mRecycledPhotos.get(item.mAttendee.mEmail); - } - if (badge != null) { - item.mBadge = badge; - } - badgeView.setImageDrawable(item.mBadge); - - if (item.mAttendee.mStatus == Attendees.ATTENDEE_STATUS_NONE) { - item.mBadge.setAlpha(mNoResponsePhotoAlpha); - } else { - item.mBadge.setAlpha(mDefaultPhotoAlpha); - } - if (item.mAttendee.mStatus == Attendees.ATTENDEE_STATUS_DECLINED) { - item.mBadge.setColorFilter(mGrayscaleFilter); - } else { - item.mBadge.setColorFilter(null); - } - - // If we know the lookup-uri of the contact, it is a good idea to set this here. This - // allows QuickContact to be started without an extra database lookup. If we don't know - // the lookup uri (yet), we can set Email and QuickContact will lookup once tapped. - if (item.mContactLookupUri != null) { - badgeView.assignContactUri(item.mContactLookupUri); - } else { - badgeView.assignContactFromEmail(item.mAttendee.mEmail, true); - } - badgeView.setMaxHeight(60); - - return view; - } - - public boolean contains(Attendee attendee) { - final int size = getChildCount(); - for (int i = 0; i < size; i++) { - final View view = getChildAt(i); - if (view instanceof TextView) { // divider - continue; - } - AttendeeItem attendeeItem = (AttendeeItem) view.getTag(); - if (TextUtils.equals(attendee.mEmail, attendeeItem.mAttendee.mEmail)) { - return true; - } - } - return false; - } - - public void clearAttendees() { - - // Before clearing the views, save all the badges. The updateAtendeeView will use the saved - // photo instead of the default badge thus prevent switching between the two while the - // most current photo is loaded in the background. - mRecycledPhotos = new HashMap<String, Drawable> (); - final int size = getChildCount(); - for (int i = 0; i < size; i++) { - final View view = getChildAt(i); - if (view instanceof TextView) { // divider - continue; - } - AttendeeItem attendeeItem = (AttendeeItem) view.getTag(); - mRecycledPhotos.put(attendeeItem.mAttendee.mEmail, attendeeItem.mBadge); - } - - removeAllViews(); - mYes = 0; - mNo = 0; - mMaybe = 0; - mNoResponse = 0; - } - - private void addOneAttendee(Attendee attendee) { - if (contains(attendee)) { - return; - } - final AttendeeItem item = new AttendeeItem(attendee, mDefaultBadge); - final int status = attendee.mStatus; - final int index; - boolean firstAttendeeInCategory = false; - switch (status) { - case Attendees.ATTENDEE_STATUS_ACCEPTED: { - final int startIndex = 0; - updateDividerViewLabel(mDividerForYes, mEntries[1], mYes + 1); - if (mYes == 0) { - addView(mDividerForYes, startIndex); - firstAttendeeInCategory = true; - } - mYes++; - index = startIndex + mYes; - break; - } - case Attendees.ATTENDEE_STATUS_DECLINED: { - final int startIndex = (mYes == 0 ? 0 : 1 + mYes); - updateDividerViewLabel(mDividerForNo, mEntries[3], mNo + 1); - if (mNo == 0) { - addView(mDividerForNo, startIndex); - firstAttendeeInCategory = true; - } - mNo++; - index = startIndex + mNo; - break; - } - case Attendees.ATTENDEE_STATUS_TENTATIVE: { - final int startIndex = (mYes == 0 ? 0 : 1 + mYes) + (mNo == 0 ? 0 : 1 + mNo); - updateDividerViewLabel(mDividerForMaybe, mEntries[2], mMaybe + 1); - if (mMaybe == 0) { - addView(mDividerForMaybe, startIndex); - firstAttendeeInCategory = true; - } - mMaybe++; - index = startIndex + mMaybe; - break; - } - default: { - final int startIndex = (mYes == 0 ? 0 : 1 + mYes) + (mNo == 0 ? 0 : 1 + mNo) - + (mMaybe == 0 ? 0 : 1 + mMaybe); - updateDividerViewLabel(mDividerForNoResponse, mEntries[0], mNoResponse + 1); - if (mNoResponse == 0) { - addView(mDividerForNoResponse, startIndex); - firstAttendeeInCategory = true; - } - mNoResponse++; - index = startIndex + mNoResponse; - break; - } - } - - final View view = constructAttendeeView(item); - view.setTag(item); - addView(view, index); - // Show separator between Attendees - if (!firstAttendeeInCategory) { - View prevItem = getChildAt(index - 1); - if (prevItem != null) { - View Separator = prevItem.findViewById(R.id.contact_separator); - if (Separator != null) { - Separator.setVisibility(View.VISIBLE); - } - } - } - - Uri uri; - String selection = null; - String[] selectionArgs = null; - if (attendee.mIdentity != null && attendee.mIdNamespace != null) { - // Query by identity + namespace - uri = Data.CONTENT_URI; - selection = Data.MIMETYPE + "=? AND " + Identity.IDENTITY + "=? AND " + - Identity.NAMESPACE + "=?"; - selectionArgs = new String[] {Identity.CONTENT_ITEM_TYPE, attendee.mIdentity, - attendee.mIdNamespace}; - } else { - // Query by email - uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(attendee.mEmail)); - } - - mPresenceQueryHandler.startQuery(item.mUpdateCounts + 1, item, uri, PROJECTION, selection, - selectionArgs, null); - } - - public void addAttendees(ArrayList<Attendee> attendees) { - synchronized (this) { - for (final Attendee attendee : attendees) { - addOneAttendee(attendee); - } - } - } - - public void addAttendees(HashMap<String, Attendee> attendees) { - synchronized (this) { - for (final Attendee attendee : attendees.values()) { - addOneAttendee(attendee); - } - } - } - - public void addAttendees(String attendees) { - final LinkedHashSet<Rfc822Token> addresses = - EditEventHelper.getAddressesFromList(attendees, mValidator); - synchronized (this) { - for (final Rfc822Token address : addresses) { - final Attendee attendee = new Attendee(address.getName(), address.getAddress()); - if (TextUtils.isEmpty(attendee.mName)) { - attendee.mName = attendee.mEmail; - } - addOneAttendee(attendee); - } - } - } - - /** - * Returns true when the attendee at that index is marked as "removed" (the name of - * the attendee is shown with a strike through line). - */ - public boolean isMarkAsRemoved(int index) { - final View view = getChildAt(index); - if (view instanceof TextView) { // divider - return false; - } - return ((AttendeeItem) view.getTag()).mRemoved; - } - - // TODO put this into a Loader for auto-requeries - private class PresenceQueryHandler extends AsyncQueryHandler { - public PresenceQueryHandler(ContentResolver cr) { - super(cr); - } - - @Override - protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) { - if (cursor == null || cookie == null) { - if (DEBUG) { - Log.d(TAG, "onQueryComplete: cursor=" + cursor + ", cookie=" + cookie); - } - return; - } - - final AttendeeItem item = (AttendeeItem)cookie; - try { - if (item.mUpdateCounts < queryIndex) { - item.mUpdateCounts = queryIndex; - if (cursor.moveToFirst()) { - final long contactId = cursor.getLong(EMAIL_PROJECTION_CONTACT_ID_INDEX); - final Uri contactUri = - ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); - - final String lookupKey = - cursor.getString(EMAIL_PROJECTION_CONTACT_LOOKUP_INDEX); - item.mContactLookupUri = Contacts.getLookupUri(contactId, lookupKey); - - final long photoId = cursor.getLong(EMAIL_PROJECTION_PHOTO_ID_INDEX); - // If we found a picture, start the async loading - if (photoId > 0) { - // Query for this contacts picture - ContactsAsyncHelper.retrieveContactPhotoAsync( - mContext, item, new Runnable() { - @Override - public void run() { - updateAttendeeView(item); - } - }, contactUri); - } else { - // call update view to make sure that the lookup key gets set in - // the QuickContactBadge - updateAttendeeView(item); - } - } else { - // Contact not found. For real emails, keep the QuickContactBadge with - // its Email address set, so that the user can create a contact by tapping. - item.mContactLookupUri = null; - if (!Utils.isValidEmail(item.mAttendee.mEmail)) { - item.mAttendee.mEmail = null; - updateAttendeeView(item); - } - } - } - } finally { - cursor.close(); - } - } - } - - public Attendee getItem(int index) { - final View view = getChildAt(index); - if (view instanceof TextView) { // divider - return null; - } - return ((AttendeeItem) view.getTag()).mAttendee; - } - - @Override - public void onClick(View view) { - // Button corresponding to R.id.contact_remove. - final AttendeeItem item = (AttendeeItem) view.getTag(); - item.mRemoved = !item.mRemoved; - updateAttendeeView(item); - } -} diff --git a/src/com/android/calendar/event/CreateEventDialogFragment.java b/src/com/android/calendar/event/CreateEventDialogFragment.java deleted file mode 100644 index 0381b302..00000000 --- a/src/com/android/calendar/event/CreateEventDialogFragment.java +++ /dev/null @@ -1,325 +0,0 @@ -/* Copyright (C) 2012 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.android.calendar.event; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Calendars; -import android.provider.Settings; -import android.text.Editable; -import android.text.TextWatcher; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.calendar.AsyncQueryService; -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarEventModel; -import com.android.calendar.GeneralPreferences; -import com.android.calendar.R; -import com.android.calendar.Utils; - - -/** - * Allows the user to quickly create a new all-day event from the calendar's month view. - */ -public class CreateEventDialogFragment extends DialogFragment implements TextWatcher { - - private static final String TAG = "CreateEventDialogFragment"; - - private static final int TOKEN_CALENDARS = 1 << 3; - - private static final String KEY_DATE_STRING = "date_string"; - private static final String KEY_DATE_IN_MILLIS = "date_in_millis"; - private static final String EVENT_DATE_FORMAT = "%a, %b %d, %Y"; - - private AlertDialog mAlertDialog; - - private CalendarQueryService mService; - - private EditText mEventTitle; - private View mColor; - - private TextView mCalendarName; - private TextView mAccountName; - private TextView mDate; - private Button mButtonAddEvent; - - private CalendarController mController; - private EditEventHelper mEditEventHelper; - - private String mDateString; - private long mDateInMillis; - - private CalendarEventModel mModel; - private long mCalendarId = -1; - private String mCalendarOwner; - - private class CalendarQueryService extends AsyncQueryService { - - /** - * @param context - */ - public CalendarQueryService(Context context) { - super(context); - } - - @Override - public void onQueryComplete(int token, Object cookie, Cursor cursor) { - setDefaultCalendarView(cursor); - if (cursor != null) { - cursor.close(); - } - } - } - - public CreateEventDialogFragment() { - // Empty constructor required for DialogFragment. - } - - public CreateEventDialogFragment(Time day) { - setDay(day); - } - - public void setDay(Time day) { - mDateString = day.format(EVENT_DATE_FORMAT); - mDateInMillis = day.toMillis(true); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mDateString = savedInstanceState.getString(KEY_DATE_STRING); - mDateInMillis = savedInstanceState.getLong(KEY_DATE_IN_MILLIS); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); - final LayoutInflater layoutInflater = (LayoutInflater) activity - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = layoutInflater.inflate(R.layout.create_event_dialog, null); - - mColor = view.findViewById(R.id.color); - mCalendarName = (TextView) view.findViewById(R.id.calendar_name); - mAccountName = (TextView) view.findViewById(R.id.account_name); - - mEventTitle = (EditText) view.findViewById(R.id.event_title); - mEventTitle.addTextChangedListener(this); - - mDate = (TextView) view.findViewById(R.id.event_day); - if (mDateString != null) { - mDate.setText(mDateString); - } - - mAlertDialog = new AlertDialog.Builder(activity) - .setTitle(R.string.new_event_dialog_label) - .setView(view) - .setPositiveButton(R.string.create_event_dialog_save, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - createAllDayEvent(); - dismiss(); - } - }) - .setNeutralButton(R.string.edit_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - mController.sendEventRelatedEventWithExtraWithTitleWithCalendarId(this, - EventType.CREATE_EVENT, -1, mDateInMillis, - mDateInMillis + DateUtils.DAY_IN_MILLIS, 0, 0, - CalendarController.EXTRA_CREATE_ALL_DAY, -1, - mEventTitle.getText().toString(), - mCalendarId); - dismiss(); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); - - return mAlertDialog; - } - - @Override - public void onResume() { - super.onResume(); - if (mButtonAddEvent == null) { - mButtonAddEvent = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE); - mButtonAddEvent.setEnabled(mEventTitle.getText().toString().length() > 0); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(KEY_DATE_STRING, mDateString); - outState.putLong(KEY_DATE_IN_MILLIS, mDateInMillis); - } - - @Override - public void onActivityCreated(Bundle args) { - super.onActivityCreated(args); - final Context context = getActivity(); - mController = CalendarController.getInstance(getActivity()); - mEditEventHelper = new EditEventHelper(context); - mModel = new CalendarEventModel(context); - mService = new CalendarQueryService(context); - mService.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, - EditEventHelper.CALENDARS_PROJECTION, - EditEventHelper.CALENDARS_WHERE_WRITEABLE_VISIBLE, null, - null); - } - - private void createAllDayEvent() { - mModel.mStart = mDateInMillis; - mModel.mEnd = mDateInMillis + DateUtils.DAY_IN_MILLIS; - mModel.mTitle = mEventTitle.getText().toString(); - mModel.mAllDay = true; - mModel.mCalendarId = mCalendarId; - mModel.mOwnerAccount = mCalendarOwner; - - if (mEditEventHelper.saveEvent(mModel, null, 0)) { - Toast.makeText(getActivity(), R.string.creating_event, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void afterTextChanged(Editable s) { - // Do nothing. - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (mButtonAddEvent != null) { - mButtonAddEvent.setEnabled(s.length() > 0); - } - } - - // Find the calendar position in the cursor that matches calendar in - // preference - private void setDefaultCalendarView(Cursor cursor) { - if (cursor == null || cursor.getCount() == 0) { - // Create an error message for the user that, when clicked, - // will exit this activity without saving the event. - dismiss(); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( - android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) - .setPositiveButton(R.string.add_account, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - final Activity activity = getActivity(); - if (activity != null) { - Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); - final String[] array = {"com.android.calendar"}; - nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); - nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_NEW_TASK); - activity.startActivity(nextIntent); - } - } - }) - .setNegativeButton(android.R.string.no, null); - builder.show(); - return; - } - - - String defaultCalendar = null; - final Activity activity = getActivity(); - if (activity != null) { - defaultCalendar = Utils.getSharedPreference(activity, - GeneralPreferences.KEY_DEFAULT_CALENDAR, (String) null); - } else { - Log.e(TAG, "Activity is null, cannot load default calendar"); - } - - int calendarOwnerIndex = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); - int accountNameIndex = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - int accountTypeIndex = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - String calendarOwner = cursor.getString(calendarOwnerIndex); - if (defaultCalendar == null) { - // There is no stored default upon the first time running. Use a primary - // calendar in this case. - if (calendarOwner != null && - calendarOwner.equals(cursor.getString(accountNameIndex)) && - !CalendarContract.ACCOUNT_TYPE_LOCAL.equals( - cursor.getString(accountTypeIndex))) { - setCalendarFields(cursor); - return; - } - } else if (defaultCalendar.equals(calendarOwner)) { - // Found the default calendar. - setCalendarFields(cursor); - return; - } - } - cursor.moveToFirst(); - setCalendarFields(cursor); - } - - private void setCalendarFields(Cursor cursor) { - int calendarIdIndex = cursor.getColumnIndexOrThrow(Calendars._ID); - int colorIndex = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); - int calendarNameIndex = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); - int accountNameIndex = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - int calendarOwnerIndex = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); - - mCalendarId = cursor.getLong(calendarIdIndex); - mCalendarOwner = cursor.getString(calendarOwnerIndex); - mColor.setBackgroundColor(Utils.getDisplayColorFromColor(cursor - .getInt(colorIndex))); - String accountName = cursor.getString(accountNameIndex); - String calendarName = cursor.getString(calendarNameIndex); - mCalendarName.setText(calendarName); - if (calendarName.equals(accountName)) { - mAccountName.setVisibility(View.GONE); - } else { - mAccountName.setVisibility(View.VISIBLE); - mAccountName.setText(accountName); - } - } -} diff --git a/src/com/android/calendar/event/EditEventActivity.java b/src/com/android/calendar/event/EditEventActivity.java deleted file mode 100644 index 173a5735..00000000 --- a/src/com/android/calendar/event/EditEventActivity.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY; -import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; -import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; - -import android.app.ActionBar; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.provider.CalendarContract.Events; -import android.text.format.Time; -import android.util.Log; -import android.view.MenuItem; - -import com.android.calendar.AbstractCalendarActivity; -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.R; -import com.android.calendar.Utils; - -import java.util.ArrayList; - -public class EditEventActivity extends AbstractCalendarActivity { - private static final String TAG = "EditEventActivity"; - - private static final boolean DEBUG = false; - - private static final String BUNDLE_KEY_EVENT_ID = "key_event_id"; - - public static final String EXTRA_EVENT_COLOR = "event_color"; - - public static final String EXTRA_EVENT_REMINDERS = "reminders"; - - private static boolean mIsMultipane; - - private EditEventFragment mEditFragment; - - private ArrayList<ReminderEntry> mReminders; - - private int mEventColor; - - private boolean mEventColorInitialized; - - private EventInfo mEventInfo; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.simple_frame_layout); - - mEventInfo = getEventInfoFromIntent(icicle); - mReminders = getReminderEntriesFromIntent(); - mEventColorInitialized = getIntent().hasExtra(EXTRA_EVENT_COLOR); - mEventColor = getIntent().getIntExtra(EXTRA_EVENT_COLOR, -1); - - - mEditFragment = (EditEventFragment) getFragmentManager().findFragmentById(R.id.main_frame); - - mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config); - - if (mIsMultipane) { - getActionBar().setDisplayOptions( - ActionBar.DISPLAY_SHOW_TITLE, - ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME - | ActionBar.DISPLAY_SHOW_TITLE); - getActionBar().setTitle( - mEventInfo.id == -1 ? R.string.event_create : R.string.event_edit); - } - else { - getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, - ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME| - ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); - } - - if (mEditFragment == null) { - Intent intent = null; - if (mEventInfo.id == -1) { - intent = getIntent(); - } - - mEditFragment = new EditEventFragment(mEventInfo, mReminders, mEventColorInitialized, - mEventColor, false, intent); - - mEditFragment.mShowModifyDialogOnLaunch = getIntent().getBooleanExtra( - CalendarController.EVENT_EDIT_ON_LAUNCH, false); - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(R.id.main_frame, mEditFragment); - ft.show(mEditFragment); - ft.commit(); - } - } - - @SuppressWarnings("unchecked") - private ArrayList<ReminderEntry> getReminderEntriesFromIntent() { - Intent intent = getIntent(); - return (ArrayList<ReminderEntry>) intent.getSerializableExtra(EXTRA_EVENT_REMINDERS); - } - - private EventInfo getEventInfoFromIntent(Bundle icicle) { - EventInfo info = new EventInfo(); - long eventId = -1; - Intent intent = getIntent(); - Uri data = intent.getData(); - if (data != null) { - try { - eventId = Long.parseLong(data.getLastPathSegment()); - } catch (NumberFormatException e) { - if (DEBUG) { - Log.d(TAG, "Create new event"); - } - } - } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) { - eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID); - } - - boolean allDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false); - - long begin = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1); - long end = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1); - if (end != -1) { - info.endTime = new Time(); - if (allDay) { - info.endTime.timezone = Time.TIMEZONE_UTC; - } - info.endTime.set(end); - } - if (begin != -1) { - info.startTime = new Time(); - if (allDay) { - info.startTime.timezone = Time.TIMEZONE_UTC; - } - info.startTime.set(begin); - } - info.id = eventId; - info.eventTitle = intent.getStringExtra(Events.TITLE); - info.calendarId = intent.getLongExtra(Events.CALENDAR_ID, -1); - - if (allDay) { - info.extraLong = CalendarController.EXTRA_CREATE_ALL_DAY; - } else { - info.extraLong = 0; - } - return info; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - Utils.returnToCalendarHome(this); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/com/android/calendar/event/EditEventFragment.java b/src/com/android/calendar/event/EditEventFragment.java deleted file mode 100644 index 2c966e94..00000000 --- a/src/com/android/calendar/event/EditEventFragment.java +++ /dev/null @@ -1,990 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.AsyncQueryHandler; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Colors; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.text.TextUtils; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.LinearLayout; -import android.widget.Toast; - -import com.android.calendar.AsyncQueryService; -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventHandler; -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarEventModel; -import com.android.calendar.CalendarEventModel.Attendee; -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.DeleteEventHelper; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; -import com.android.colorpicker.HsvColorComparator; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; - -public class EditEventFragment extends Fragment implements EventHandler, OnColorSelectedListener { - private static final String TAG = "EditEventActivity"; - private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog"; - - private static final int REQUEST_CODE_COLOR_PICKER = 0; - - private static final String BUNDLE_KEY_MODEL = "key_model"; - private static final String BUNDLE_KEY_EDIT_STATE = "key_edit_state"; - private static final String BUNDLE_KEY_EVENT = "key_event"; - private static final String BUNDLE_KEY_READ_ONLY = "key_read_only"; - private static final String BUNDLE_KEY_EDIT_ON_LAUNCH = "key_edit_on_launch"; - private static final String BUNDLE_KEY_SHOW_COLOR_PALETTE = "show_color_palette"; - - private static final String BUNDLE_KEY_DATE_BUTTON_CLICKED = "date_button_clicked"; - - private static final boolean DEBUG = false; - - private static final int TOKEN_EVENT = 1; - private static final int TOKEN_ATTENDEES = 1 << 1; - private static final int TOKEN_REMINDERS = 1 << 2; - private static final int TOKEN_CALENDARS = 1 << 3; - private static final int TOKEN_COLORS = 1 << 4; - - private static final int TOKEN_ALL = TOKEN_EVENT | TOKEN_ATTENDEES | TOKEN_REMINDERS - | TOKEN_CALENDARS | TOKEN_COLORS; - private static final int TOKEN_UNITIALIZED = 1 << 31; - - /** - * A bitfield of TOKEN_* to keep track which query hasn't been completed - * yet. Once all queries have returned, the model can be applied to the - * view. - */ - private int mOutstandingQueries = TOKEN_UNITIALIZED; - - EditEventHelper mHelper; - CalendarEventModel mModel; - CalendarEventModel mOriginalModel; - CalendarEventModel mRestoreModel; - EditEventView mView; - QueryHandler mHandler; - - private AlertDialog mModifyDialog; - int mModification = Utils.MODIFY_UNINITIALIZED; - - private final EventInfo mEvent; - private EventBundle mEventBundle; - private ArrayList<ReminderEntry> mReminders; - private int mEventColor; - private boolean mEventColorInitialized = false; - private Uri mUri; - private long mBegin; - private long mEnd; - private long mCalendarId = -1; - - private EventColorPickerDialog mColorPickerDialog; - - private Activity mActivity; - private final Done mOnDone = new Done(); - - private boolean mSaveOnDetach = true; - private boolean mIsReadOnly = false; - public boolean mShowModifyDialogOnLaunch = false; - private boolean mShowColorPalette = false; - - private boolean mTimeSelectedWasStartTime; - private boolean mDateSelectedWasStartDate; - - private InputMethodManager mInputMethodManager; - - private final Intent mIntent; - - private boolean mUseCustomActionBar; - - private final View.OnClickListener mActionBarListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - onActionBarItemSelected(v.getId()); - } - }; - - // TODO turn this into a helper function in EditEventHelper for building the - // model - private class QueryHandler extends AsyncQueryHandler { - public QueryHandler(ContentResolver cr) { - super(cr); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - // If the query didn't return a cursor for some reason return - if (cursor == null) { - return; - } - - // If the Activity is finishing, then close the cursor. - // Otherwise, use the new cursor in the adapter. - final Activity activity = EditEventFragment.this.getActivity(); - if (activity == null || activity.isFinishing()) { - cursor.close(); - return; - } - long eventId; - switch (token) { - case TOKEN_EVENT: - if (cursor.getCount() == 0) { - // The cursor is empty. This can happen if the event - // was deleted. - cursor.close(); - mOnDone.setDoneCode(Utils.DONE_EXIT); - mSaveOnDetach = false; - mOnDone.run(); - return; - } - mOriginalModel = new CalendarEventModel(); - EditEventHelper.setModelFromCursor(mOriginalModel, cursor); - EditEventHelper.setModelFromCursor(mModel, cursor); - cursor.close(); - - mOriginalModel.mUri = mUri.toString(); - - mModel.mUri = mUri.toString(); - mModel.mOriginalStart = mBegin; - mModel.mOriginalEnd = mEnd; - mModel.mIsFirstEventInSeries = mBegin == mOriginalModel.mStart; - mModel.mStart = mBegin; - mModel.mEnd = mEnd; - if (mEventColorInitialized) { - mModel.setEventColor(mEventColor); - } - eventId = mModel.mId; - - // TOKEN_ATTENDEES - if (mModel.mHasAttendeeData && eventId != -1) { - Uri attUri = Attendees.CONTENT_URI; - String[] whereArgs = { - Long.toString(eventId) - }; - mHandler.startQuery(TOKEN_ATTENDEES, null, attUri, - EditEventHelper.ATTENDEES_PROJECTION, - EditEventHelper.ATTENDEES_WHERE /* selection */, - whereArgs /* selection args */, null /* sort order */); - } else { - setModelIfDone(TOKEN_ATTENDEES); - } - - // TOKEN_REMINDERS - if (mModel.mHasAlarm && mReminders == null) { - Uri rUri = Reminders.CONTENT_URI; - String[] remArgs = { - Long.toString(eventId) - }; - mHandler.startQuery(TOKEN_REMINDERS, null, rUri, - EditEventHelper.REMINDERS_PROJECTION, - EditEventHelper.REMINDERS_WHERE /* selection */, - remArgs /* selection args */, null /* sort order */); - } else { - if (mReminders == null) { - // mReminders should not be null. - mReminders = new ArrayList<ReminderEntry>(); - } else { - Collections.sort(mReminders); - } - mOriginalModel.mReminders = mReminders; - mModel.mReminders = - (ArrayList<ReminderEntry>) mReminders.clone(); - setModelIfDone(TOKEN_REMINDERS); - } - - // TOKEN_CALENDARS - String[] selArgs = { - Long.toString(mModel.mCalendarId) - }; - mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, - EditEventHelper.CALENDARS_PROJECTION, EditEventHelper.CALENDARS_WHERE, - selArgs /* selection args */, null /* sort order */); - - // TOKEN_COLORS - mHandler.startQuery(TOKEN_COLORS, null, Colors.CONTENT_URI, - EditEventHelper.COLORS_PROJECTION, - Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT, null, null); - - setModelIfDone(TOKEN_EVENT); - break; - case TOKEN_ATTENDEES: - try { - while (cursor.moveToNext()) { - String name = cursor.getString(EditEventHelper.ATTENDEES_INDEX_NAME); - String email = cursor.getString(EditEventHelper.ATTENDEES_INDEX_EMAIL); - int status = cursor.getInt(EditEventHelper.ATTENDEES_INDEX_STATUS); - int relationship = cursor - .getInt(EditEventHelper.ATTENDEES_INDEX_RELATIONSHIP); - if (relationship == Attendees.RELATIONSHIP_ORGANIZER) { - if (email != null) { - mModel.mOrganizer = email; - mModel.mIsOrganizer = mModel.mOwnerAccount - .equalsIgnoreCase(email); - mOriginalModel.mOrganizer = email; - mOriginalModel.mIsOrganizer = mOriginalModel.mOwnerAccount - .equalsIgnoreCase(email); - } - - if (TextUtils.isEmpty(name)) { - mModel.mOrganizerDisplayName = mModel.mOrganizer; - mOriginalModel.mOrganizerDisplayName = - mOriginalModel.mOrganizer; - } else { - mModel.mOrganizerDisplayName = name; - mOriginalModel.mOrganizerDisplayName = name; - } - } - - if (email != null) { - if (mModel.mOwnerAccount != null && - mModel.mOwnerAccount.equalsIgnoreCase(email)) { - int attendeeId = - cursor.getInt(EditEventHelper.ATTENDEES_INDEX_ID); - mModel.mOwnerAttendeeId = attendeeId; - mModel.mSelfAttendeeStatus = status; - mOriginalModel.mOwnerAttendeeId = attendeeId; - mOriginalModel.mSelfAttendeeStatus = status; - continue; - } - } - Attendee attendee = new Attendee(name, email); - attendee.mStatus = status; - mModel.addAttendee(attendee); - mOriginalModel.addAttendee(attendee); - } - } finally { - cursor.close(); - } - - setModelIfDone(TOKEN_ATTENDEES); - break; - case TOKEN_REMINDERS: - try { - // Add all reminders to the models - while (cursor.moveToNext()) { - int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES); - int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD); - ReminderEntry re = ReminderEntry.valueOf(minutes, method); - mModel.mReminders.add(re); - mOriginalModel.mReminders.add(re); - } - - // Sort appropriately for display - Collections.sort(mModel.mReminders); - Collections.sort(mOriginalModel.mReminders); - } finally { - cursor.close(); - } - - setModelIfDone(TOKEN_REMINDERS); - break; - case TOKEN_CALENDARS: - try { - if (mModel.mId == -1) { - // Populate Calendar spinner only if no event id is set. - MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); - if (DEBUG) { - Log.d(TAG, "onQueryComplete: setting cursor with " - + matrixCursor.getCount() + " calendars"); - } - mView.setCalendarsCursor(matrixCursor, isAdded() && isResumed(), - mCalendarId); - } else { - // Populate model for an existing event - EditEventHelper.setModelFromCalendarCursor(mModel, cursor); - EditEventHelper.setModelFromCalendarCursor(mOriginalModel, cursor); - } - } finally { - cursor.close(); - } - setModelIfDone(TOKEN_CALENDARS); - break; - case TOKEN_COLORS: - if (cursor.moveToFirst()) { - EventColorCache cache = new EventColorCache(); - do - { - int colorKey = cursor.getInt(EditEventHelper.COLORS_INDEX_COLOR_KEY); - int rawColor = cursor.getInt(EditEventHelper.COLORS_INDEX_COLOR); - int displayColor = Utils.getDisplayColorFromColor(rawColor); - String accountName = cursor - .getString(EditEventHelper.COLORS_INDEX_ACCOUNT_NAME); - String accountType = cursor - .getString(EditEventHelper.COLORS_INDEX_ACCOUNT_TYPE); - cache.insertColor(accountName, accountType, - displayColor, colorKey); - } while (cursor.moveToNext()); - cache.sortPalettes(new HsvColorComparator()); - - mModel.mEventColorCache = cache; - mView.mColorPickerNewEvent.setOnClickListener(mOnColorPickerClicked); - mView.mColorPickerExistingEvent.setOnClickListener(mOnColorPickerClicked); - } - if (cursor != null) { - cursor.close(); - } - - // If the account name/type is null, the calendar event colors cannot be - // determined, so take the default/savedInstanceState value. - if (mModel.mCalendarAccountName == null - || mModel.mCalendarAccountType == null) { - mView.setColorPickerButtonStates(mShowColorPalette); - } else { - mView.setColorPickerButtonStates(mModel.getCalendarEventColors()); - } - - setModelIfDone(TOKEN_COLORS); - break; - default: - cursor.close(); - break; - } - } - } - - private View.OnClickListener mOnColorPickerClicked = new View.OnClickListener() { - - @Override - public void onClick(View v) { - int[] colors = mModel.getCalendarEventColors(); - if (mColorPickerDialog == null) { - mColorPickerDialog = EventColorPickerDialog.newInstance(colors, - mModel.getEventColor(), mModel.getCalendarColor(), mView.mIsMultipane); - mColorPickerDialog.setOnColorSelectedListener(EditEventFragment.this); - } else { - mColorPickerDialog.setCalendarColor(mModel.getCalendarColor()); - mColorPickerDialog.setColors(colors, mModel.getEventColor()); - } - final FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.executePendingTransactions(); - if (!mColorPickerDialog.isAdded()) { - mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); - } - } - }; - - private void setModelIfDone(int queryType) { - synchronized (this) { - mOutstandingQueries &= ~queryType; - if (mOutstandingQueries == 0) { - if (mRestoreModel != null) { - mModel = mRestoreModel; - } - if (mShowModifyDialogOnLaunch && mModification == Utils.MODIFY_UNINITIALIZED) { - if (!TextUtils.isEmpty(mModel.mRrule)) { - displayEditWhichDialog(); - } else { - mModification = Utils.MODIFY_ALL; - } - - } - mView.setModel(mModel); - mView.setModification(mModification); - } - } - } - - public EditEventFragment() { - this(null, null, false, -1, false, null); - } - - public EditEventFragment(EventInfo event, ArrayList<ReminderEntry> reminders, - boolean eventColorInitialized, int eventColor, boolean readOnly, Intent intent) { - mEvent = event; - mIsReadOnly = readOnly; - mIntent = intent; - - mReminders = reminders; - mEventColorInitialized = eventColorInitialized; - if (eventColorInitialized) { - mEventColor = eventColor; - } - setHasOptionsMenu(true); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mColorPickerDialog = (EventColorPickerDialog) getActivity().getFragmentManager() - .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); - if (mColorPickerDialog != null) { - mColorPickerDialog.setOnColorSelectedListener(this); - } - } - - private void startQuery() { - mUri = null; - mBegin = -1; - mEnd = -1; - if (mEvent != null) { - if (mEvent.id != -1) { - mModel.mId = mEvent.id; - mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEvent.id); - } else { - // New event. All day? - mModel.mAllDay = mEvent.extraLong == CalendarController.EXTRA_CREATE_ALL_DAY; - } - if (mEvent.startTime != null) { - mBegin = mEvent.startTime.toMillis(true); - } - if (mEvent.endTime != null) { - mEnd = mEvent.endTime.toMillis(true); - } - if (mEvent.calendarId != -1) { - mCalendarId = mEvent.calendarId; - } - } else if (mEventBundle != null) { - if (mEventBundle.id != -1) { - mModel.mId = mEventBundle.id; - mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventBundle.id); - } - mBegin = mEventBundle.start; - mEnd = mEventBundle.end; - } - - if (mReminders != null) { - mModel.mReminders = mReminders; - } - - if (mEventColorInitialized) { - mModel.setEventColor(mEventColor); - } - - if (mBegin <= 0) { - // use a default value instead - mBegin = mHelper.constructDefaultStartTime(System.currentTimeMillis()); - } - if (mEnd < mBegin) { - // use a default value instead - mEnd = mHelper.constructDefaultEndTime(mBegin); - } - - // Kick off the query for the event - boolean newEvent = mUri == null; - if (!newEvent) { - mModel.mCalendarAccessLevel = Calendars.CAL_ACCESS_NONE; - mOutstandingQueries = TOKEN_ALL; - if (DEBUG) { - Log.d(TAG, "startQuery: uri for event is " + mUri.toString()); - } - mHandler.startQuery(TOKEN_EVENT, null, mUri, EditEventHelper.EVENT_PROJECTION, - null /* selection */, null /* selection args */, null /* sort order */); - } else { - mOutstandingQueries = TOKEN_CALENDARS | TOKEN_COLORS; - if (DEBUG) { - Log.d(TAG, "startQuery: Editing a new event."); - } - mModel.mOriginalStart = mBegin; - mModel.mOriginalEnd = mEnd; - mModel.mStart = mBegin; - mModel.mEnd = mEnd; - mModel.mCalendarId = mCalendarId; - mModel.mSelfAttendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; - - // Start a query in the background to read the list of calendars and colors - mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, - EditEventHelper.CALENDARS_PROJECTION, - EditEventHelper.CALENDARS_WHERE_WRITEABLE_VISIBLE, null /* selection args */, - null /* sort order */); - - mHandler.startQuery(TOKEN_COLORS, null, Colors.CONTENT_URI, - EditEventHelper.COLORS_PROJECTION, - Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT, null, null); - - mModification = Utils.MODIFY_ALL; - mView.setModification(mModification); - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mActivity = activity; - - mHelper = new EditEventHelper(activity, null); - mHandler = new QueryHandler(activity.getContentResolver()); - mModel = new CalendarEventModel(activity, mIntent); - mInputMethodManager = (InputMethodManager) - activity.getSystemService(Context.INPUT_METHOD_SERVICE); - - mUseCustomActionBar = !Utils.getConfigBool(mActivity, R.bool.multiple_pane_config); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { -// mContext.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - View view; - if (mIsReadOnly) { - view = inflater.inflate(R.layout.edit_event_single_column, null); - } else { - view = inflater.inflate(R.layout.edit_event, null); - } - mView = new EditEventView(mActivity, view, mOnDone, mTimeSelectedWasStartTime, - mDateSelectedWasStartDate); - startQuery(); - - if (mUseCustomActionBar) { - View actionBarButtons = inflater.inflate(R.layout.edit_event_custom_actionbar, - new LinearLayout(mActivity), false); - View cancelActionView = actionBarButtons.findViewById(R.id.action_cancel); - cancelActionView.setOnClickListener(mActionBarListener); - View doneActionView = actionBarButtons.findViewById(R.id.action_done); - doneActionView.setOnClickListener(mActionBarListener); - - mActivity.getActionBar().setCustomView(actionBarButtons); - } - - return view; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - if (mUseCustomActionBar) { - mActivity.getActionBar().setCustomView(null); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - if (savedInstanceState.containsKey(BUNDLE_KEY_MODEL)) { - mRestoreModel = (CalendarEventModel) savedInstanceState.getSerializable( - BUNDLE_KEY_MODEL); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_EDIT_STATE)) { - mModification = savedInstanceState.getInt(BUNDLE_KEY_EDIT_STATE); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_EDIT_ON_LAUNCH)) { - mShowModifyDialogOnLaunch = savedInstanceState - .getBoolean(BUNDLE_KEY_EDIT_ON_LAUNCH); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_EVENT)) { - mEventBundle = (EventBundle) savedInstanceState.getSerializable(BUNDLE_KEY_EVENT); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_READ_ONLY)) { - mIsReadOnly = savedInstanceState.getBoolean(BUNDLE_KEY_READ_ONLY); - } - if (savedInstanceState.containsKey("EditEventView_timebuttonclicked")) { - mTimeSelectedWasStartTime = savedInstanceState.getBoolean( - "EditEventView_timebuttonclicked"); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_DATE_BUTTON_CLICKED)) { - mDateSelectedWasStartDate = savedInstanceState.getBoolean( - BUNDLE_KEY_DATE_BUTTON_CLICKED); - } - if (savedInstanceState.containsKey(BUNDLE_KEY_SHOW_COLOR_PALETTE)) { - mShowColorPalette = savedInstanceState.getBoolean(BUNDLE_KEY_SHOW_COLOR_PALETTE); - } - - } - } - - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - - if (!mUseCustomActionBar) { - inflater.inflate(R.menu.edit_event_title_bar, menu); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return onActionBarItemSelected(item.getItemId()); - } - - /** - * Handles menu item selections, whether they come from our custom action bar buttons or from - * the standard menu items. Depends on the menu item ids matching the custom action bar button - * ids. - * - * @param itemId the button or menu item id - * @return whether the event was handled here - */ - private boolean onActionBarItemSelected(int itemId) { - if (itemId == R.id.action_done) { - if (EditEventHelper.canModifyEvent(mModel) || EditEventHelper.canRespond(mModel)) { - if (mView != null && mView.prepareForSave()) { - if (mModification == Utils.MODIFY_UNINITIALIZED) { - mModification = Utils.MODIFY_ALL; - } - mOnDone.setDoneCode(Utils.DONE_SAVE | Utils.DONE_EXIT); - mOnDone.run(); - } else { - mOnDone.setDoneCode(Utils.DONE_REVERT); - mOnDone.run(); - } - } else if (EditEventHelper.canAddReminders(mModel) && mModel.mId != -1 - && mOriginalModel != null && mView.prepareForSave()) { - saveReminders(); - mOnDone.setDoneCode(Utils.DONE_EXIT); - mOnDone.run(); - } else { - mOnDone.setDoneCode(Utils.DONE_REVERT); - mOnDone.run(); - } - } else if (itemId == R.id.action_cancel) { - mOnDone.setDoneCode(Utils.DONE_REVERT); - mOnDone.run(); - } - return true; - } - - private void saveReminders() { - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); - boolean changed = EditEventHelper.saveReminders(ops, mModel.mId, mModel.mReminders, - mOriginalModel.mReminders, false /* no force save */); - - if (!changed) { - return; - } - - AsyncQueryService service = new AsyncQueryService(getActivity()); - service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0); - // Update the "hasAlarm" field for the event - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mModel.mId); - int len = mModel.mReminders.size(); - boolean hasAlarm = len > 0; - if (hasAlarm != mOriginalModel.mHasAlarm) { - ContentValues values = new ContentValues(); - values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0); - service.startUpdate(0, null, uri, values, null, null, 0); - } - - Toast.makeText(mActivity, R.string.saving_event, Toast.LENGTH_SHORT).show(); - } - - protected void displayEditWhichDialog() { - if (mModification == Utils.MODIFY_UNINITIALIZED) { - final boolean notSynced = TextUtils.isEmpty(mModel.mSyncId); - boolean isFirstEventInSeries = mModel.mIsFirstEventInSeries; - int itemIndex = 0; - CharSequence[] items; - - if (notSynced) { - // If this event has not been synced, then don't allow deleting - // or changing a single instance. - if (isFirstEventInSeries) { - // Still display the option so the user knows all events are - // changing - items = new CharSequence[1]; - } else { - items = new CharSequence[2]; - } - } else { - if (isFirstEventInSeries) { - items = new CharSequence[2]; - } else { - items = new CharSequence[3]; - } - items[itemIndex++] = mActivity.getText(R.string.modify_event); - } - items[itemIndex++] = mActivity.getText(R.string.modify_all); - - // Do one more check to make sure this remains at the end of the list - if (!isFirstEventInSeries) { - items[itemIndex++] = mActivity.getText(R.string.modify_all_following); - } - - // Display the modification dialog. - if (mModifyDialog != null) { - mModifyDialog.dismiss(); - mModifyDialog = null; - } - mModifyDialog = new AlertDialog.Builder(mActivity).setTitle(R.string.edit_event_label) - .setItems(items, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - // Update this if we start allowing exceptions - // to unsynced events in the app - mModification = notSynced ? Utils.MODIFY_ALL - : Utils.MODIFY_SELECTED; - if (mModification == Utils.MODIFY_SELECTED) { - mModel.mOriginalSyncId = notSynced ? null : mModel.mSyncId; - mModel.mOriginalId = mModel.mId; - } - } else if (which == 1) { - mModification = notSynced ? Utils.MODIFY_ALL_FOLLOWING - : Utils.MODIFY_ALL; - } else if (which == 2) { - mModification = Utils.MODIFY_ALL_FOLLOWING; - } - - mView.setModification(mModification); - } - }).show(); - - mModifyDialog.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - Activity a = EditEventFragment.this.getActivity(); - if (a != null) { - a.finish(); - } - } - }); - } - } - - class Done implements EditEventHelper.EditDoneRunnable { - private int mCode = -1; - - @Override - public void setDoneCode(int code) { - mCode = code; - } - - @Override - public void run() { - // We only want this to get called once, either because the user - // pressed back/home or one of the buttons on screen - mSaveOnDetach = false; - if (mModification == Utils.MODIFY_UNINITIALIZED) { - // If this is uninitialized the user hit back, the only - // changeable item is response to default to all events. - mModification = Utils.MODIFY_ALL; - } - - if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null - && (EditEventHelper.canRespond(mModel) - || EditEventHelper.canModifyEvent(mModel)) - && mView.prepareForSave() - && !isEmptyNewEvent() - && mModel.normalizeReminders() - && mHelper.saveEvent(mModel, mOriginalModel, mModification)) { - int stringResource; - if (!mModel.mAttendeesList.isEmpty()) { - if (mModel.mUri != null) { - stringResource = R.string.saving_event_with_guest; - } else { - stringResource = R.string.creating_event_with_guest; - } - } else { - if (mModel.mUri != null) { - stringResource = R.string.saving_event; - } else { - stringResource = R.string.creating_event; - } - } - Toast.makeText(mActivity, stringResource, Toast.LENGTH_SHORT).show(); - } else if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null && isEmptyNewEvent()) { - Toast.makeText(mActivity, R.string.empty_event, Toast.LENGTH_SHORT).show(); - } - - if ((mCode & Utils.DONE_DELETE) != 0 && mOriginalModel != null - && EditEventHelper.canModifyCalendar(mOriginalModel)) { - long begin = mModel.mStart; - long end = mModel.mEnd; - int which = -1; - switch (mModification) { - case Utils.MODIFY_SELECTED: - which = DeleteEventHelper.DELETE_SELECTED; - break; - case Utils.MODIFY_ALL_FOLLOWING: - which = DeleteEventHelper.DELETE_ALL_FOLLOWING; - break; - case Utils.MODIFY_ALL: - which = DeleteEventHelper.DELETE_ALL; - break; - } - DeleteEventHelper deleteHelper = new DeleteEventHelper( - mActivity, mActivity, !mIsReadOnly /* exitWhenDone */); - deleteHelper.delete(begin, end, mOriginalModel, which); - } - - if ((mCode & Utils.DONE_EXIT) != 0) { - // This will exit the edit event screen, should be called - // when we want to return to the main calendar views - if ((mCode & Utils.DONE_SAVE) != 0) { - if (mActivity != null) { - long start = mModel.mStart; - long end = mModel.mEnd; - if (mModel.mAllDay) { - // For allday events we want to go to the day in the - // user's current tz - String tz = Utils.getTimeZone(mActivity, null); - Time t = new Time(Time.TIMEZONE_UTC); - t.set(start); - t.timezone = tz; - start = t.toMillis(true); - - t.timezone = Time.TIMEZONE_UTC; - t.set(end); - t.timezone = tz; - end = t.toMillis(true); - } - CalendarController.getInstance(mActivity).launchViewEvent(-1, start, end, - Attendees.ATTENDEE_STATUS_NONE); - } - } - Activity a = EditEventFragment.this.getActivity(); - if (a != null) { - a.finish(); - } - } - - // Hide a software keyboard so that user won't see it even after this Fragment's - // disappearing. - final View focusedView = mActivity.getCurrentFocus(); - if (focusedView != null) { - mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); - focusedView.clearFocus(); - } - } - } - - boolean isEmptyNewEvent() { - if (mOriginalModel != null) { - // Not new - return false; - } - - if (mModel.mOriginalStart != mModel.mStart || mModel.mOriginalEnd != mModel.mEnd) { - return false; - } - - if (!mModel.mAttendeesList.isEmpty()) { - return false; - } - - return mModel.isEmpty(); - } - - @Override - public void onPause() { - Activity act = getActivity(); - if (mSaveOnDetach && act != null && !mIsReadOnly && !act.isChangingConfigurations() - && mView.prepareForSave()) { - mOnDone.setDoneCode(Utils.DONE_SAVE); - mOnDone.run(); - } - super.onPause(); - } - - @Override - public void onDestroy() { - if (mView != null) { - mView.setModel(null); - } - if (mModifyDialog != null) { - mModifyDialog.dismiss(); - mModifyDialog = null; - } - super.onDestroy(); - } - - @Override - public void eventsChanged() { - // TODO Requery to see if event has changed - } - - @Override - public void onSaveInstanceState(Bundle outState) { - mView.prepareForSave(); - outState.putSerializable(BUNDLE_KEY_MODEL, mModel); - outState.putInt(BUNDLE_KEY_EDIT_STATE, mModification); - if (mEventBundle == null && mEvent != null) { - mEventBundle = new EventBundle(); - mEventBundle.id = mEvent.id; - if (mEvent.startTime != null) { - mEventBundle.start = mEvent.startTime.toMillis(true); - } - if (mEvent.endTime != null) { - mEventBundle.end = mEvent.startTime.toMillis(true); - } - } - outState.putBoolean(BUNDLE_KEY_EDIT_ON_LAUNCH, mShowModifyDialogOnLaunch); - outState.putSerializable(BUNDLE_KEY_EVENT, mEventBundle); - outState.putBoolean(BUNDLE_KEY_READ_ONLY, mIsReadOnly); - outState.putBoolean(BUNDLE_KEY_SHOW_COLOR_PALETTE, mView.isColorPaletteVisible()); - - outState.putBoolean("EditEventView_timebuttonclicked", mView.mTimeSelectedWasStartTime); - outState.putBoolean(BUNDLE_KEY_DATE_BUTTON_CLICKED, mView.mDateSelectedWasStartDate); - } - - @Override - public long getSupportedEventTypes() { - return EventType.USER_HOME; - } - - @Override - public void handleEvent(EventInfo event) { - // It's currently unclear if we want to save the event or not when home - // is pressed. When creating a new event we shouldn't save since we - // can't get the id of the new event easily. - if ((false && event.eventType == EventType.USER_HOME) || (event.eventType == EventType.GO_TO - && mSaveOnDetach)) { - if (mView != null && mView.prepareForSave()) { - mOnDone.setDoneCode(Utils.DONE_SAVE); - mOnDone.run(); - } - } - } - - private static class EventBundle implements Serializable { - private static final long serialVersionUID = 1L; - long id = -1; - long start = -1; - long end = -1; - } - - @Override - public void onColorSelected(int color) { - if (!mModel.isEventColorInitialized() || mModel.getEventColor() != color) { - mModel.setEventColor(color); - mView.updateHeadlineColor(mModel, color); - } - } -} diff --git a/src/com/android/calendar/event/EditEventHelper.java b/src/com/android/calendar/event/EditEventHelper.java deleted file mode 100644 index c735e91e..00000000 --- a/src/com/android/calendar/event/EditEventHelper.java +++ /dev/null @@ -1,1390 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import android.content.ContentProviderOperation; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Colors; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.text.util.Rfc822Token; -import android.text.util.Rfc822Tokenizer; -import android.util.Log; -import android.view.View; - -import com.android.calendar.AbstractCalendarActivity; -import com.android.calendar.AsyncQueryService; -import com.android.calendar.CalendarEventModel; -import com.android.calendar.CalendarEventModel.Attendee; -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.Utils; -import com.android.calendarcommon2.DateException; -import com.android.calendarcommon2.EventRecurrence; -import com.android.calendarcommon2.RecurrenceProcessor; -import com.android.calendarcommon2.RecurrenceSet; -import com.android.common.Rfc822Validator; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.TimeZone; - -public class EditEventHelper { - private static final String TAG = "EditEventHelper"; - - private static final boolean DEBUG = false; - - // Used for parsing rrules for special cases. - private EventRecurrence mEventRecurrence = new EventRecurrence(); - - private static final String NO_EVENT_COLOR = ""; - - public static final String[] EVENT_PROJECTION = new String[] { - Events._ID, // 0 - Events.TITLE, // 1 - Events.DESCRIPTION, // 2 - Events.EVENT_LOCATION, // 3 - Events.ALL_DAY, // 4 - Events.HAS_ALARM, // 5 - Events.CALENDAR_ID, // 6 - Events.DTSTART, // 7 - Events.DTEND, // 8 - Events.DURATION, // 9 - Events.EVENT_TIMEZONE, // 10 - Events.RRULE, // 11 - Events._SYNC_ID, // 12 - Events.AVAILABILITY, // 13 - Events.ACCESS_LEVEL, // 14 - Events.OWNER_ACCOUNT, // 15 - Events.HAS_ATTENDEE_DATA, // 16 - Events.ORIGINAL_SYNC_ID, // 17 - Events.ORGANIZER, // 18 - Events.GUESTS_CAN_MODIFY, // 19 - Events.ORIGINAL_ID, // 20 - Events.STATUS, // 21 - Events.CALENDAR_COLOR, // 22 - Events.EVENT_COLOR, // 23 - Events.EVENT_COLOR_KEY // 24 - }; - protected static final int EVENT_INDEX_ID = 0; - protected static final int EVENT_INDEX_TITLE = 1; - protected static final int EVENT_INDEX_DESCRIPTION = 2; - protected static final int EVENT_INDEX_EVENT_LOCATION = 3; - protected static final int EVENT_INDEX_ALL_DAY = 4; - protected static final int EVENT_INDEX_HAS_ALARM = 5; - protected static final int EVENT_INDEX_CALENDAR_ID = 6; - protected static final int EVENT_INDEX_DTSTART = 7; - protected static final int EVENT_INDEX_DTEND = 8; - protected static final int EVENT_INDEX_DURATION = 9; - protected static final int EVENT_INDEX_TIMEZONE = 10; - protected static final int EVENT_INDEX_RRULE = 11; - protected static final int EVENT_INDEX_SYNC_ID = 12; - protected static final int EVENT_INDEX_AVAILABILITY = 13; - protected static final int EVENT_INDEX_ACCESS_LEVEL = 14; - protected static final int EVENT_INDEX_OWNER_ACCOUNT = 15; - protected static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 16; - protected static final int EVENT_INDEX_ORIGINAL_SYNC_ID = 17; - protected static final int EVENT_INDEX_ORGANIZER = 18; - protected static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 19; - protected static final int EVENT_INDEX_ORIGINAL_ID = 20; - protected static final int EVENT_INDEX_EVENT_STATUS = 21; - protected static final int EVENT_INDEX_CALENDAR_COLOR = 22; - protected static final int EVENT_INDEX_EVENT_COLOR = 23; - protected static final int EVENT_INDEX_EVENT_COLOR_KEY = 24; - - public static final String[] REMINDERS_PROJECTION = new String[] { - Reminders._ID, // 0 - Reminders.MINUTES, // 1 - Reminders.METHOD, // 2 - }; - public static final int REMINDERS_INDEX_MINUTES = 1; - public static final int REMINDERS_INDEX_METHOD = 2; - public static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?"; - - // Visible for testing - static final String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND " - + Attendees.ATTENDEE_EMAIL + " IN ("; - - public static final int DOES_NOT_REPEAT = 0; - public static final int REPEATS_DAILY = 1; - public static final int REPEATS_EVERY_WEEKDAY = 2; - public static final int REPEATS_WEEKLY_ON_DAY = 3; - public static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4; - public static final int REPEATS_MONTHLY_ON_DAY = 5; - public static final int REPEATS_YEARLY = 6; - public static final int REPEATS_CUSTOM = 7; - - protected static final int MODIFY_UNINITIALIZED = 0; - protected static final int MODIFY_SELECTED = 1; - protected static final int MODIFY_ALL_FOLLOWING = 2; - protected static final int MODIFY_ALL = 3; - - protected static final int DAY_IN_SECONDS = 24 * 60 * 60; - - private final AsyncQueryService mService; - - // This allows us to flag the event if something is wrong with it, right now - // if an uri is provided for an event that doesn't exist in the db. - protected boolean mEventOk = true; - - public static final int ATTENDEE_ID_NONE = -1; - public static final int[] ATTENDEE_VALUES = { - Attendees.ATTENDEE_STATUS_NONE, - Attendees.ATTENDEE_STATUS_ACCEPTED, - Attendees.ATTENDEE_STATUS_TENTATIVE, - Attendees.ATTENDEE_STATUS_DECLINED, - }; - - /** - * This is the symbolic name for the key used to pass in the boolean for - * creating all-day events that is part of the extra data of the intent. - * This is used only for creating new events and is set to true if the - * default for the new event should be an all-day event. - */ - public static final String EVENT_ALL_DAY = "allDay"; - - static final String[] CALENDARS_PROJECTION = new String[] { - Calendars._ID, // 0 - Calendars.CALENDAR_DISPLAY_NAME, // 1 - Calendars.OWNER_ACCOUNT, // 2 - Calendars.CALENDAR_COLOR, // 3 - Calendars.CAN_ORGANIZER_RESPOND, // 4 - Calendars.CALENDAR_ACCESS_LEVEL, // 5 - Calendars.VISIBLE, // 6 - Calendars.MAX_REMINDERS, // 7 - Calendars.ALLOWED_REMINDERS, // 8 - Calendars.ALLOWED_ATTENDEE_TYPES, // 9 - Calendars.ALLOWED_AVAILABILITY, // 10 - Calendars.ACCOUNT_NAME, // 11 - Calendars.ACCOUNT_TYPE, //12 - }; - static final int CALENDARS_INDEX_ID = 0; - static final int CALENDARS_INDEX_DISPLAY_NAME = 1; - static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; - static final int CALENDARS_INDEX_COLOR = 3; - static final int CALENDARS_INDEX_CAN_ORGANIZER_RESPOND = 4; - static final int CALENDARS_INDEX_ACCESS_LEVEL = 5; - static final int CALENDARS_INDEX_VISIBLE = 6; - static final int CALENDARS_INDEX_MAX_REMINDERS = 7; - static final int CALENDARS_INDEX_ALLOWED_REMINDERS = 8; - static final int CALENDARS_INDEX_ALLOWED_ATTENDEE_TYPES = 9; - static final int CALENDARS_INDEX_ALLOWED_AVAILABILITY = 10; - static final int CALENDARS_INDEX_ACCOUNT_NAME = 11; - static final int CALENDARS_INDEX_ACCOUNT_TYPE = 12; - - static final String CALENDARS_WHERE_WRITEABLE_VISIBLE = Calendars.CALENDAR_ACCESS_LEVEL + ">=" - + Calendars.CAL_ACCESS_CONTRIBUTOR + " AND " + Calendars.VISIBLE + "=1"; - - static final String CALENDARS_WHERE = Calendars._ID + "=?"; - - static final String[] COLORS_PROJECTION = new String[] { - Colors._ID, // 0 - Colors.ACCOUNT_NAME, - Colors.ACCOUNT_TYPE, - Colors.COLOR, // 1 - Colors.COLOR_KEY // 2 - }; - - static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE + - "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT; - - static final int COLORS_INDEX_ACCOUNT_NAME = 1; - static final int COLORS_INDEX_ACCOUNT_TYPE = 2; - static final int COLORS_INDEX_COLOR = 3; - static final int COLORS_INDEX_COLOR_KEY = 4; - - static final String[] ATTENDEES_PROJECTION = new String[] { - Attendees._ID, // 0 - Attendees.ATTENDEE_NAME, // 1 - Attendees.ATTENDEE_EMAIL, // 2 - Attendees.ATTENDEE_RELATIONSHIP, // 3 - Attendees.ATTENDEE_STATUS, // 4 - }; - static final int ATTENDEES_INDEX_ID = 0; - static final int ATTENDEES_INDEX_NAME = 1; - static final int ATTENDEES_INDEX_EMAIL = 2; - static final int ATTENDEES_INDEX_RELATIONSHIP = 3; - static final int ATTENDEES_INDEX_STATUS = 4; - static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND attendeeEmail IS NOT NULL"; - - public static class AttendeeItem { - public boolean mRemoved; - public Attendee mAttendee; - public Drawable mBadge; - public int mUpdateCounts; - public View mView; - public Uri mContactLookupUri; - - public AttendeeItem(Attendee attendee, Drawable badge) { - mAttendee = attendee; - mBadge = badge; - } - } - - public EditEventHelper(Context context) { - mService = ((AbstractCalendarActivity)context).getAsyncQueryService(); - } - - public EditEventHelper(Context context, CalendarEventModel model) { - this(context); - // TODO: Remove unnecessary constructor. - } - - /** - * Saves the event. Returns true if the event was successfully saved, false - * otherwise. - * - * @param model The event model to save - * @param originalModel A model of the original event if it exists - * @param modifyWhich For recurring events which type of series modification to use - * @return true if the event was successfully queued for saving - */ - public boolean saveEvent(CalendarEventModel model, CalendarEventModel originalModel, - int modifyWhich) { - boolean forceSaveReminders = false; - - if (DEBUG) { - Log.d(TAG, "Saving event model: " + model); - } - - if (!mEventOk) { - if (DEBUG) { - Log.w(TAG, "Event no longer exists. Event was not saved."); - } - return false; - } - - // It's a problem if we try to save a non-existent or invalid model or if we're - // modifying an existing event and we have the wrong original model - if (model == null) { - Log.e(TAG, "Attempted to save null model."); - return false; - } - if (!model.isValid()) { - Log.e(TAG, "Attempted to save invalid model."); - return false; - } - if (originalModel != null && !isSameEvent(model, originalModel)) { - Log.e(TAG, "Attempted to update existing event but models didn't refer to the same " - + "event."); - return false; - } - if (originalModel != null && model.isUnchanged(originalModel)) { - return false; - } - - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); - int eventIdIndex = -1; - - ContentValues values = getContentValuesFromModel(model); - - if (model.mUri != null && originalModel == null) { - Log.e(TAG, "Existing event but no originalModel provided. Aborting save."); - return false; - } - Uri uri = null; - if (model.mUri != null) { - uri = Uri.parse(model.mUri); - } - - // Update the "hasAlarm" field for the event - ArrayList<ReminderEntry> reminders = model.mReminders; - int len = reminders.size(); - values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0); - - if (uri == null) { - // Add hasAttendeeData for a new event - values.put(Events.HAS_ATTENDEE_DATA, 1); - values.put(Events.STATUS, Events.STATUS_CONFIRMED); - eventIdIndex = ops.size(); - ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( - Events.CONTENT_URI).withValues(values); - ops.add(b.build()); - forceSaveReminders = true; - - } else if (TextUtils.isEmpty(model.mRrule) && TextUtils.isEmpty(originalModel.mRrule)) { - // Simple update to a non-recurring event - checkTimeDependentFields(originalModel, model, values, modifyWhich); - ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); - - } else if (TextUtils.isEmpty(originalModel.mRrule)) { - // This event was changed from a non-repeating event to a - // repeating event. - ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); - - } else if (modifyWhich == MODIFY_SELECTED) { - // Modify contents of the current instance of repeating event - // Create a recurrence exception - long begin = model.mOriginalStart; - values.put(Events.ORIGINAL_SYNC_ID, originalModel.mSyncId); - values.put(Events.ORIGINAL_INSTANCE_TIME, begin); - boolean allDay = originalModel.mAllDay; - values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0); - values.put(Events.STATUS, originalModel.mEventStatus); - - eventIdIndex = ops.size(); - ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( - Events.CONTENT_URI).withValues(values); - ops.add(b.build()); - forceSaveReminders = true; - - } else if (modifyWhich == MODIFY_ALL_FOLLOWING) { - - if (TextUtils.isEmpty(model.mRrule)) { - // We've changed a recurring event to a non-recurring event. - // If the event we are editing is the first in the series, - // then delete the whole series. Otherwise, update the series - // to end at the new start time. - if (isFirstEventInSeries(model, originalModel)) { - ops.add(ContentProviderOperation.newDelete(uri).build()); - } else { - // Update the current repeating event to end at the new start time. We - // ignore the RRULE returned because the exception event doesn't want one. - updatePastEvents(ops, originalModel, model.mOriginalStart); - } - eventIdIndex = ops.size(); - values.put(Events.STATUS, originalModel.mEventStatus); - ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) - .build()); - } else { - if (isFirstEventInSeries(model, originalModel)) { - checkTimeDependentFields(originalModel, model, values, modifyWhich); - ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(uri) - .withValues(values); - ops.add(b.build()); - } else { - // We need to update the existing recurrence to end before the exception - // event starts. If the recurrence rule has a COUNT, we need to adjust - // that in the original and in the exception. This call rewrites the - // original event's recurrence rule (in "ops"), and returns a new rule - // for the exception. If the exception explicitly set a new rule, however, - // we don't want to overwrite it. - String newRrule = updatePastEvents(ops, originalModel, model.mOriginalStart); - if (model.mRrule.equals(originalModel.mRrule)) { - values.put(Events.RRULE, newRrule); - } - - // Create a new event with the user-modified fields - eventIdIndex = ops.size(); - values.put(Events.STATUS, originalModel.mEventStatus); - ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues( - values).build()); - } - } - forceSaveReminders = true; - - } else if (modifyWhich == MODIFY_ALL) { - - // Modify all instances of repeating event - if (TextUtils.isEmpty(model.mRrule)) { - // We've changed a recurring event to a non-recurring event. - // Delete the whole series and replace it with a new - // non-recurring event. - ops.add(ContentProviderOperation.newDelete(uri).build()); - - eventIdIndex = ops.size(); - ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) - .build()); - forceSaveReminders = true; - } else { - checkTimeDependentFields(originalModel, model, values, modifyWhich); - ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); - } - } - - // New Event or New Exception to an existing event - boolean newEvent = (eventIdIndex != -1); - ArrayList<ReminderEntry> originalReminders; - if (originalModel != null) { - originalReminders = originalModel.mReminders; - } else { - originalReminders = new ArrayList<ReminderEntry>(); - } - - if (newEvent) { - saveRemindersWithBackRef(ops, eventIdIndex, reminders, originalReminders, - forceSaveReminders); - } else if (uri != null) { - long eventId = ContentUris.parseId(uri); - saveReminders(ops, eventId, reminders, originalReminders, forceSaveReminders); - } - - ContentProviderOperation.Builder b; - boolean hasAttendeeData = model.mHasAttendeeData; - - if (hasAttendeeData && model.mOwnerAttendeeId == -1) { - // Organizer is not an attendee - - String ownerEmail = model.mOwnerAccount; - if (model.mAttendeesList.size() != 0 && Utils.isValidEmail(ownerEmail)) { - // Add organizer as attendee since we got some attendees - - values.clear(); - values.put(Attendees.ATTENDEE_EMAIL, ownerEmail); - values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); - values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); - - if (newEvent) { - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) - .withValues(values); - b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex); - } else { - values.put(Attendees.EVENT_ID, model.mId); - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) - .withValues(values); - } - ops.add(b.build()); - } - } else if (hasAttendeeData && - model.mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus && - model.mOwnerAttendeeId != -1) { - if (DEBUG) { - Log.d(TAG, "Setting attendee status to " + model.mSelfAttendeeStatus); - } - Uri attUri = ContentUris.withAppendedId(Attendees.CONTENT_URI, model.mOwnerAttendeeId); - - values.clear(); - values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus); - values.put(Attendees.EVENT_ID, model.mId); - b = ContentProviderOperation.newUpdate(attUri).withValues(values); - ops.add(b.build()); - } - - // TODO: is this the right test? this currently checks if this is - // a new event or an existing event. or is this a paranoia check? - if (hasAttendeeData && (newEvent || uri != null)) { - String attendees = model.getAttendeesString(); - String originalAttendeesString; - if (originalModel != null) { - originalAttendeesString = originalModel.getAttendeesString(); - } else { - originalAttendeesString = ""; - } - // Hit the content provider only if this is a new event or the user - // has changed it - if (newEvent || !TextUtils.equals(originalAttendeesString, attendees)) { - // figure out which attendees need to be added and which ones - // need to be deleted. use a linked hash set, so we maintain - // order (but also remove duplicates). - HashMap<String, Attendee> newAttendees = model.mAttendeesList; - LinkedList<String> removedAttendees = new LinkedList<String>(); - - // the eventId is only used if eventIdIndex is -1. - // TODO: clean up this code. - long eventId = uri != null ? ContentUris.parseId(uri) : -1; - - // only compute deltas if this is an existing event. - // new events (being inserted into the Events table) won't - // have any existing attendees. - if (!newEvent) { - removedAttendees.clear(); - HashMap<String, Attendee> originalAttendees = originalModel.mAttendeesList; - for (String originalEmail : originalAttendees.keySet()) { - if (newAttendees.containsKey(originalEmail)) { - // existing attendee. remove from new attendees set. - newAttendees.remove(originalEmail); - } else { - // no longer in attendees. mark as removed. - removedAttendees.add(originalEmail); - } - } - - // delete removed attendees if necessary - if (removedAttendees.size() > 0) { - b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI); - - String[] args = new String[removedAttendees.size() + 1]; - args[0] = Long.toString(eventId); - int i = 1; - StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX); - for (String removedAttendee : removedAttendees) { - if (i > 1) { - deleteWhere.append(","); - } - deleteWhere.append("?"); - args[i++] = removedAttendee; - } - deleteWhere.append(")"); - b.withSelection(deleteWhere.toString(), args); - ops.add(b.build()); - } - } - - if (newAttendees.size() > 0) { - // Insert the new attendees - for (Attendee attendee : newAttendees.values()) { - values.clear(); - values.put(Attendees.ATTENDEE_NAME, attendee.mName); - values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail); - values.put(Attendees.ATTENDEE_RELATIONSHIP, - Attendees.RELATIONSHIP_ATTENDEE); - values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); - - if (newEvent) { - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) - .withValues(values); - b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex); - } else { - values.put(Attendees.EVENT_ID, eventId); - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) - .withValues(values); - } - ops.add(b.build()); - } - } - } - } - - - mService.startBatch(mService.getNextToken(), null, android.provider.CalendarContract.AUTHORITY, ops, - Utils.UNDO_DELAY); - - return true; - } - - public static LinkedHashSet<Rfc822Token> getAddressesFromList(String list, - Rfc822Validator validator) { - LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>(); - Rfc822Tokenizer.tokenize(list, addresses); - if (validator == null) { - return addresses; - } - - // validate the emails, out of paranoia. they should already be - // validated on input, but drop any invalid emails just to be safe. - Iterator<Rfc822Token> addressIterator = addresses.iterator(); - while (addressIterator.hasNext()) { - Rfc822Token address = addressIterator.next(); - if (!validator.isValid(address.getAddress())) { - Log.v(TAG, "Dropping invalid attendee email address: " + address.getAddress()); - addressIterator.remove(); - } - } - return addresses; - } - - /** - * When we aren't given an explicit start time, we default to the next - * upcoming half hour. So, for example, 5:01 -> 5:30, 5:30 -> 6:00, etc. - * - * @return a UTC time in milliseconds representing the next upcoming half - * hour - */ - protected long constructDefaultStartTime(long now) { - Time defaultStart = new Time(); - defaultStart.set(now); - defaultStart.second = 0; - defaultStart.minute = 30; - long defaultStartMillis = defaultStart.toMillis(false); - if (now < defaultStartMillis) { - return defaultStartMillis; - } else { - return defaultStartMillis + 30 * DateUtils.MINUTE_IN_MILLIS; - } - } - - /** - * When we aren't given an explicit end time, we default to an hour after - * the start time. - * @param startTime the start time - * @return a default end time - */ - protected long constructDefaultEndTime(long startTime) { - return startTime + DateUtils.HOUR_IN_MILLIS; - } - - // TODO think about how useful this is. Probably check if our event has - // changed early on and either update all or nothing. Should still do the if - // MODIFY_ALL bit. - void checkTimeDependentFields(CalendarEventModel originalModel, CalendarEventModel model, - ContentValues values, int modifyWhich) { - long oldBegin = model.mOriginalStart; - long oldEnd = model.mOriginalEnd; - boolean oldAllDay = originalModel.mAllDay; - String oldRrule = originalModel.mRrule; - String oldTimezone = originalModel.mTimezone; - - long newBegin = model.mStart; - long newEnd = model.mEnd; - boolean newAllDay = model.mAllDay; - String newRrule = model.mRrule; - String newTimezone = model.mTimezone; - - // If none of the time-dependent fields changed, then remove them. - if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay - && TextUtils.equals(oldRrule, newRrule) - && TextUtils.equals(oldTimezone, newTimezone)) { - values.remove(Events.DTSTART); - values.remove(Events.DTEND); - values.remove(Events.DURATION); - values.remove(Events.ALL_DAY); - values.remove(Events.RRULE); - values.remove(Events.EVENT_TIMEZONE); - return; - } - - if (TextUtils.isEmpty(oldRrule) || TextUtils.isEmpty(newRrule)) { - return; - } - - // If we are modifying all events then we need to set DTSTART to the - // start time of the first event in the series, not the current - // date and time. If the start time of the event was changed - // (from, say, 3pm to 4pm), then we want to add the time difference - // to the start time of the first event in the series (the DTSTART - // value). If we are modifying one instance or all following instances, - // then we leave the DTSTART field alone. - if (modifyWhich == MODIFY_ALL) { - long oldStartMillis = originalModel.mStart; - if (oldBegin != newBegin) { - // The user changed the start time of this event - long offset = newBegin - oldBegin; - oldStartMillis += offset; - } - if (newAllDay) { - Time time = new Time(Time.TIMEZONE_UTC); - time.set(oldStartMillis); - time.hour = 0; - time.minute = 0; - time.second = 0; - oldStartMillis = time.toMillis(false); - } - values.put(Events.DTSTART, oldStartMillis); - } - } - - /** - * Prepares an update to the original event so it stops where the new series - * begins. When we update 'this and all following' events we need to change - * the original event to end before a new series starts. This creates an - * update to the old event's rrule to do that. - *<p> - * If the event's recurrence rule has a COUNT, we also need to reduce the count in the - * RRULE for the exception event. - * - * @param ops The list of operations to add the update to - * @param originalModel The original event that we're updating - * @param endTimeMillis The time before which the event must end (i.e. the start time of the - * exception event instance). - * @return A replacement exception recurrence rule. - */ - public String updatePastEvents(ArrayList<ContentProviderOperation> ops, - CalendarEventModel originalModel, long endTimeMillis) { - boolean origAllDay = originalModel.mAllDay; - String origRrule = originalModel.mRrule; - String newRrule = origRrule; - - EventRecurrence origRecurrence = new EventRecurrence(); - origRecurrence.parse(origRrule); - - // Get the start time of the first instance in the original recurrence. - long startTimeMillis = originalModel.mStart; - Time dtstart = new Time(); - dtstart.timezone = originalModel.mTimezone; - dtstart.set(startTimeMillis); - - ContentValues updateValues = new ContentValues(); - - if (origRecurrence.count > 0) { - /* - * Generate the full set of instances for this recurrence, from the first to the - * one just before endTimeMillis. The list should never be empty, because this method - * should not be called for the first instance. All we're really interested in is - * the *number* of instances found. - * - * TODO: the model assumes RRULE and ignores RDATE, EXRULE, and EXDATE. For the - * current environment this is reasonable, but that may not hold in the future. - * - * TODO: if COUNT is 1, should we convert the event to non-recurring? e.g. we - * do an "edit this and all future events" on the 2nd instances. - */ - RecurrenceSet recurSet = new RecurrenceSet(originalModel.mRrule, null, null, null); - RecurrenceProcessor recurProc = new RecurrenceProcessor(); - long[] recurrences; - try { - recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis); - } catch (DateException de) { - throw new RuntimeException(de); - } - - if (recurrences.length == 0) { - throw new RuntimeException("can't use this method on first instance"); - } - - EventRecurrence excepRecurrence = new EventRecurrence(); - excepRecurrence.parse(origRrule); // TODO: add+use a copy constructor instead - excepRecurrence.count -= recurrences.length; - newRrule = excepRecurrence.toString(); - - origRecurrence.count = recurrences.length; - - } else { - // 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. - Time untilTime = new Time(); - untilTime.timezone = Time.TIMEZONE_UTC; - - // Subtract one second from the old begin time to get the new - // "until" time. - untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis) - if (origAllDay) { - untilTime.hour = 0; - untilTime.minute = 0; - untilTime.second = 0; - untilTime.allDay = true; - untilTime.normalize(false); - - // This should no longer be necessary -- DTSTART should already be in the correct - // format for an all-day event. - dtstart.hour = 0; - dtstart.minute = 0; - dtstart.second = 0; - dtstart.allDay = true; - dtstart.timezone = Time.TIMEZONE_UTC; - } - origRecurrence.until = untilTime.format2445(); - } - - updateValues.put(Events.RRULE, origRecurrence.toString()); - updateValues.put(Events.DTSTART, dtstart.normalize(true)); - ContentProviderOperation.Builder b = - ContentProviderOperation.newUpdate(Uri.parse(originalModel.mUri)) - .withValues(updateValues); - ops.add(b.build()); - - return newRrule; - } - - /** - * Compares two models to ensure that they refer to the same event. This is - * a safety check to make sure an updated event model refers to the same - * event as the original model. If the original model is null then this is a - * new event or we're forcing an overwrite so we return true in that case. - * The important identifiers are the Calendar Id and the Event Id. - * - * @return - */ - public static boolean isSameEvent(CalendarEventModel model, CalendarEventModel originalModel) { - if (originalModel == null) { - return true; - } - - if (model.mCalendarId != originalModel.mCalendarId) { - return false; - } - if (model.mId != originalModel.mId) { - return false; - } - - return true; - } - - /** - * Saves the reminders, if they changed. Returns true if operations to - * update the database were added. - * - * @param ops the array of ContentProviderOperations - * @param eventId the id of the event whose reminders are being updated - * @param reminders the array of reminders set by the user - * @param originalReminders the original array of reminders - * @param forceSave if true, then save the reminders even if they didn't change - * @return true if operations to update the database were added - */ - public static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId, - ArrayList<ReminderEntry> reminders, ArrayList<ReminderEntry> originalReminders, - boolean forceSave) { - // If the reminders have not changed, then don't update the database - if (reminders.equals(originalReminders) && !forceSave) { - return false; - } - - // Delete all the existing reminders for this event - String where = Reminders.EVENT_ID + "=?"; - String[] args = new String[] {Long.toString(eventId)}; - ContentProviderOperation.Builder b = ContentProviderOperation - .newDelete(Reminders.CONTENT_URI); - b.withSelection(where, args); - ops.add(b.build()); - - ContentValues values = new ContentValues(); - int len = reminders.size(); - - // Insert the new reminders, if any - for (int i = 0; i < len; i++) { - ReminderEntry re = reminders.get(i); - - values.clear(); - values.put(Reminders.MINUTES, re.getMinutes()); - values.put(Reminders.METHOD, re.getMethod()); - values.put(Reminders.EVENT_ID, eventId); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); - ops.add(b.build()); - } - return true; - } - - /** - * Saves the reminders, if they changed. Returns true if operations to - * update the database were added. Uses a reference id since an id isn't - * created until the row is added. - * - * @param ops the array of ContentProviderOperations - * @param eventId the id of the event whose reminders are being updated - * @param reminderMinutes the array of reminders set by the user - * @param originalMinutes the original array of reminders - * @param forceSave if true, then save the reminders even if they didn't change - * @return true if operations to update the database were added - */ - public static boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops, - int eventIdIndex, ArrayList<ReminderEntry> reminders, - ArrayList<ReminderEntry> originalReminders, boolean forceSave) { - // If the reminders have not changed, then don't update the database - if (reminders.equals(originalReminders) && !forceSave) { - return false; - } - - // Delete all the existing reminders for this event - ContentProviderOperation.Builder b = ContentProviderOperation - .newDelete(Reminders.CONTENT_URI); - b.withSelection(Reminders.EVENT_ID + "=?", new String[1]); - b.withSelectionBackReference(0, eventIdIndex); - ops.add(b.build()); - - ContentValues values = new ContentValues(); - int len = reminders.size(); - - // Insert the new reminders, if any - for (int i = 0; i < len; i++) { - ReminderEntry re = reminders.get(i); - - values.clear(); - values.put(Reminders.MINUTES, re.getMinutes()); - values.put(Reminders.METHOD, re.getMethod()); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); - b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex); - ops.add(b.build()); - } - return true; - } - - // It's the first event in the series if the start time before being - // modified is the same as the original event's start time - static boolean isFirstEventInSeries(CalendarEventModel model, - CalendarEventModel originalModel) { - return model.mOriginalStart == originalModel.mStart; - } - - // Adds an rRule and duration to a set of content values - void addRecurrenceRule(ContentValues values, CalendarEventModel model) { - String rrule = model.mRrule; - - values.put(Events.RRULE, rrule); - long end = model.mEnd; - long start = model.mStart; - String duration = model.mDuration; - - boolean isAllDay = model.mAllDay; - if (end >= start) { - if (isAllDay) { - // if it's all day compute the duration in days - long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) - / DateUtils.DAY_IN_MILLIS; - duration = "P" + days + "D"; - } else { - // otherwise compute the duration in seconds - long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS; - duration = "P" + seconds + "S"; - } - } else if (TextUtils.isEmpty(duration)) { - - // If no good duration info exists assume the default - if (isAllDay) { - duration = "P1D"; - } else { - duration = "P3600S"; - } - } - // recurring events should have a duration and dtend set to null - values.put(Events.DURATION, duration); - values.put(Events.DTEND, (Long) null); - } - - /** - * Uses the recurrence selection and the model data to build an rrule and - * write it to the model. - * - * @param selection the type of rrule - * @param model The event to update - * @param weekStart the week start day, specified as java.util.Calendar - * constants - */ - static void updateRecurrenceRule(int selection, CalendarEventModel model, - int weekStart) { - // Make sure we don't have any leftover data from the previous setting - EventRecurrence eventRecurrence = new EventRecurrence(); - - if (selection == DOES_NOT_REPEAT) { - model.mRrule = null; - return; - } else if (selection == REPEATS_CUSTOM) { - // Keep custom recurrence as before. - return; - } else if (selection == REPEATS_DAILY) { - eventRecurrence.freq = EventRecurrence.DAILY; - } else if (selection == REPEATS_EVERY_WEEKDAY) { - eventRecurrence.freq = EventRecurrence.WEEKLY; - int dayCount = 5; - int[] byday = new int[dayCount]; - int[] bydayNum = new int[dayCount]; - - byday[0] = EventRecurrence.MO; - byday[1] = EventRecurrence.TU; - byday[2] = EventRecurrence.WE; - byday[3] = EventRecurrence.TH; - byday[4] = EventRecurrence.FR; - for (int day = 0; day < dayCount; day++) { - bydayNum[day] = 0; - } - - eventRecurrence.byday = byday; - eventRecurrence.bydayNum = bydayNum; - eventRecurrence.bydayCount = dayCount; - } else if (selection == REPEATS_WEEKLY_ON_DAY) { - eventRecurrence.freq = EventRecurrence.WEEKLY; - int[] days = new int[1]; - int dayCount = 1; - int[] dayNum = new int[dayCount]; - Time startTime = new Time(model.mTimezone); - startTime.set(model.mStart); - - days[0] = EventRecurrence.timeDay2Day(startTime.weekDay); - // not sure why this needs to be zero, but set it for now. - dayNum[0] = 0; - - eventRecurrence.byday = days; - eventRecurrence.bydayNum = dayNum; - eventRecurrence.bydayCount = dayCount; - } else if (selection == REPEATS_MONTHLY_ON_DAY) { - eventRecurrence.freq = EventRecurrence.MONTHLY; - eventRecurrence.bydayCount = 0; - eventRecurrence.bymonthdayCount = 1; - int[] bymonthday = new int[1]; - Time startTime = new Time(model.mTimezone); - startTime.set(model.mStart); - bymonthday[0] = startTime.monthDay; - eventRecurrence.bymonthday = bymonthday; - } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) { - eventRecurrence.freq = EventRecurrence.MONTHLY; - eventRecurrence.bydayCount = 1; - eventRecurrence.bymonthdayCount = 0; - - int[] byday = new int[1]; - int[] bydayNum = new int[1]; - Time startTime = new Time(model.mTimezone); - startTime.set(model.mStart); - // Compute the week number (for example, the "2nd" Monday) - int dayCount = 1 + ((startTime.monthDay - 1) / 7); - if (dayCount == 5) { - dayCount = -1; - } - bydayNum[0] = dayCount; - byday[0] = EventRecurrence.timeDay2Day(startTime.weekDay); - eventRecurrence.byday = byday; - eventRecurrence.bydayNum = bydayNum; - } else if (selection == REPEATS_YEARLY) { - eventRecurrence.freq = EventRecurrence.YEARLY; - } - - // Set the week start day. - eventRecurrence.wkst = EventRecurrence.calendarDay2Day(weekStart); - model.mRrule = eventRecurrence.toString(); - } - - /** - * Uses an event cursor to fill in the given model This method assumes the - * cursor used {@link #EVENT_PROJECTION} as it's query projection. It uses - * the cursor to fill in the given model with all the information available. - * - * @param model The model to fill in - * @param cursor An event cursor that used {@link #EVENT_PROJECTION} for the query - */ - public static void setModelFromCursor(CalendarEventModel model, Cursor cursor) { - if (model == null || cursor == null || cursor.getCount() != 1) { - Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query."); - return; - } - - model.clear(); - cursor.moveToFirst(); - - model.mId = cursor.getInt(EVENT_INDEX_ID); - model.mTitle = cursor.getString(EVENT_INDEX_TITLE); - model.mDescription = cursor.getString(EVENT_INDEX_DESCRIPTION); - model.mLocation = cursor.getString(EVENT_INDEX_EVENT_LOCATION); - model.mAllDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0; - model.mHasAlarm = cursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; - model.mCalendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID); - model.mStart = cursor.getLong(EVENT_INDEX_DTSTART); - String tz = cursor.getString(EVENT_INDEX_TIMEZONE); - if (!TextUtils.isEmpty(tz)) { - model.mTimezone = tz; - } - String rRule = cursor.getString(EVENT_INDEX_RRULE); - model.mRrule = rRule; - model.mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID); - model.mAvailability = cursor.getInt(EVENT_INDEX_AVAILABILITY); - int accessLevel = cursor.getInt(EVENT_INDEX_ACCESS_LEVEL); - model.mOwnerAccount = cursor.getString(EVENT_INDEX_OWNER_ACCOUNT); - model.mHasAttendeeData = cursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; - model.mOriginalSyncId = cursor.getString(EVENT_INDEX_ORIGINAL_SYNC_ID); - model.mOriginalId = cursor.getLong(EVENT_INDEX_ORIGINAL_ID); - model.mOrganizer = cursor.getString(EVENT_INDEX_ORGANIZER); - model.mIsOrganizer = model.mOwnerAccount.equalsIgnoreCase(model.mOrganizer); - model.mGuestsCanModify = cursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0; - - int rawEventColor; - if (cursor.isNull(EVENT_INDEX_EVENT_COLOR)) { - rawEventColor = cursor.getInt(EVENT_INDEX_CALENDAR_COLOR); - } else { - rawEventColor = cursor.getInt(EVENT_INDEX_EVENT_COLOR); - } - model.setEventColor(Utils.getDisplayColorFromColor(rawEventColor)); - - if (accessLevel > 0) { - // For now the array contains the values 0, 2, and 3. We subtract - // one to make it easier to handle in code as 0,1,2. - // Default (0), Private (1), Public (2) - accessLevel--; - } - model.mAccessLevel = accessLevel; - model.mEventStatus = cursor.getInt(EVENT_INDEX_EVENT_STATUS); - - boolean hasRRule = !TextUtils.isEmpty(rRule); - - // We expect only one of these, so ignore the other - if (hasRRule) { - model.mDuration = cursor.getString(EVENT_INDEX_DURATION); - } else { - model.mEnd = cursor.getLong(EVENT_INDEX_DTEND); - } - - model.mModelUpdatedWithEventCursor = true; - } - - /** - * Uses a calendar cursor to fill in the given model This method assumes the - * cursor used {@link #CALENDARS_PROJECTION} as it's query projection. It uses - * the cursor to fill in the given model with all the information available. - * - * @param model The model to fill in - * @param cursor An event cursor that used {@link #CALENDARS_PROJECTION} for the query - * @return returns true if model was updated with the info in the cursor. - */ - public static boolean setModelFromCalendarCursor(CalendarEventModel model, Cursor cursor) { - if (model == null || cursor == null) { - Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query."); - return false; - } - - if (model.mCalendarId == -1) { - return false; - } - - if (!model.mModelUpdatedWithEventCursor) { - Log.wtf(TAG, - "Can't update model with a Calendar cursor until it has seen an Event cursor."); - return false; - } - - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - if (model.mCalendarId != cursor.getInt(CALENDARS_INDEX_ID)) { - continue; - } - - model.mOrganizerCanRespond = cursor.getInt(CALENDARS_INDEX_CAN_ORGANIZER_RESPOND) != 0; - - model.mCalendarAccessLevel = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL); - model.mCalendarDisplayName = cursor.getString(CALENDARS_INDEX_DISPLAY_NAME); - model.setCalendarColor(Utils.getDisplayColorFromColor( - cursor.getInt(CALENDARS_INDEX_COLOR))); - - model.mCalendarAccountName = cursor.getString(CALENDARS_INDEX_ACCOUNT_NAME); - model.mCalendarAccountType = cursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE); - - model.mCalendarMaxReminders = cursor.getInt(CALENDARS_INDEX_MAX_REMINDERS); - model.mCalendarAllowedReminders = cursor.getString(CALENDARS_INDEX_ALLOWED_REMINDERS); - model.mCalendarAllowedAttendeeTypes = cursor - .getString(CALENDARS_INDEX_ALLOWED_ATTENDEE_TYPES); - model.mCalendarAllowedAvailability = cursor - .getString(CALENDARS_INDEX_ALLOWED_AVAILABILITY); - - return true; - } - return false; - } - - public static boolean canModifyEvent(CalendarEventModel model) { - return canModifyCalendar(model) - && (model.mIsOrganizer || model.mGuestsCanModify); - } - - public static boolean canModifyCalendar(CalendarEventModel model) { - return model.mCalendarAccessLevel >= Calendars.CAL_ACCESS_CONTRIBUTOR - || model.mCalendarId == -1; - } - - public static boolean canAddReminders(CalendarEventModel model) { - return model.mCalendarAccessLevel >= Calendars.CAL_ACCESS_READ; - } - - public static boolean canRespond(CalendarEventModel model) { - // For non-organizers, write permission to the calendar is sufficient. - // For organizers, the user needs a) write permission to the calendar - // AND b) ownerCanRespond == true AND c) attendee data exist - // (this means num of attendees > 1, the calendar owner's and others). - // Note that mAttendeeList omits the organizer. - - // (there are more cases involved to be 100% accurate, such as - // paying attention to whether or not an attendee status was - // included in the feed, but we're currently omitting those corner cases - // for simplicity). - - if (!canModifyCalendar(model)) { - return false; - } - - if (!model.mIsOrganizer) { - return true; - } - - if (!model.mOrganizerCanRespond) { - return false; - } - - // This means we don't have the attendees data so we can't send - // the list of attendees and the status back to the server - if (model.mHasAttendeeData && model.mAttendeesList.size() == 0) { - return false; - } - - return true; - } - - /** - * Goes through an event model and fills in content values for saving. This - * method will perform the initial collection of values from the model and - * put them into a set of ContentValues. It performs some basic work such as - * fixing the time on allDay events and choosing whether to use an rrule or - * dtend. - * - * @param model The complete model of the event you want to save - * @return values - */ - ContentValues getContentValuesFromModel(CalendarEventModel model) { - String title = model.mTitle; - boolean isAllDay = model.mAllDay; - String rrule = model.mRrule; - String timezone = model.mTimezone; - if (timezone == null) { - timezone = TimeZone.getDefault().getID(); - } - Time startTime = new Time(timezone); - Time endTime = new Time(timezone); - - startTime.set(model.mStart); - endTime.set(model.mEnd); - offsetStartTimeIfNecessary(startTime, endTime, rrule, model); - - ContentValues values = new ContentValues(); - - long startMillis; - long endMillis; - long calendarId = model.mCalendarId; - if (isAllDay) { - // Reset start and end time, ensure at least 1 day duration, and set - // the timezone to UTC, as required for all-day events. - timezone = Time.TIMEZONE_UTC; - startTime.hour = 0; - startTime.minute = 0; - startTime.second = 0; - startTime.timezone = timezone; - startMillis = startTime.normalize(true); - - endTime.hour = 0; - endTime.minute = 0; - endTime.second = 0; - endTime.timezone = timezone; - endMillis = endTime.normalize(true); - if (endMillis < startMillis + DateUtils.DAY_IN_MILLIS) { - // EditEventView#fillModelFromUI() should treat this case, but we want to ensure - // the condition anyway. - endMillis = startMillis + DateUtils.DAY_IN_MILLIS; - } - } else { - startMillis = startTime.toMillis(true); - endMillis = endTime.toMillis(true); - } - - values.put(Events.CALENDAR_ID, calendarId); - values.put(Events.EVENT_TIMEZONE, timezone); - values.put(Events.TITLE, title); - values.put(Events.ALL_DAY, isAllDay ? 1 : 0); - values.put(Events.DTSTART, startMillis); - values.put(Events.RRULE, rrule); - if (!TextUtils.isEmpty(rrule)) { - addRecurrenceRule(values, model); - } else { - values.put(Events.DURATION, (String) null); - values.put(Events.DTEND, endMillis); - } - if (model.mDescription != null) { - values.put(Events.DESCRIPTION, model.mDescription.trim()); - } else { - values.put(Events.DESCRIPTION, (String) null); - } - if (model.mLocation != null) { - values.put(Events.EVENT_LOCATION, model.mLocation.trim()); - } else { - values.put(Events.EVENT_LOCATION, (String) null); - } - values.put(Events.AVAILABILITY, model.mAvailability); - values.put(Events.HAS_ATTENDEE_DATA, model.mHasAttendeeData ? 1 : 0); - - int accessLevel = model.mAccessLevel; - if (accessLevel > 0) { - // For now the array contains the values 0, 2, and 3. We add one to match. - // Default (0), Private (2), Public (3) - accessLevel++; - } - values.put(Events.ACCESS_LEVEL, accessLevel); - values.put(Events.STATUS, model.mEventStatus); - if (model.isEventColorInitialized()) { - if (model.getEventColor() == model.getCalendarColor()) { - values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR); - } else { - values.put(Events.EVENT_COLOR_KEY, model.getEventColorKey()); - } - } - return values; - } - - /** - * If the recurrence rule is such that the event start date doesn't actually fall in one of the - * recurrences, then push the start date up to the first actual instance of the event. - */ - private void offsetStartTimeIfNecessary(Time startTime, Time endTime, String rrule, - CalendarEventModel model) { - if (rrule == null || rrule.isEmpty()) { - // No need to waste any time with the parsing if the rule is empty. - return; - } - - mEventRecurrence.parse(rrule); - // Check if we meet the specific special case. It has to: - // * be weekly - // * not recur on the same day of the week that the startTime falls on - // In this case, we'll need to push the start time to fall on the first day of the week - // that is part of the recurrence. - if (mEventRecurrence.freq != EventRecurrence.WEEKLY) { - // Not weekly so nothing to worry about. - return; - } - if (mEventRecurrence.byday == null || - mEventRecurrence.byday.length > mEventRecurrence.bydayCount) { - // This shouldn't happen, but just in case something is weird about the recurrence. - return; - } - - // Start to figure out what the nearest weekday is. - int closestWeekday = Integer.MAX_VALUE; - int weekstart = EventRecurrence.day2TimeDay(mEventRecurrence.wkst); - int startDay = startTime.weekDay; - for (int i = 0; i < mEventRecurrence.bydayCount; i++) { - int day = EventRecurrence.day2TimeDay(mEventRecurrence.byday[i]); - if (day == startDay) { - // Our start day is one of the recurring days, so we're good. - return; - } - - if (day < weekstart) { - // Let's not make any assumptions about what weekstart can be. - day += 7; - } - // We either want the earliest day that is later in the week than startDay ... - if (day > startDay && (day < closestWeekday || closestWeekday < startDay)) { - closestWeekday = day; - } - // ... or if there are no days later than startDay, we want the earliest day that is - // earlier in the week than startDay. - if (closestWeekday == Integer.MAX_VALUE || closestWeekday < startDay) { - // We haven't found a day that's later in the week than startDay yet. - if (day < closestWeekday) { - closestWeekday = day; - } - } - } - - // We're here, so unfortunately our event's start day is not included in the days of - // the week of the recurrence. To save this event correctly we'll need to push the start - // date to the closest weekday that *is* part of the recurrence. - if (closestWeekday < startDay) { - closestWeekday += 7; - } - int daysOffset = closestWeekday - startDay; - startTime.monthDay += daysOffset; - endTime.monthDay += daysOffset; - long newStartTime = startTime.normalize(true); - long newEndTime = endTime.normalize(true); - - // Later we'll actually be using the values from the model rather than the startTime - // and endTime themselves, so we need to make these changes to the model as well. - model.mStart = newStartTime; - model.mEnd = newEndTime; - } - - /** - * Takes an e-mail address and returns the domain (everything after the last @) - */ - public static String extractDomain(String email) { - int separator = email.lastIndexOf('@'); - if (separator != -1 && ++separator < email.length()) { - return email.substring(separator); - } - return null; - } - - public interface EditDoneRunnable extends Runnable { - public void setDoneCode(int code); - } -} diff --git a/src/com/android/calendar/event/EditEventView.java b/src/com/android/calendar/event/EditEventView.java deleted file mode 100644 index a06774b8..00000000 --- a/src/com/android/calendar/event/EditEventView.java +++ /dev/null @@ -1,1852 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.app.ProgressDialog; -import android.app.Service; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.provider.Settings; -import android.text.InputFilter; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.text.util.Rfc822Tokenizer; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.LinearLayout; -import android.widget.MultiAutoCompleteTextView; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.ResourceCursorAdapter; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; - -import com.android.calendar.CalendarEventModel; -import com.android.calendar.CalendarEventModel.Attendee; -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.EmailAddressAdapter; -import com.android.calendar.EventInfoFragment; -import com.android.calendar.EventRecurrenceFormatter; -import com.android.calendar.GeneralPreferences; -import com.android.calendar.R; -import com.android.calendar.RecipientAdapter; -import com.android.calendar.Utils; -import com.android.calendar.event.EditEventHelper.EditDoneRunnable; -import com.android.calendar.recurrencepicker.RecurrencePickerDialog; -import com.android.calendarcommon2.EventRecurrence; -import com.android.common.Rfc822InputFilter; -import com.android.common.Rfc822Validator; -import com.android.datetimepicker.date.DatePickerDialog; -import com.android.datetimepicker.date.DatePickerDialog.OnDateSetListener; -import com.android.datetimepicker.time.RadialPickerLayout; -import com.android.datetimepicker.time.TimePickerDialog; -import com.android.datetimepicker.time.TimePickerDialog.OnTimeSetListener; -import com.android.ex.chips.AccountSpecifier; -import com.android.ex.chips.BaseRecipientAdapter; -import com.android.ex.chips.ChipsUtil; -import com.android.ex.chips.RecipientEditTextView; -import com.android.timezonepicker.TimeZoneInfo; -import com.android.timezonepicker.TimeZonePickerDialog; -import com.android.timezonepicker.TimeZonePickerUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Formatter; -import java.util.HashMap; -import java.util.Locale; -import java.util.TimeZone; - -public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener, - DialogInterface.OnClickListener, OnItemSelectedListener, - RecurrencePickerDialog.OnRecurrenceSetListener, - TimeZonePickerDialog.OnTimeZoneSetListener { - - private static final String TAG = "EditEvent"; - private static final String GOOGLE_SECONDARY_CALENDAR = "calendar.google.com"; - private static final String PERIOD_SPACE = ". "; - - private static final String FRAG_TAG_DATE_PICKER = "datePickerDialogFragment"; - private static final String FRAG_TAG_TIME_PICKER = "timePickerDialogFragment"; - private static final String FRAG_TAG_TIME_ZONE_PICKER = "timeZonePickerDialogFragment"; - private static final String FRAG_TAG_RECUR_PICKER = "recurrencePickerDialogFragment"; - - ArrayList<View> mEditOnlyList = new ArrayList<View>(); - ArrayList<View> mEditViewList = new ArrayList<View>(); - ArrayList<View> mViewOnlyList = new ArrayList<View>(); - TextView mLoadingMessage; - ScrollView mScrollView; - Button mStartDateButton; - Button mEndDateButton; - Button mStartTimeButton; - Button mEndTimeButton; - Button mTimezoneButton; - View mColorPickerNewEvent; - View mColorPickerExistingEvent; - OnClickListener mChangeColorOnClickListener; - View mTimezoneRow; - TextView mStartTimeHome; - TextView mStartDateHome; - TextView mEndTimeHome; - TextView mEndDateHome; - CheckBox mAllDayCheckBox; - Spinner mCalendarsSpinner; - Button mRruleButton; - Spinner mAvailabilitySpinner; - Spinner mAccessLevelSpinner; - RadioGroup mResponseRadioGroup; - TextView mTitleTextView; - AutoCompleteTextView mLocationTextView; - EventLocationAdapter mLocationAdapter; - TextView mDescriptionTextView; - TextView mWhenView; - TextView mTimezoneTextView; - TextView mTimezoneLabel; - LinearLayout mRemindersContainer; - MultiAutoCompleteTextView mAttendeesList; - View mCalendarSelectorGroup; - View mCalendarSelectorWrapper; - View mCalendarStaticGroup; - View mLocationGroup; - View mDescriptionGroup; - View mRemindersGroup; - View mResponseGroup; - View mOrganizerGroup; - View mAttendeesGroup; - View mStartHomeGroup; - View mEndHomeGroup; - - private int[] mOriginalPadding = new int[4]; - - public boolean mIsMultipane; - private ProgressDialog mLoadingCalendarsDialog; - private AlertDialog mNoCalendarsDialog; - private DialogFragment mTimezoneDialog; - private Activity mActivity; - private EditDoneRunnable mDone; - private View mView; - private CalendarEventModel mModel; - private Cursor mCalendarsCursor; - private AccountSpecifier mAddressAdapter; - private Rfc822Validator mEmailValidator; - - public boolean mTimeSelectedWasStartTime; - public boolean mDateSelectedWasStartDate; - private TimePickerDialog mStartTimePickerDialog; - private TimePickerDialog mEndTimePickerDialog; - private DatePickerDialog mDatePickerDialog; - - /** - * Contents of the "minutes" spinner. This has default values from the XML file, augmented - * with any additional values that were already associated with the event. - */ - private ArrayList<Integer> mReminderMinuteValues; - private ArrayList<String> mReminderMinuteLabels; - - /** - * Contents of the "methods" spinner. The "values" list specifies the method constant - * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels. Any methods that - * aren't allowed by the Calendar will be removed. - */ - private ArrayList<Integer> mReminderMethodValues; - private ArrayList<String> mReminderMethodLabels; - - /** - * Contents of the "availability" spinner. The "values" list specifies the - * type constant (e.g. {@link Events#AVAILABILITY_BUSY}) associated with the - * labels. Any types that aren't allowed by the Calendar will be removed. - */ - private ArrayList<Integer> mAvailabilityValues; - private ArrayList<String> mAvailabilityLabels; - private ArrayList<String> mOriginalAvailabilityLabels; - private ArrayAdapter<String> mAvailabilityAdapter; - private boolean mAvailabilityExplicitlySet; - private boolean mAllDayChangingAvailability; - private int mAvailabilityCurrentlySelected; - - private int mDefaultReminderMinutes; - - private boolean mSaveAfterQueryComplete = false; - - private TimeZonePickerUtils mTzPickerUtils; - private Time mStartTime; - private Time mEndTime; - private String mTimezone; - private boolean mAllDay = false; - private int mModification = EditEventHelper.MODIFY_UNINITIALIZED; - - private EventRecurrence mEventRecurrence = new EventRecurrence(); - - private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0); - private ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>(); - private String mRrule; - - private static StringBuilder mSB = new StringBuilder(50); - private static Formatter mF = new Formatter(mSB, Locale.getDefault()); - - /* This class is used to update the time buttons. */ - private class TimeListener implements OnTimeSetListener { - private View mView; - - public TimeListener(View view) { - mView = view; - } - - @Override - public void onTimeSet(RadialPickerLayout 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; - - // Update tz in case the start time switched from/to DLS - populateTimezone(startMillis); - } else { - // The end time was changed. - startMillis = startTime.toMillis(true); - endTime.hour = hourOfDay; - endTime.minute = minute; - - // Move to the start time if the end time is before the start - // time. - if (endTime.before(startTime)) { - endTime.monthDay = startTime.monthDay + 1; - } - // Call populateTimezone if we support end time zone as well - } - - endMillis = endTime.normalize(true); - - setDate(mEndDateButton, endMillis); - setTime(mStartTimeButton, startMillis); - setTime(mEndTimeButton, endMillis); - updateHomeTime(); - } - } - - private class TimeClickListener implements View.OnClickListener { - private Time mTime; - - public TimeClickListener(Time time) { - mTime = time; - } - - @Override - public void onClick(View v) { - - TimePickerDialog dialog; - if (v == mStartTimeButton) { - mTimeSelectedWasStartTime = true; - if (mStartTimePickerDialog == null) { - mStartTimePickerDialog = TimePickerDialog.newInstance(new TimeListener(v), - mTime.hour, mTime.minute, DateFormat.is24HourFormat(mActivity)); - } else { - mStartTimePickerDialog.setStartTime(mTime.hour, mTime.minute); - } - dialog = mStartTimePickerDialog; - } else { - mTimeSelectedWasStartTime = false; - if (mEndTimePickerDialog == null) { - mEndTimePickerDialog = TimePickerDialog.newInstance(new TimeListener(v), - mTime.hour, mTime.minute, DateFormat.is24HourFormat(mActivity)); - } else { - mEndTimePickerDialog.setStartTime(mTime.hour, mTime.minute); - } - dialog = mEndTimePickerDialog; - - } - - final FragmentManager fm = mActivity.getFragmentManager(); - fm.executePendingTransactions(); - - if (dialog != null && !dialog.isAdded()) { - dialog.show(fm, FRAG_TAG_TIME_PICKER); - } - } - } - - private class DateListener implements OnDateSetListener { - View mView; - - public DateListener(View view) { - mView = view; - } - - @Override - public void onDateSet(DatePickerDialog view, int year, int month, int monthDay) { - Log.d(TAG, "onDateSet: " + year + " " + month + " " + 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(); - - // Update tz in case the start time switched from/to DLS - populateTimezone(startMillis); - } 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; - } - // Call populateTimezone if we support end time zone as well - } - - setDate(mStartDateButton, startMillis); - setDate(mEndDateButton, endMillis); - setTime(mEndTimeButton, endMillis); // In case end time had to be - // reset - updateHomeTime(); - } - } - - // Fills in the date and time fields - private void populateWhen() { - long startMillis = mStartTime.toMillis(false /* use isDst */); - long endMillis = mEndTime.toMillis(false /* use isDst */); - setDate(mStartDateButton, startMillis); - setDate(mEndDateButton, endMillis); - - setTime(mStartTimeButton, startMillis); - setTime(mEndTimeButton, endMillis); - - mStartDateButton.setOnClickListener(new DateClickListener(mStartTime)); - mEndDateButton.setOnClickListener(new DateClickListener(mEndTime)); - - mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime)); - mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime)); - } - - // Implements OnTimeZoneSetListener - @Override - public void onTimeZoneSet(TimeZoneInfo tzi) { - setTimezone(tzi.mTzId); - updateHomeTime(); - } - - private void setTimezone(String timeZone) { - mTimezone = timeZone; - mStartTime.timezone = mTimezone; - long timeMillis = mStartTime.normalize(true); - mEndTime.timezone = mTimezone; - mEndTime.normalize(true); - - populateTimezone(timeMillis); - } - - private void populateTimezone(long eventStartTime) { - if (mTzPickerUtils == null) { - mTzPickerUtils = new TimeZonePickerUtils(mActivity); - } - CharSequence displayName = - mTzPickerUtils.getGmtDisplayName(mActivity, mTimezone, eventStartTime, true); - - mTimezoneTextView.setText(displayName); - mTimezoneButton.setText(displayName); - } - - private void showTimezoneDialog() { - Bundle b = new Bundle(); - b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, mStartTime.toMillis(false)); - b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, mTimezone); - - FragmentManager fm = mActivity.getFragmentManager(); - TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm - .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER); - if (tzpd != null) { - tzpd.dismiss(); - } - tzpd = new TimeZonePickerDialog(); - tzpd.setArguments(b); - tzpd.setOnTimeZoneSetListener(EditEventView.this); - tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER); - } - - private void populateRepeats() { - Resources r = mActivity.getResources(); - String repeatString; - boolean enabled; - if (!TextUtils.isEmpty(mRrule)) { - repeatString = EventRecurrenceFormatter.getRepeatString(mActivity, r, - mEventRecurrence, true); - - if (repeatString == null) { - repeatString = r.getString(R.string.custom); - Log.e(TAG, "Can't generate display string for " + mRrule); - enabled = false; - } else { - // TODO Should give option to clear/reset rrule - enabled = RecurrencePickerDialog.canHandleRecurrenceRule(mEventRecurrence); - if (!enabled) { - Log.e(TAG, "UI can't handle " + mRrule); - } - } - } else { - repeatString = r.getString(R.string.does_not_repeat); - enabled = true; - } - - mRruleButton.setText(repeatString); - - // Don't allow the user to make exceptions recurring events. - if (mModel.mOriginalSyncId != null) { - enabled = false; - } - mRruleButton.setOnClickListener(this); - mRruleButton.setEnabled(enabled); - } - - private class DateClickListener implements View.OnClickListener { - private Time mTime; - - public DateClickListener(Time time) { - mTime = time; - } - - @Override - public void onClick(View v) { - if (!mView.hasWindowFocus()) { - // Don't do anything if the activity if paused. Since Activity doesn't - // have a built in way to do this, we would have to implement one ourselves and - // either cast our Activity to a specialized activity base class or implement some - // generic interface that tells us if an activity is paused. hasWindowFocus() is - // close enough if not quite perfect. - return; - } - if (v == mStartDateButton) { - mDateSelectedWasStartDate = true; - } else { - mDateSelectedWasStartDate = false; - } - - final DateListener listener = new DateListener(v); - if (mDatePickerDialog != null) { - mDatePickerDialog.dismiss(); - } - mDatePickerDialog = DatePickerDialog.newInstance(listener, - mTime.year, mTime.month, mTime.monthDay); - mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(mActivity)); - mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX); - mDatePickerDialog.show(mActivity.getFragmentManager(), FRAG_TAG_DATE_PICKER); - } - } - - public static class CalendarsAdapter extends ResourceCursorAdapter { - public CalendarsAdapter(Context context, int resourceId, Cursor c) { - super(context, resourceId, c); - setDropDownViewResource(R.layout.calendars_dropdown_item); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - View colorBar = view.findViewById(R.id.color); - int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); - int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); - int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); - if (colorBar != null) { - colorBar.setBackgroundColor(Utils.getDisplayColorFromColor(cursor - .getInt(colorColumn))); - } - - TextView name = (TextView) view.findViewById(R.id.calendar_name); - if (name != null) { - String displayName = cursor.getString(nameColumn); - name.setText(displayName); - - TextView accountName = (TextView) view.findViewById(R.id.account_name); - if (accountName != null) { - accountName.setText(cursor.getString(ownerColumn)); - accountName.setVisibility(TextView.VISIBLE); - } - } - } - } - - /** - * Does prep steps for saving a calendar event. - * - * This triggers a parse of the attendees list and checks if the event is - * ready to be saved. An event is ready to be saved so long as a model - * exists and has a calendar it can be associated with, either because it's - * an existing event or we've finished querying. - * - * @return false if there is no model or no calendar had been loaded yet, - * true otherwise. - */ - public boolean prepareForSave() { - if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { - return false; - } - return fillModelFromUI(); - } - - public boolean fillModelFromReadOnlyUi() { - if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { - return false; - } - mModel.mReminders = EventViewUtils.reminderItemsToReminders( - mReminderItems, mReminderMinuteValues, mReminderMethodValues); - mModel.mReminders.addAll(mUnsupportedReminders); - mModel.normalizeReminders(); - int status = EventInfoFragment.getResponseFromButtonId( - mResponseRadioGroup.getCheckedRadioButtonId()); - if (status != Attendees.ATTENDEE_STATUS_NONE) { - mModel.mSelfAttendeeStatus = status; - } - return true; - } - - // This is called if the user clicks on one of the buttons: "Save", - // "Discard", or "Delete". This is also called if the user clicks - // on the "remove reminder" button. - @Override - public void onClick(View view) { - if (view == mRruleButton) { - Bundle b = new Bundle(); - b.putLong(RecurrencePickerDialog.BUNDLE_START_TIME_MILLIS, - mStartTime.toMillis(false)); - b.putString(RecurrencePickerDialog.BUNDLE_TIME_ZONE, mStartTime.timezone); - - // TODO may be more efficient to serialize and pass in EventRecurrence - b.putString(RecurrencePickerDialog.BUNDLE_RRULE, mRrule); - - FragmentManager fm = mActivity.getFragmentManager(); - RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm - .findFragmentByTag(FRAG_TAG_RECUR_PICKER); - if (rpd != null) { - rpd.dismiss(); - } - rpd = new RecurrencePickerDialog(); - rpd.setArguments(b); - rpd.setOnRecurrenceSetListener(EditEventView.this); - rpd.show(fm, FRAG_TAG_RECUR_PICKER); - return; - } - - // This must be a click on one of the "remove reminder" buttons - LinearLayout reminderItem = (LinearLayout) view.getParent(); - LinearLayout parent = (LinearLayout) reminderItem.getParent(); - parent.removeView(reminderItem); - mReminderItems.remove(reminderItem); - updateRemindersVisibility(mReminderItems.size()); - EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); - } - - @Override - public void onRecurrenceSet(String rrule) { - Log.d(TAG, "Old rrule:" + mRrule); - Log.d(TAG, "New rrule:" + rrule); - mRrule = rrule; - if (mRrule != null) { - mEventRecurrence.parse(mRrule); - } - populateRepeats(); - } - - // This is called if the user cancels the "No calendars" dialog. - // The "No calendars" dialog is shown if there are no syncable calendars. - @Override - public void onCancel(DialogInterface dialog) { - if (dialog == mLoadingCalendarsDialog) { - mLoadingCalendarsDialog = null; - mSaveAfterQueryComplete = false; - } else if (dialog == mNoCalendarsDialog) { - mDone.setDoneCode(Utils.DONE_REVERT); - mDone.run(); - return; - } - } - - // This is called if the user clicks on a dialog button. - @Override - public void onClick(DialogInterface dialog, int which) { - if (dialog == mNoCalendarsDialog) { - mDone.setDoneCode(Utils.DONE_REVERT); - mDone.run(); - if (which == DialogInterface.BUTTON_POSITIVE) { - Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); - final String[] array = {"com.android.calendar"}; - nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); - nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - mActivity.startActivity(nextIntent); - } - } - } - - // Goes through the UI elements and updates the model as necessary - private boolean fillModelFromUI() { - if (mModel == null) { - return false; - } - mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems, - mReminderMinuteValues, mReminderMethodValues); - mModel.mReminders.addAll(mUnsupportedReminders); - mModel.normalizeReminders(); - mModel.mHasAlarm = mReminderItems.size() > 0; - mModel.mTitle = mTitleTextView.getText().toString(); - mModel.mAllDay = mAllDayCheckBox.isChecked(); - mModel.mLocation = mLocationTextView.getText().toString(); - mModel.mDescription = mDescriptionTextView.getText().toString(); - if (TextUtils.isEmpty(mModel.mLocation)) { - mModel.mLocation = null; - } - if (TextUtils.isEmpty(mModel.mDescription)) { - mModel.mDescription = null; - } - - int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup - .getCheckedRadioButtonId()); - if (status != Attendees.ATTENDEE_STATUS_NONE) { - mModel.mSelfAttendeeStatus = status; - } - - if (mAttendeesList != null) { - mEmailValidator.setRemoveInvalid(true); - mAttendeesList.performValidation(); - mModel.mAttendeesList.clear(); - mModel.addAttendees(mAttendeesList.getText().toString(), mEmailValidator); - mEmailValidator.setRemoveInvalid(false); - } - - // If this was a new event we need to fill in the Calendar information - if (mModel.mUri == null) { - mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId(); - int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition(); - if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) { - String defaultCalendar = mCalendarsCursor.getString( - EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); - Utils.setSharedPreference( - mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar); - mModel.mOwnerAccount = defaultCalendar; - mModel.mOrganizer = defaultCalendar; - mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID); - } - } - - if (mModel.mAllDay) { - // Reset start and end time, increment the monthDay by 1, and set - // the timezone to UTC, as required for all-day events. - mTimezone = Time.TIMEZONE_UTC; - mStartTime.hour = 0; - mStartTime.minute = 0; - mStartTime.second = 0; - mStartTime.timezone = mTimezone; - mModel.mStart = mStartTime.normalize(true); - - mEndTime.hour = 0; - mEndTime.minute = 0; - mEndTime.second = 0; - mEndTime.timezone = mTimezone; - // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time - // should be Y + 1 (Oct.30). - final long normalizedEndTimeMillis = - mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS; - if (normalizedEndTimeMillis < mModel.mStart) { - // mEnd should be midnight of the next day of mStart. - mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS; - } else { - mModel.mEnd = normalizedEndTimeMillis; - } - } else { - mStartTime.timezone = mTimezone; - mEndTime.timezone = mTimezone; - mModel.mStart = mStartTime.toMillis(true); - mModel.mEnd = mEndTime.toMillis(true); - } - mModel.mTimezone = mTimezone; - mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition(); - // TODO set correct availability value - mModel.mAvailability = mAvailabilityValues.get(mAvailabilitySpinner - .getSelectedItemPosition()); - - // rrrule - // If we're making an exception we don't want it to be a repeating - // event. - if (mModification == EditEventHelper.MODIFY_SELECTED) { - mModel.mRrule = null; - } else { - mModel.mRrule = mRrule; - } - - return true; - } - - public EditEventView(Activity activity, View view, EditDoneRunnable done, - boolean timeSelectedWasStartTime, boolean dateSelectedWasStartDate) { - - mActivity = activity; - mView = view; - mDone = done; - - // cache top level view elements - mLoadingMessage = (TextView) view.findViewById(R.id.loading_message); - mScrollView = (ScrollView) view.findViewById(R.id.scroll_view); - - // cache all the widgets - mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner); - mTitleTextView = (TextView) view.findViewById(R.id.title); - mLocationTextView = (AutoCompleteTextView) view.findViewById(R.id.location); - mDescriptionTextView = (TextView) view.findViewById(R.id.description); - mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label); - mStartDateButton = (Button) view.findViewById(R.id.start_date); - mEndDateButton = (Button) view.findViewById(R.id.end_date); - mWhenView = (TextView) mView.findViewById(R.id.when); - mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView); - mStartTimeButton = (Button) view.findViewById(R.id.start_time); - mEndTimeButton = (Button) view.findViewById(R.id.end_time); - mTimezoneButton = (Button) view.findViewById(R.id.timezone_button); - mTimezoneButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showTimezoneDialog(); - } - }); - mTimezoneRow = view.findViewById(R.id.timezone_button_row); - mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz); - mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz); - mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz); - mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz); - mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day); - mRruleButton = (Button) view.findViewById(R.id.rrule); - mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability); - mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility); - mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group); - mCalendarSelectorWrapper = view.findViewById(R.id.calendar_selector_wrapper); - mCalendarStaticGroup = view.findViewById(R.id.calendar_group); - mRemindersGroup = view.findViewById(R.id.reminders_row); - mResponseGroup = view.findViewById(R.id.response_row); - mOrganizerGroup = view.findViewById(R.id.organizer_row); - mAttendeesGroup = view.findViewById(R.id.add_attendees_row); - mLocationGroup = view.findViewById(R.id.where_row); - mDescriptionGroup = view.findViewById(R.id.description_row); - mStartHomeGroup = view.findViewById(R.id.from_row_home_tz); - mEndHomeGroup = view.findViewById(R.id.to_row_home_tz); - mAttendeesList = (MultiAutoCompleteTextView) view.findViewById(R.id.attendees); - - mColorPickerNewEvent = view.findViewById(R.id.change_color_new_event); - mColorPickerExistingEvent = view.findViewById(R.id.change_color_existing_event); - - mTitleTextView.setTag(mTitleTextView.getBackground()); - mLocationTextView.setTag(mLocationTextView.getBackground()); - mLocationAdapter = new EventLocationAdapter(activity); - mLocationTextView.setAdapter(mLocationAdapter); - mLocationTextView.setOnEditorActionListener(new OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - // Dismiss the suggestions dropdown. Return false so the other - // side effects still occur (soft keyboard going away, etc.). - mLocationTextView.dismissDropDown(); - } - return false; - } - }); - - mAvailabilityExplicitlySet = false; - mAllDayChangingAvailability = false; - mAvailabilityCurrentlySelected = -1; - mAvailabilitySpinner.setOnItemSelectedListener( - new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, - View view, int position, long id) { - // The spinner's onItemSelected gets called while it is being - // initialized to the first item, and when we explicitly set it - // in the allDay checkbox toggling, so we need these checks to - // find out when the spinner is actually being clicked. - - // Set the initial selection. - if (mAvailabilityCurrentlySelected == -1) { - mAvailabilityCurrentlySelected = position; - } - - if (mAvailabilityCurrentlySelected != position && - !mAllDayChangingAvailability) { - mAvailabilityExplicitlySet = true; - } else { - mAvailabilityCurrentlySelected = position; - mAllDayChangingAvailability = false; - } - } - @Override - public void onNothingSelected(AdapterView<?> arg0) { } - }); - - - mDescriptionTextView.setTag(mDescriptionTextView.getBackground()); - mAttendeesList.setTag(mAttendeesList.getBackground()); - mOriginalPadding[0] = mLocationTextView.getPaddingLeft(); - mOriginalPadding[1] = mLocationTextView.getPaddingTop(); - mOriginalPadding[2] = mLocationTextView.getPaddingRight(); - mOriginalPadding[3] = mLocationTextView.getPaddingBottom(); - mEditViewList.add(mTitleTextView); - mEditViewList.add(mLocationTextView); - mEditViewList.add(mDescriptionTextView); - mEditViewList.add(mAttendeesList); - - mViewOnlyList.add(view.findViewById(R.id.when_row)); - mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row)); - - mEditOnlyList.add(view.findViewById(R.id.all_day_row)); - mEditOnlyList.add(view.findViewById(R.id.availability_row)); - mEditOnlyList.add(view.findViewById(R.id.visibility_row)); - mEditOnlyList.add(view.findViewById(R.id.from_row)); - mEditOnlyList.add(view.findViewById(R.id.to_row)); - mEditOnlyList.add(mTimezoneRow); - mEditOnlyList.add(mStartHomeGroup); - mEditOnlyList.add(mEndHomeGroup); - - mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value); - mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container); - - mTimezone = Utils.getTimeZone(activity, null); - mIsMultipane = activity.getResources().getBoolean(R.bool.tablet_config); - mStartTime = new Time(mTimezone); - mEndTime = new Time(mTimezone); - mEmailValidator = new Rfc822Validator(null); - initMultiAutoCompleteTextView((RecipientEditTextView) mAttendeesList); - - // Display loading screen - setModel(null); - - FragmentManager fm = activity.getFragmentManager(); - RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm - .findFragmentByTag(FRAG_TAG_RECUR_PICKER); - if (rpd != null) { - rpd.setOnRecurrenceSetListener(this); - } - TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm - .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER); - if (tzpd != null) { - tzpd.setOnTimeZoneSetListener(this); - } - TimePickerDialog tpd = (TimePickerDialog) fm.findFragmentByTag(FRAG_TAG_TIME_PICKER); - if (tpd != null) { - View v; - mTimeSelectedWasStartTime = timeSelectedWasStartTime; - if (timeSelectedWasStartTime) { - v = mStartTimeButton; - } else { - v = mEndTimeButton; - } - tpd.setOnTimeSetListener(new TimeListener(v)); - } - mDatePickerDialog = (DatePickerDialog) fm.findFragmentByTag(FRAG_TAG_DATE_PICKER); - if (mDatePickerDialog != null) { - View v; - mDateSelectedWasStartDate = dateSelectedWasStartDate; - if (dateSelectedWasStartDate) { - v = mStartDateButton; - } else { - v = mEndDateButton; - } - mDatePickerDialog.setOnDateSetListener(new DateListener(v)); - } - } - - - /** - * Loads an integer array asset into a list. - */ - private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) { - int[] vals = r.getIntArray(resNum); - int size = vals.length; - ArrayList<Integer> list = new ArrayList<Integer>(size); - - for (int i = 0; i < size; i++) { - list.add(vals[i]); - } - - return list; - } - - /** - * Loads a String array asset into a list. - */ - private static ArrayList<String> loadStringArray(Resources r, int resNum) { - String[] labels = r.getStringArray(resNum); - ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels)); - return list; - } - - private void prepareAvailability() { - Resources r = mActivity.getResources(); - - mAvailabilityValues = loadIntegerArray(r, R.array.availability_values); - mAvailabilityLabels = loadStringArray(r, R.array.availability); - // Copy the unadulterated availability labels for all-day toggling. - mOriginalAvailabilityLabels = new ArrayList<String>(); - mOriginalAvailabilityLabels.addAll(mAvailabilityLabels); - - if (mModel.mCalendarAllowedAvailability != null) { - EventViewUtils.reduceMethodList(mAvailabilityValues, mAvailabilityLabels, - mModel.mCalendarAllowedAvailability); - } - - mAvailabilityAdapter = new ArrayAdapter<String>(mActivity, - android.R.layout.simple_spinner_item, mAvailabilityLabels); - mAvailabilityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAvailabilitySpinner.setAdapter(mAvailabilityAdapter); - } - - /** - * Prepares the reminder UI elements. - * <p> - * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as - * needed for the current set of reminders and calendar properties, and then creates UI - * elements. - */ - private void prepareReminders() { - CalendarEventModel model = mModel; - Resources r = mActivity.getResources(); - - // Load the labels and corresponding numeric values for the minutes and methods lists - // from the assets. If we're switching calendars, we need to clear and re-populate the - // lists (which may have elements added and removed based on calendar properties). This - // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a - // new event that aren't in the default set. - mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values); - mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels); - mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values); - mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels); - - // Remove any reminder methods that aren't allowed for this calendar. If this is - // a new event, mCalendarAllowedReminders may not be set the first time we're called. - if (mModel.mCalendarAllowedReminders != null) { - EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels, - mModel.mCalendarAllowedReminders); - } - - int numReminders = 0; - if (model.mHasAlarm) { - ArrayList<ReminderEntry> reminders = model.mReminders; - numReminders = reminders.size(); - // Insert any minute values that aren't represented in the minutes list. - for (ReminderEntry re : reminders) { - if (mReminderMethodValues.contains(re.getMethod())) { - EventViewUtils.addMinutesToList(mActivity, mReminderMinuteValues, - mReminderMinuteLabels, re.getMinutes()); - } - } - - // Create a UI element for each reminder. We display all of the reminders we get - // from the provider, even if the count exceeds the calendar maximum. (Also, for - // a new event, we won't have a maxReminders value available.) - mUnsupportedReminders.clear(); - for (ReminderEntry re : reminders) { - if (mReminderMethodValues.contains(re.getMethod()) - || re.getMethod() == Reminders.METHOD_DEFAULT) { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, - mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, - mReminderMethodLabels, re, Integer.MAX_VALUE, null); - } else { - // TODO figure out a way to display unsupported reminders - mUnsupportedReminders.add(re); - } - } - } - - updateRemindersVisibility(numReminders); - EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); - } - - /** - * Fill in the view with the contents of the given event model. This allows - * an edit view to be initialized before the event has been loaded. Passing - * in null for the model will display a loading screen. A non-null model - * will fill in the view's fields with the data contained in the model. - * - * @param model The event model to pull the data from - */ - public void setModel(CalendarEventModel model) { - mModel = model; - - // Need to close the autocomplete adapter to prevent leaking cursors. - if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) { - ((EmailAddressAdapter)mAddressAdapter).close(); - mAddressAdapter = null; - } - - if (model == null) { - // Display loading screen - mLoadingMessage.setVisibility(View.VISIBLE); - mScrollView.setVisibility(View.GONE); - return; - } - - boolean canRespond = EditEventHelper.canRespond(model); - - long begin = model.mStart; - long end = model.mEnd; - mTimezone = model.mTimezone; // this will be UTC for all day events - - // Set up the starting times - if (begin > 0) { - mStartTime.timezone = mTimezone; - mStartTime.set(begin); - mStartTime.normalize(true); - } - if (end > 0) { - mEndTime.timezone = mTimezone; - mEndTime.set(end); - mEndTime.normalize(true); - } - - mRrule = model.mRrule; - if (!TextUtils.isEmpty(mRrule)) { - mEventRecurrence.parse(mRrule); - } - - if (mEventRecurrence.startDate == null) { - mEventRecurrence.startDate = mStartTime; - } - - // If the user is allowed to change the attendees set up the view and - // validator - if (!model.mHasAttendeeData) { - mAttendeesGroup.setVisibility(View.GONE); - } - - mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - setAllDayViewsVisibility(isChecked); - } - }); - - boolean prevAllDay = mAllDayCheckBox.isChecked(); - mAllDay = false; // default to false. Let setAllDayViewsVisibility update it as needed - if (model.mAllDay) { - mAllDayCheckBox.setChecked(true); - // put things back in local time for all day events - mTimezone = Utils.getTimeZone(mActivity, null); - mStartTime.timezone = mTimezone; - mEndTime.timezone = mTimezone; - mEndTime.normalize(true); - } else { - mAllDayCheckBox.setChecked(false); - } - // On a rotation we need to update the views but onCheckedChanged - // doesn't get called - if (prevAllDay == mAllDayCheckBox.isChecked()) { - setAllDayViewsVisibility(prevAllDay); - } - - populateTimezone(mStartTime.normalize(true)); - - SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); - String defaultReminderString = prefs.getString( - GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); - mDefaultReminderMinutes = Integer.parseInt(defaultReminderString); - - prepareReminders(); - prepareAvailability(); - - View reminderAddButton = mView.findViewById(R.id.reminder_add); - View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - addReminder(); - } - }; - reminderAddButton.setOnClickListener(addReminderOnClickListener); - - if (!mIsMultipane) { - mView.findViewById(R.id.is_all_day_label).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - mAllDayCheckBox.setChecked(!mAllDayCheckBox.isChecked()); - } - }); - } - - if (model.mTitle != null) { - mTitleTextView.setTextKeepState(model.mTitle); - } - - if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer) - || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) { - mView.findViewById(R.id.organizer_label).setVisibility(View.GONE); - mView.findViewById(R.id.organizer).setVisibility(View.GONE); - mOrganizerGroup.setVisibility(View.GONE); - } else { - ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName); - } - - if (model.mLocation != null) { - mLocationTextView.setTextKeepState(model.mLocation); - } - - if (model.mDescription != null) { - mDescriptionTextView.setTextKeepState(model.mDescription); - } - - int availIndex = mAvailabilityValues.indexOf(model.mAvailability); - if (availIndex != -1) { - mAvailabilitySpinner.setSelection(availIndex); - } - mAccessLevelSpinner.setSelection(model.mAccessLevel); - - View responseLabel = mView.findViewById(R.id.response_label); - if (canRespond) { - int buttonToCheck = EventInfoFragment - .findButtonIdForResponse(model.mSelfAttendeeStatus); - mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons - mResponseRadioGroup.setVisibility(View.VISIBLE); - responseLabel.setVisibility(View.VISIBLE); - } else { - responseLabel.setVisibility(View.GONE); - mResponseRadioGroup.setVisibility(View.GONE); - mResponseGroup.setVisibility(View.GONE); - } - - if (model.mUri != null) { - // This is an existing event so hide the calendar spinner - // since we can't change the calendar. - View calendarGroup = mView.findViewById(R.id.calendar_selector_group); - calendarGroup.setVisibility(View.GONE); - TextView tv = (TextView) mView.findViewById(R.id.calendar_textview); - tv.setText(model.mCalendarDisplayName); - tv = (TextView) mView.findViewById(R.id.calendar_textview_secondary); - if (tv != null) { - tv.setText(model.mOwnerAccount); - } - } else { - View calendarGroup = mView.findViewById(R.id.calendar_group); - calendarGroup.setVisibility(View.GONE); - } - if (model.isEventColorInitialized()) { - updateHeadlineColor(model, model.getEventColor()); - } - - populateWhen(); - populateRepeats(); - updateAttendees(model.mAttendeesList); - - updateView(); - mScrollView.setVisibility(View.VISIBLE); - mLoadingMessage.setVisibility(View.GONE); - sendAccessibilityEvent(); - } - - public void updateHeadlineColor(CalendarEventModel model, int displayColor) { - if (model.mUri != null) { - if (mIsMultipane) { - mView.findViewById(R.id.calendar_textview_with_colorpicker) - .setBackgroundColor(displayColor); - } else { - mView.findViewById(R.id.calendar_group).setBackgroundColor(displayColor); - } - } else { - setSpinnerBackgroundColor(displayColor); - } - } - - private void setSpinnerBackgroundColor(int displayColor) { - if (mIsMultipane) { - mCalendarSelectorWrapper.setBackgroundColor(displayColor); - } else { - mCalendarSelectorGroup.setBackgroundColor(displayColor); - } - } - - private void sendAccessibilityEvent() { - AccessibilityManager am = - (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE); - if (!am.isEnabled() || mModel == null) { - return; - } - StringBuilder b = new StringBuilder(); - addFieldsRecursive(b, mView); - CharSequence msg = b.toString(); - - AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); - event.setClassName(getClass().getName()); - event.setPackageName(mActivity.getPackageName()); - event.getText().add(msg); - event.setAddedCount(msg.length()); - - am.sendAccessibilityEvent(event); - } - - private void addFieldsRecursive(StringBuilder b, View v) { - if (v == null || v.getVisibility() != View.VISIBLE) { - return; - } - if (v instanceof TextView) { - CharSequence tv = ((TextView) v).getText(); - if (!TextUtils.isEmpty(tv.toString().trim())) { - b.append(tv + PERIOD_SPACE); - } - } else if (v instanceof RadioGroup) { - RadioGroup rg = (RadioGroup) v; - int id = rg.getCheckedRadioButtonId(); - if (id != View.NO_ID) { - b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE); - } - } else if (v instanceof Spinner) { - Spinner s = (Spinner) v; - if (s.getSelectedItem() instanceof String) { - String str = ((String) (s.getSelectedItem())).trim(); - if (!TextUtils.isEmpty(str)) { - b.append(str + PERIOD_SPACE); - } - } - } else if (v instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) v; - int children = vg.getChildCount(); - for (int i = 0; i < children; i++) { - addFieldsRecursive(b, vg.getChildAt(i)); - } - } - } - - /** - * Creates a single line string for the time/duration - */ - protected void setWhenString() { - String when; - int flags = DateUtils.FORMAT_SHOW_DATE; - String tz = mTimezone; - if (mModel.mAllDay) { - flags |= DateUtils.FORMAT_SHOW_WEEKDAY; - tz = Time.TIMEZONE_UTC; - } else { - flags |= DateUtils.FORMAT_SHOW_TIME; - if (DateFormat.is24HourFormat(mActivity)) { - flags |= DateUtils.FORMAT_24HOUR; - } - } - long startMillis = mStartTime.normalize(true); - long endMillis = mEndTime.normalize(true); - mSB.setLength(0); - when = DateUtils - .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString(); - mWhenView.setText(when); - } - - /** - * Configures the Calendars spinner. This is only done for new events, because only new - * events allow you to select a calendar while editing an event. - * <p> - * We tuck a reference to a Cursor with calendar database data into the spinner, so that - * we can easily extract calendar-specific values when the value changes (the spinner's - * onItemSelected callback is configured). - */ - public void setCalendarsCursor(Cursor cursor, boolean userVisible, long selectedCalendarId) { - // If there are no syncable calendars, then we cannot allow - // creating a new event. - mCalendarsCursor = cursor; - if (cursor == null || cursor.getCount() == 0) { - // Cancel the "loading calendars" dialog if it exists - if (mSaveAfterQueryComplete) { - mLoadingCalendarsDialog.cancel(); - } - if (!userVisible) { - return; - } - // Create an error message for the user that, when clicked, - // will exit this activity without saving the event. - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( - android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) - .setPositiveButton(R.string.add_account, this) - .setNegativeButton(android.R.string.no, this).setOnCancelListener(this); - mNoCalendarsDialog = builder.show(); - return; - } - - int selection; - if (selectedCalendarId != -1) { - selection = findSelectedCalendarPosition(cursor, selectedCalendarId); - } else { - selection = findDefaultCalendarPosition(cursor); - } - - // populate the calendars spinner - CalendarsAdapter adapter = new CalendarsAdapter(mActivity, - R.layout.calendars_spinner_item, cursor); - mCalendarsSpinner.setAdapter(adapter); - mCalendarsSpinner.setOnItemSelectedListener(this); - mCalendarsSpinner.setSelection(selection); - - if (mSaveAfterQueryComplete) { - mLoadingCalendarsDialog.cancel(); - if (prepareForSave() && fillModelFromUI()) { - int exit = userVisible ? Utils.DONE_EXIT : 0; - mDone.setDoneCode(Utils.DONE_SAVE | exit); - mDone.run(); - } else if (userVisible) { - mDone.setDoneCode(Utils.DONE_EXIT); - mDone.run(); - } else if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view"); - } - return; - } - } - - /** - * Updates the view based on {@link #mModification} and {@link #mModel} - */ - public void updateView() { - if (mModel == null) { - return; - } - if (EditEventHelper.canModifyEvent(mModel)) { - setViewStates(mModification); - } else { - setViewStates(Utils.MODIFY_UNINITIALIZED); - } - } - - private void setViewStates(int mode) { - // Extra canModify check just in case - if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) { - setWhenString(); - - for (View v : mViewOnlyList) { - v.setVisibility(View.VISIBLE); - } - for (View v : mEditOnlyList) { - v.setVisibility(View.GONE); - } - for (View v : mEditViewList) { - v.setEnabled(false); - v.setBackgroundDrawable(null); - } - mCalendarSelectorGroup.setVisibility(View.GONE); - mCalendarStaticGroup.setVisibility(View.VISIBLE); - mRruleButton.setEnabled(false); - if (EditEventHelper.canAddReminders(mModel)) { - mRemindersGroup.setVisibility(View.VISIBLE); - } else { - mRemindersGroup.setVisibility(View.GONE); - } - if (TextUtils.isEmpty(mLocationTextView.getText())) { - mLocationGroup.setVisibility(View.GONE); - } - if (TextUtils.isEmpty(mDescriptionTextView.getText())) { - mDescriptionGroup.setVisibility(View.GONE); - } - } else { - for (View v : mViewOnlyList) { - v.setVisibility(View.GONE); - } - for (View v : mEditOnlyList) { - v.setVisibility(View.VISIBLE); - } - for (View v : mEditViewList) { - v.setEnabled(true); - if (v.getTag() != null) { - v.setBackgroundDrawable((Drawable) v.getTag()); - v.setPadding(mOriginalPadding[0], mOriginalPadding[1], mOriginalPadding[2], - mOriginalPadding[3]); - } - } - if (mModel.mUri == null) { - mCalendarSelectorGroup.setVisibility(View.VISIBLE); - mCalendarStaticGroup.setVisibility(View.GONE); - } else { - mCalendarSelectorGroup.setVisibility(View.GONE); - mCalendarStaticGroup.setVisibility(View.VISIBLE); - } - if (mModel.mOriginalSyncId == null) { - mRruleButton.setEnabled(true); - } else { - mRruleButton.setEnabled(false); - mRruleButton.setBackgroundDrawable(null); - } - mRemindersGroup.setVisibility(View.VISIBLE); - - mLocationGroup.setVisibility(View.VISIBLE); - mDescriptionGroup.setVisibility(View.VISIBLE); - } - setAllDayViewsVisibility(mAllDayCheckBox.isChecked()); - } - - public void setModification(int modifyWhich) { - mModification = modifyWhich; - updateView(); - updateHomeTime(); - } - - private int findSelectedCalendarPosition(Cursor calendarsCursor, long calendarId) { - if (calendarsCursor.getCount() <= 0) { - return -1; - } - int calendarIdColumn = calendarsCursor.getColumnIndexOrThrow(Calendars._ID); - int position = 0; - calendarsCursor.moveToPosition(-1); - while (calendarsCursor.moveToNext()) { - if (calendarsCursor.getLong(calendarIdColumn) == calendarId) { - return position; - } - position++; - } - return 0; - } - - // Find the calendar position in the cursor that matches calendar in - // preference - private int findDefaultCalendarPosition(Cursor calendarsCursor) { - if (calendarsCursor.getCount() <= 0) { - return -1; - } - - String defaultCalendar = Utils.getSharedPreference( - mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, (String) null); - - int calendarsOwnerIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); - int accountNameIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - int accountTypeIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - int position = 0; - calendarsCursor.moveToPosition(-1); - while (calendarsCursor.moveToNext()) { - String calendarOwner = calendarsCursor.getString(calendarsOwnerIndex); - if (defaultCalendar == null) { - // There is no stored default upon the first time running. Use a primary - // calendar in this case. - if (calendarOwner != null && - calendarOwner.equals(calendarsCursor.getString(accountNameIndex)) && - !CalendarContract.ACCOUNT_TYPE_LOCAL.equals( - calendarsCursor.getString(accountTypeIndex))) { - return position; - } - } else if (defaultCalendar.equals(calendarOwner)) { - // Found the default calendar. - return position; - } - position++; - } - return 0; - } - - private void updateAttendees(HashMap<String, Attendee> attendeesList) { - if (attendeesList == null || attendeesList.isEmpty()) { - return; - } - mAttendeesList.setText(null); - for (Attendee attendee : attendeesList.values()) { - - // TODO: Please remove separator when Calendar uses the chips MR2 project - - // Adding a comma separator between email addresses to prevent a chips MR1.1 bug - // in which email addresses are concatenated together with no separator. - mAttendeesList.append(attendee.mEmail + ", "); - } - } - - private void updateRemindersVisibility(int numReminders) { - if (numReminders == 0) { - mRemindersContainer.setVisibility(View.GONE); - } else { - mRemindersContainer.setVisibility(View.VISIBLE); - } - } - - /** - * Add a new reminder when the user hits the "add reminder" button. We use the default - * reminder time and method. - */ - private void addReminder() { - // TODO: when adding a new reminder, make it different from the - // last one in the list (if any). - if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, - mReminderMinuteValues, mReminderMinuteLabels, - mReminderMethodValues, mReminderMethodLabels, - ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), - mModel.mCalendarMaxReminders, null); - } else { - EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, - mReminderMinuteValues, mReminderMinuteLabels, - mReminderMethodValues, mReminderMethodLabels, - ReminderEntry.valueOf(mDefaultReminderMinutes), - mModel.mCalendarMaxReminders, null); - } - updateRemindersVisibility(mReminderItems.size()); - EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); - } - - // From com.google.android.gm.ComposeActivity - private MultiAutoCompleteTextView initMultiAutoCompleteTextView(RecipientEditTextView list) { - if (ChipsUtil.supportsChipsUi()) { - mAddressAdapter = new RecipientAdapter(mActivity); - list.setAdapter((BaseRecipientAdapter) mAddressAdapter); - list.setOnFocusListShrinkRecipients(false); - } else { - mAddressAdapter = new EmailAddressAdapter(mActivity); - list.setAdapter((EmailAddressAdapter)mAddressAdapter); - } - list.setTokenizer(new Rfc822Tokenizer()); - list.setValidator(mEmailValidator); - - // NOTE: assumes no other filters are set - list.setFilters(sRecipientFilters); - - return list; - } - - /** - * From com.google.android.gm.ComposeActivity Implements special address - * cleanup rules: The first space key entry following an "@" symbol that is - * followed by any combination of letters and symbols, including one+ dots - * and zero commas, should insert an extra comma (followed by the space). - */ - private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() }; - - private void setDate(TextView view, long millis) { - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH - | DateUtils.FORMAT_ABBREV_WEEKDAY; - - // Unfortunately, DateUtils doesn't support a timezone other than the - // default timezone provided by the system, so we have this ugly hack - // here to trick it into formatting our time correctly. In order to - // prevent all sorts of craziness, we synchronize on the TimeZone class - // to prevent other threads from reading an incorrect timezone from - // calls to TimeZone#getDefault() - // TODO fix this if/when DateUtils allows for passing in a timezone - String dateString; - synchronized (TimeZone.class) { - TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); - dateString = DateUtils.formatDateTime(mActivity, millis, flags); - // setting the default back to null restores the correct behavior - TimeZone.setDefault(null); - } - view.setText(dateString); - } - - private void setTime(TextView view, long millis) { - int flags = DateUtils.FORMAT_SHOW_TIME; - flags |= DateUtils.FORMAT_CAP_NOON_MIDNIGHT; - if (DateFormat.is24HourFormat(mActivity)) { - flags |= DateUtils.FORMAT_24HOUR; - } - - // Unfortunately, DateUtils doesn't support a timezone other than the - // default timezone provided by the system, so we have this ugly hack - // here to trick it into formatting our time correctly. In order to - // prevent all sorts of craziness, we synchronize on the TimeZone class - // to prevent other threads from reading an incorrect timezone from - // calls to TimeZone#getDefault() - // TODO fix this if/when DateUtils allows for passing in a timezone - String timeString; - synchronized (TimeZone.class) { - TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); - timeString = DateUtils.formatDateTime(mActivity, millis, flags); - TimeZone.setDefault(null); - } - view.setText(timeString); - } - - /** - * @param isChecked - */ - protected void setAllDayViewsVisibility(boolean isChecked) { - if (isChecked) { - if (mEndTime.hour == 0 && mEndTime.minute == 0) { - if (mAllDay != isChecked) { - 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); - mTimezoneRow.setVisibility(View.GONE); - } else { - if (mEndTime.hour == 0 && mEndTime.minute == 0) { - if (mAllDay != isChecked) { - mEndTime.monthDay++; - } - - long endMillis = mEndTime.normalize(true); - setDate(mEndDateButton, endMillis); - setTime(mEndTimeButton, endMillis); - } - mStartTimeButton.setVisibility(View.VISIBLE); - mEndTimeButton.setVisibility(View.VISIBLE); - mTimezoneRow.setVisibility(View.VISIBLE); - } - - // If this is a new event, and if availability has not yet been - // explicitly set, toggle busy/available as the inverse of all day. - if (mModel.mUri == null && !mAvailabilityExplicitlySet) { - // Values are from R.arrays.availability_values. - // 0 = busy - // 1 = available - int newAvailabilityValue = isChecked? 1 : 0; - if (mAvailabilityAdapter != null && mAvailabilityValues != null - && mAvailabilityValues.contains(newAvailabilityValue)) { - // We'll need to let the spinner's listener know that we're - // explicitly toggling it. - mAllDayChangingAvailability = true; - - String newAvailabilityLabel = mOriginalAvailabilityLabels.get(newAvailabilityValue); - int newAvailabilityPos = mAvailabilityAdapter.getPosition(newAvailabilityLabel); - mAvailabilitySpinner.setSelection(newAvailabilityPos); - } - } - - mAllDay = isChecked; - updateHomeTime(); - } - - public void setColorPickerButtonStates(int[] colorArray) { - setColorPickerButtonStates(colorArray != null && colorArray.length > 0); - } - - public void setColorPickerButtonStates(boolean showColorPalette) { - if (showColorPalette) { - mColorPickerNewEvent.setVisibility(View.VISIBLE); - mColorPickerExistingEvent.setVisibility(View.VISIBLE); - } else { - mColorPickerNewEvent.setVisibility(View.INVISIBLE); - mColorPickerExistingEvent.setVisibility(View.GONE); - } - } - - public boolean isColorPaletteVisible() { - return mColorPickerNewEvent.getVisibility() == View.VISIBLE || - mColorPickerExistingEvent.getVisibility() == View.VISIBLE; - } - - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - // This is only used for the Calendar spinner in new events, and only fires when the - // calendar selection changes or on screen rotation - Cursor c = (Cursor) parent.getItemAtPosition(position); - if (c == null) { - // TODO: can this happen? should we drop this check? - Log.w(TAG, "Cursor not set on calendar item"); - return; - } - - // Do nothing if the selection didn't change so that reminders will not get lost - int idColumn = c.getColumnIndexOrThrow(Calendars._ID); - long calendarId = c.getLong(idColumn); - int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); - int color = c.getInt(colorColumn); - int displayColor = Utils.getDisplayColorFromColor(color); - - // Prevents resetting of data (reminders, etc.) on orientation change. - if (calendarId == mModel.mCalendarId && mModel.isCalendarColorInitialized() && - displayColor == mModel.getCalendarColor()) { - return; - } - - setSpinnerBackgroundColor(displayColor); - - mModel.mCalendarId = calendarId; - mModel.setCalendarColor(displayColor); - mModel.mCalendarAccountName = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_NAME); - mModel.mCalendarAccountType = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_TYPE); - mModel.setEventColor(mModel.getCalendarColor()); - - setColorPickerButtonStates(mModel.getCalendarEventColors()); - - // Update the max/allowed reminders with the new calendar properties. - int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS); - mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn); - int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS); - mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn); - int allowedAttendeeTypesColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_ATTENDEE_TYPES); - mModel.mCalendarAllowedAttendeeTypes = c.getString(allowedAttendeeTypesColumn); - int allowedAvailabilityColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_AVAILABILITY); - mModel.mCalendarAllowedAvailability = c.getString(allowedAvailabilityColumn); - - // Discard the current reminders and replace them with the model's default reminder set. - // We could attempt to save & restore the reminders that have been added, but that's - // probably more trouble than it's worth. - mModel.mReminders.clear(); - mModel.mReminders.addAll(mModel.mDefaultReminders); - mModel.mHasAlarm = mModel.mReminders.size() != 0; - - // Update the UI elements. - mReminderItems.clear(); - LinearLayout reminderLayout = - (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container); - reminderLayout.removeAllViews(); - prepareReminders(); - prepareAvailability(); - } - - /** - * Checks if the start and end times for this event should be displayed in - * the Calendar app's time zone as well and formats and displays them. - */ - private void updateHomeTime() { - String tz = Utils.getTimeZone(mActivity, null); - if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone) - && mModification != EditEventHelper.MODIFY_UNINITIALIZED) { - int flags = DateUtils.FORMAT_SHOW_TIME; - boolean is24Format = DateFormat.is24HourFormat(mActivity); - if (is24Format) { - flags |= DateUtils.FORMAT_24HOUR; - } - long millisStart = mStartTime.toMillis(false); - long millisEnd = mEndTime.toMillis(false); - - boolean isDSTStart = mStartTime.isDst != 0; - boolean isDSTEnd = mEndTime.isDst != 0; - - // First update the start date and times - String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( - isDSTStart, TimeZone.SHORT, Locale.getDefault()); - StringBuilder time = new StringBuilder(); - - mSB.setLength(0); - time.append(DateUtils - .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz)) - .append(" ").append(tzDisplay); - mStartTimeHome.setText(time.toString()); - - flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; - mSB.setLength(0); - mStartDateHome - .setText(DateUtils.formatDateRange( - mActivity, mF, millisStart, millisStart, flags, tz).toString()); - - // Make any adjustments needed for the end times - if (isDSTEnd != isDSTStart) { - tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( - isDSTEnd, TimeZone.SHORT, Locale.getDefault()); - } - flags = DateUtils.FORMAT_SHOW_TIME; - if (is24Format) { - flags |= DateUtils.FORMAT_24HOUR; - } - - // Then update the end times - time.setLength(0); - mSB.setLength(0); - time.append(DateUtils.formatDateRange( - mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay); - mEndTimeHome.setText(time.toString()); - - flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; - mSB.setLength(0); - mEndDateHome.setText(DateUtils.formatDateRange( - mActivity, mF, millisEnd, millisEnd, flags, tz).toString()); - - mStartHomeGroup.setVisibility(View.VISIBLE); - mEndHomeGroup.setVisibility(View.VISIBLE); - } else { - mStartHomeGroup.setVisibility(View.GONE); - mEndHomeGroup.setVisibility(View.GONE); - } - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - } -} diff --git a/src/com/android/calendar/event/EventColorCache.java b/src/com/android/calendar/event/EventColorCache.java deleted file mode 100644 index 8fc03701..00000000 --- a/src/com/android/calendar/event/EventColorCache.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -/** - * A cache for event colors and event color keys stored based upon calendar account name and type. - */ -public class EventColorCache implements Serializable { - - private static final long serialVersionUID = 2L; - - private static final String SEPARATOR = "::"; - - private Map<String, ArrayList<Integer>> mColorPaletteMap; - private Map<String, Integer> mColorKeyMap; - - public EventColorCache() { - mColorPaletteMap = new HashMap<String, ArrayList<Integer>>(); - mColorKeyMap = new HashMap<String, Integer>(); - } - - /** - * Inserts a color into the cache. - */ - public void insertColor(String accountName, String accountType, int displayColor, - int colorKey) { - mColorKeyMap.put(createKey(accountName, accountType, displayColor), colorKey); - String key = createKey(accountName, accountType); - ArrayList<Integer> colorPalette; - if ((colorPalette = mColorPaletteMap.get(key)) == null) { - colorPalette = new ArrayList<Integer>(); - } - colorPalette.add(displayColor); - mColorPaletteMap.put(key, colorPalette); - } - - /** - * Retrieve an array of colors for a specific account name and type. - */ - public int[] getColorArray(String accountName, String accountType) { - ArrayList<Integer> colors = mColorPaletteMap.get(createKey(accountName, accountType)); - if (colors == null) { - return null; - } - int[] ret = new int[colors.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = colors.get(i); - } - return ret; - } - - /** - * Retrieve an event color's unique key based on account name, type, and color. - */ - public int getColorKey(String accountName, String accountType, int displayColor) { - return mColorKeyMap.get(createKey(accountName, accountType, displayColor)); - } - - /** - * Sorts the arrays of colors based on a comparator. - */ - public void sortPalettes(Comparator<Integer> comparator) { - for (String key : mColorPaletteMap.keySet()) { - ArrayList<Integer> palette = mColorPaletteMap.get(key); - Integer[] sortedColors = new Integer[palette.size()]; - Arrays.sort(palette.toArray(sortedColors), comparator); - palette.clear(); - for (Integer color : sortedColors) { - palette.add(color); - } - mColorPaletteMap.put(key, palette); - } - } - - private String createKey(String accountName, String accountType) { - return new StringBuilder().append(accountName) - .append(SEPARATOR) - .append(accountType) - .toString(); - } - - private String createKey(String accountName, String accountType, int displayColor) { - return new StringBuilder(createKey(accountName, accountType)) - .append(SEPARATOR) - .append(displayColor) - .toString(); - } -} diff --git a/src/com/android/calendar/event/EventColorPickerDialog.java b/src/com/android/calendar/event/EventColorPickerDialog.java deleted file mode 100644 index 5a6b2685..00000000 --- a/src/com/android/calendar/event/EventColorPickerDialog.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; - -import com.android.calendar.R; -import com.android.colorpicker.ColorPickerDialog; - -/** - * A dialog which displays event colors, with an additional button for the calendar color. - */ -public class EventColorPickerDialog extends ColorPickerDialog { - - private static final int NUM_COLUMNS = 4; - private static final String KEY_CALENDAR_COLOR = "calendar_color"; - - private int mCalendarColor; - - public EventColorPickerDialog() { - // Empty constructor required for dialog fragment. - } - - public static EventColorPickerDialog newInstance(int[] colors, int selectedColor, - int calendarColor, boolean isTablet) { - EventColorPickerDialog ret = new EventColorPickerDialog(); - ret.initialize(R.string.event_color_picker_dialog_title, colors, selectedColor, NUM_COLUMNS, - isTablet ? SIZE_LARGE : SIZE_SMALL); - ret.setCalendarColor(calendarColor); - return ret; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mCalendarColor = savedInstanceState.getInt(KEY_CALENDAR_COLOR); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(KEY_CALENDAR_COLOR, mCalendarColor); - } - - public void setCalendarColor(int color) { - mCalendarColor = color; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, - getActivity().getString(R.string.event_color_set_to_default), - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - onColorSelected(mCalendarColor); - } - } - ); - return dialog; - } -} diff --git a/src/com/android/calendar/event/EventLocationAdapter.java b/src/com/android/calendar/event/EventLocationAdapter.java deleted file mode 100644 index 3bcb3acd..00000000 --- a/src/com/android/calendar/event/EventLocationAdapter.java +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.event; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.CalendarContract.Events; -import android.provider.ContactsContract.CommonDataKinds; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.RawContacts; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.calendar.R; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; -import java.util.concurrent.ExecutionException; - -// TODO: limit length of dropdown to stop at the soft keyboard -// TODO: history icon resize asset - -/** - * An adapter for autocomplete of the location field in edit-event view. - */ -public class EventLocationAdapter extends ArrayAdapter<EventLocationAdapter.Result> - implements Filterable { - private static final String TAG = "EventLocationAdapter"; - - /** - * Internal class for containing info for an item in the auto-complete results. - */ - public static class Result { - private final String mName; - private final String mAddress; - - // The default image resource for the icon. This will be null if there should - // be no icon (if multiple listings for a contact, only the first one should have the - // photo icon). - private final Integer mDefaultIcon; - - // The contact photo to use for the icon. This will override the default icon. - private final Uri mContactPhotoUri; - - public Result(String displayName, String address, Integer defaultIcon, - Uri contactPhotoUri) { - this.mName = displayName; - this.mAddress = address; - this.mDefaultIcon = defaultIcon; - this.mContactPhotoUri = contactPhotoUri; - } - - /** - * This is the autocompleted text. - */ - @Override - public String toString() { - return mAddress; - } - } - private static ArrayList<Result> EMPTY_LIST = new ArrayList<Result>(); - - // Constants for contacts query: - // SELECT ... FROM view_data data WHERE ((data1 LIKE 'input%' OR data1 LIKE '%input%' OR - // display_name LIKE 'input%' OR display_name LIKE '%input%' )) ORDER BY display_name ASC - private static final String[] CONTACTS_PROJECTION = new String[] { - CommonDataKinds.StructuredPostal._ID, - Contacts.DISPLAY_NAME, - CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, - RawContacts.CONTACT_ID, - Contacts.PHOTO_ID, - }; - private static final int CONTACTS_INDEX_ID = 0; - private static final int CONTACTS_INDEX_DISPLAY_NAME = 1; - private static final int CONTACTS_INDEX_ADDRESS = 2; - private static final int CONTACTS_INDEX_CONTACT_ID = 3; - private static final int CONTACTS_INDEX_PHOTO_ID = 4; - // TODO: Only query visible contacts? - private static final String CONTACTS_WHERE = new StringBuilder() - .append("(") - .append(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) - .append(" LIKE ? OR ") - .append(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) - .append(" LIKE ? OR ") - .append(Contacts.DISPLAY_NAME) - .append(" LIKE ? OR ") - .append(Contacts.DISPLAY_NAME) - .append(" LIKE ? )") - .toString(); - - // Constants for recent locations query (in Events table): - // SELECT ... FROM view_events WHERE (eventLocation LIKE 'input%') ORDER BY _id DESC - private static final String[] EVENT_PROJECTION = new String[] { - Events._ID, - Events.EVENT_LOCATION, - Events.VISIBLE, - }; - private static final int EVENT_INDEX_ID = 0; - private static final int EVENT_INDEX_LOCATION = 1; - private static final int EVENT_INDEX_VISIBLE = 2; - private static final String LOCATION_WHERE = Events.VISIBLE + "=? AND " - + Events.EVENT_LOCATION + " LIKE ?"; - private static final int MAX_LOCATION_SUGGESTIONS = 4; - - private final ContentResolver mResolver; - private final LayoutInflater mInflater; - private final ArrayList<Result> mResultList = new ArrayList<Result>(); - - // The cache for contacts photos. We don't have to worry about clearing this, as a - // new adapter is created for every edit event. - private final Map<Uri, Bitmap> mPhotoCache = new HashMap<Uri, Bitmap>(); - - /** - * Constructor. - */ - public EventLocationAdapter(Context context) { - super(context, R.layout.location_dropdown_item, EMPTY_LIST); - - mResolver = context.getContentResolver(); - mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public int getCount() { - return mResultList.size(); - } - - @Override - public Result getItem(int index) { - if (index < mResultList.size()) { - return mResultList.get(index); - } else { - return null; - } - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - View view = convertView; - if (view == null) { - view = mInflater.inflate(R.layout.location_dropdown_item, parent, false); - } - final Result result = getItem(position); - if (result == null) { - return view; - } - - // Update the display name in the item in auto-complete list. - TextView nameView = (TextView) view.findViewById(R.id.location_name); - if (nameView != null) { - if (result.mName == null) { - nameView.setVisibility(View.GONE); - } else { - nameView.setVisibility(View.VISIBLE); - nameView.setText(result.mName); - } - } - - // Update the address line. - TextView addressView = (TextView) view.findViewById(R.id.location_address); - if (addressView != null) { - addressView.setText(result.mAddress); - } - - // Update the icon. - final ImageView imageView = (ImageView) view.findViewById(R.id.icon); - if (imageView != null) { - if (result.mDefaultIcon == null) { - imageView.setVisibility(View.INVISIBLE); - } else { - imageView.setVisibility(View.VISIBLE); - imageView.setImageResource(result.mDefaultIcon); - - // Save the URI on the view, so we can check against it later when updating - // the image. Otherwise the async image update with using 'convertView' above - // resulted in the wrong list items being updated. - imageView.setTag(result.mContactPhotoUri); - if (result.mContactPhotoUri != null) { - Bitmap cachedPhoto = mPhotoCache.get(result.mContactPhotoUri); - if (cachedPhoto != null) { - // Use photo in cache. - imageView.setImageBitmap(cachedPhoto); - } else { - // Asynchronously load photo and update. - asyncLoadPhotoAndUpdateView(result.mContactPhotoUri, imageView); - } - } - } - } - return view; - } - - // TODO: Refactor to share code with ContactsAsyncHelper. - private void asyncLoadPhotoAndUpdateView(final Uri contactPhotoUri, - final ImageView imageView) { - AsyncTask<Void, Void, Bitmap> photoUpdaterTask = - new AsyncTask<Void, Void, Bitmap>() { - @Override - protected Bitmap doInBackground(Void... params) { - Bitmap photo = null; - InputStream imageStream = Contacts.openContactPhotoInputStream( - mResolver, contactPhotoUri); - if (imageStream != null) { - photo = BitmapFactory.decodeStream(imageStream); - mPhotoCache.put(contactPhotoUri, photo); - } - return photo; - } - - @Override - public void onPostExecute(Bitmap photo) { - // The View may have already been reused (because using 'convertView' above), so - // we must check the URI is as expected before setting the icon, or we may be - // setting the icon in other items. - if (photo != null && imageView.getTag() == contactPhotoUri) { - imageView.setImageBitmap(photo); - } - } - }.execute(); - } - - /** - * Return filter for matching against contacts info and recent locations. - */ - @Override - public Filter getFilter() { - return new LocationFilter(); - } - - /** - * Filter implementation for matching the input string against contacts info and - * recent locations. - */ - public class LocationFilter extends Filter { - - @Override - protected FilterResults performFiltering(CharSequence constraint) { - long startTime = System.currentTimeMillis(); - final String filter = constraint == null ? "" : constraint.toString(); - if (filter.isEmpty()) { - return null; - } - - // Start the recent locations query (async). - AsyncTask<Void, Void, List<Result>> locationsQueryTask = - new AsyncTask<Void, Void, List<Result>>() { - @Override - protected List<Result> doInBackground(Void... params) { - return queryRecentLocations(mResolver, filter); - } - }.execute(); - - // Perform the contacts query (sync). - HashSet<String> contactsAddresses = new HashSet<String>(); - List<Result> contacts = queryContacts(mResolver, filter, contactsAddresses); - - ArrayList<Result> resultList = new ArrayList<Result>(); - try { - // Wait for the locations query. - List<Result> recentLocations = locationsQueryTask.get(); - - // Add the matched recent locations to returned results. If a match exists in - // both the recent locations query and the contacts addresses, only display it - // as a contacts match. - for (Result recentLocation : recentLocations) { - if (recentLocation.mAddress != null && - !contactsAddresses.contains(recentLocation.mAddress)) { - resultList.add(recentLocation); - } - } - } catch (ExecutionException e) { - Log.e(TAG, "Failed waiting for locations query results.", e); - } catch (InterruptedException e) { - Log.e(TAG, "Failed waiting for locations query results.", e); - } - - // Add all the contacts matches to returned results. - if (contacts != null) { - resultList.addAll(contacts); - } - - // Log the processing duration. - if (Log.isLoggable(TAG, Log.DEBUG)) { - long duration = System.currentTimeMillis() - startTime; - StringBuilder msg = new StringBuilder(); - msg.append("Autocomplete of ").append(constraint); - msg.append(": location query match took ").append(duration).append("ms "); - msg.append("(").append(resultList.size()).append(" results)"); - Log.d(TAG, msg.toString()); - } - - final FilterResults filterResults = new FilterResults(); - filterResults.values = resultList; - filterResults.count = resultList.size(); - return filterResults; - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - mResultList.clear(); - if (results != null && results.count > 0) { - mResultList.addAll((ArrayList<Result>) results.values); - notifyDataSetChanged(); - } else { - notifyDataSetInvalidated(); - } - } - } - - /** - * Matches the input string against contacts names and addresses. - * - * @param resolver The content resolver. - * @param input The user-typed input string. - * @param addressesRetVal The addresses in the returned result are also returned here - * for faster lookup. Pass in an empty set. - * @return Ordered list of all the matched results. If there are multiple address matches - * for the same contact, they will be listed together in individual items, with only - * the first item containing a name/icon. - */ - private static List<Result> queryContacts(ContentResolver resolver, String input, - HashSet<String> addressesRetVal) { - String where = null; - String[] whereArgs = null; - - // Match any word in contact name or address. - if (!TextUtils.isEmpty(input)) { - where = CONTACTS_WHERE; - String param1 = input + "%"; - String param2 = "% " + input + "%"; - whereArgs = new String[] {param1, param2, param1, param2}; - } - - // Perform the query. - Cursor c = resolver.query(CommonDataKinds.StructuredPostal.CONTENT_URI, - CONTACTS_PROJECTION, where, whereArgs, Contacts.DISPLAY_NAME + " ASC"); - - // Process results. Group together addresses for the same contact. - try { - Map<String, List<Result>> nameToAddresses = new HashMap<String, List<Result>>(); - c.moveToPosition(-1); - while (c.moveToNext()) { - String name = c.getString(CONTACTS_INDEX_DISPLAY_NAME); - String address = c.getString(CONTACTS_INDEX_ADDRESS); - if (name != null) { - - List<Result> addressesForName = nameToAddresses.get(name); - Result result; - if (addressesForName == null) { - // Determine if there is a photo for the icon. - Uri contactPhotoUri = null; - if (c.getLong(CONTACTS_INDEX_PHOTO_ID) > 0) { - contactPhotoUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, - c.getLong(CONTACTS_INDEX_CONTACT_ID)); - } - - // First listing for a distinct contact should have the name/icon. - addressesForName = new ArrayList<Result>(); - nameToAddresses.put(name, addressesForName); - result = new Result(name, address, R.drawable.ic_contact_picture, - contactPhotoUri); - } else { - // Do not include name/icon in subsequent listings for the same contact. - result = new Result(null, address, null, null); - } - - addressesForName.add(result); - addressesRetVal.add(address); - } - } - - // Return the list of results. - List<Result> allResults = new ArrayList<Result>(); - for (List<Result> result : nameToAddresses.values()) { - allResults.addAll(result); - } - return allResults; - - } finally { - if (c != null) { - c.close(); - } - } - } - - /** - * Matches the input string against recent locations. - */ - private static List<Result> queryRecentLocations(ContentResolver resolver, String input) { - // TODO: also match each word in the address? - String filter = input == null ? "" : input + "%"; - if (filter.isEmpty()) { - return null; - } - - // Query all locations prefixed with the constraint. There is no way to insert - // 'DISTINCT' or 'GROUP BY' to get rid of dupes, so use post-processing to - // remove dupes. We will order query results by descending event ID to show - // results that were most recently inputed. - Cursor c = resolver.query(Events.CONTENT_URI, EVENT_PROJECTION, LOCATION_WHERE, - new String[] { "1", filter }, Events._ID + " DESC"); - try { - List<Result> recentLocations = null; - if (c != null) { - // Post process query results. - recentLocations = processLocationsQueryResults(c); - } - return recentLocations; - } finally { - if (c != null) { - c.close(); - } - } - } - - /** - * Post-process the query results to return the first MAX_LOCATION_SUGGESTIONS - * unique locations in alphabetical order. - * - * TODO: Refactor to share code with the recent titles auto-complete. - */ - private static List<Result> processLocationsQueryResults(Cursor cursor) { - TreeSet<String> locations = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); - cursor.moveToPosition(-1); - - // Remove dupes. - while ((locations.size() < MAX_LOCATION_SUGGESTIONS) && cursor.moveToNext()) { - String location = cursor.getString(EVENT_INDEX_LOCATION).trim(); - locations.add(location); - } - - // Copy the sorted results. - List<Result> results = new ArrayList<Result>(); - for (String location : locations) { - results.add(new Result(null, location, R.drawable.ic_history_holo_light, null)); - } - return results; - } -} diff --git a/src/com/android/calendar/event/EventViewUtils.java b/src/com/android/calendar/event/EventViewUtils.java deleted file mode 100644 index e5bdb4b9..00000000 --- a/src/com/android/calendar/event/EventViewUtils.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.calendar.event; - -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.R; - -import android.app.Activity; -import android.content.Context; -import android.content.res.Resources; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.Spinner; - -import java.util.ArrayList; - -public class EventViewUtils { - private static final String TAG = "EventViewUtils"; - - private EventViewUtils() { - } - - // Constructs a label given an arbitrary number of minutes. For example, - // if the given minutes is 63, then this returns the string "63 minutes". - // As another example, if the given minutes is 120, then this returns - // "2 hours". - public static String constructReminderLabel(Context context, int minutes, boolean abbrev) { - Resources resources = context.getResources(); - int value, resId; - - if (minutes % 60 != 0) { - value = minutes; - if (abbrev) { - resId = R.plurals.Nmins; - } else { - resId = R.plurals.Nminutes; - } - } else if (minutes % (24 * 60) != 0) { - value = minutes / 60; - resId = R.plurals.Nhours; - } else { - value = minutes / (24 * 60); - resId = R.plurals.Ndays; - } - - String format = resources.getQuantityString(resId, value); - return String.format(format, value); - } - - /** - * Finds the index of the given "minutes" in the "values" list. - * - * @param values the list of minutes corresponding to the spinner choices - * @param minutes the minutes to search for in the values list - * @return the index of "minutes" in the "values" list - */ - public static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) { - int index = values.indexOf(minutes); - if (index == -1) { - // This should never happen. - Log.e(TAG, "Cannot find minutes (" + minutes + ") in list"); - return 0; - } - return index; - } - - /** - * Finds the index of the given method in the "methods" list. If the method isn't present - * (perhaps because we don't think it's allowed for this calendar), we return zero (the - * first item in the list). - * <p> - * With the current definitions, this effectively converts DEFAULT and unsupported method - * types to ALERT. - * - * @param values the list of minutes corresponding to the spinner choices - * @param method the method to search for in the values list - * @return the index of the method in the "values" list - */ - public static int findMethodInReminderList(ArrayList<Integer> values, int method) { - int index = values.indexOf(method); - if (index == -1) { - // If not allowed, or undefined, just use the first entry in the list. - //Log.d(TAG, "Cannot find method (" + method + ") in allowed list"); - index = 0; - } - return index; - } - - /** - * Extracts reminder minutes info from UI elements. - * - * @param reminderItems UI elements (layouts with spinners) that hold array indices. - * @param reminderMinuteValues Maps array index to time in minutes. - * @param reminderMethodValues Maps array index to alert method constant. - * @return Array with reminder data. - */ - public static ArrayList<ReminderEntry> reminderItemsToReminders( - ArrayList<LinearLayout> reminderItems, ArrayList<Integer> reminderMinuteValues, - ArrayList<Integer> reminderMethodValues) { - int len = reminderItems.size(); - ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>(len); - for (int index = 0; index < len; index++) { - LinearLayout layout = reminderItems.get(index); - Spinner minuteSpinner = (Spinner) layout.findViewById(R.id.reminder_minutes_value); - Spinner methodSpinner = (Spinner) layout.findViewById(R.id.reminder_method_value); - int minutes = reminderMinuteValues.get(minuteSpinner.getSelectedItemPosition()); - int method = reminderMethodValues.get(methodSpinner.getSelectedItemPosition()); - reminders.add(ReminderEntry.valueOf(minutes, method)); - } - return reminders; - } - - /** - * If "minutes" is not currently present in "values", we add an appropriate new entry - * to values and labels. - */ - public static void addMinutesToList(Context context, ArrayList<Integer> values, - ArrayList<String> labels, int minutes) { - int index = values.indexOf(minutes); - if (index != -1) { - return; - } - - // The requested "minutes" does not exist in the list, so insert it - // into the list. - - String label = constructReminderLabel(context, minutes, false); - int len = values.size(); - for (int i = 0; i < len; i++) { - if (minutes < values.get(i)) { - values.add(i, minutes); - labels.add(i, label); - return; - } - } - - values.add(minutes); - labels.add(len, label); - } - - /** - * Remove entries from the method list that aren't allowed for this calendar. - * - * @param values List of known method values. - * @param labels List of known method labels. - * @param allowedMethods Has the form "0,1,3", indicating method constants from Reminders. - */ - public static void reduceMethodList(ArrayList<Integer> values, ArrayList<String> labels, - String allowedMethods) - { - // Parse "allowedMethods". - String[] allowedStrings = allowedMethods.split(","); - int[] allowedValues = new int[allowedStrings.length]; - - for (int i = 0; i < allowedValues.length; i++) { - try { - allowedValues[i] = Integer.parseInt(allowedStrings[i], 10); - } catch (NumberFormatException nfe) { - Log.w(TAG, "Bad allowed-strings list: '" + allowedStrings[i] + - "' in '" + allowedMethods + "'"); - return; - } - } - - // Walk through the method list, removing entries that aren't in the allowed list. - for (int i = values.size() - 1; i >= 0; i--) { - int val = values.get(i); - int j; - - for (j = allowedValues.length - 1; j >= 0; j--) { - if (val == allowedValues[j]) { - break; - } - } - if (j < 0) { - values.remove(i); - labels.remove(i); - } - } - } - - /** - * Set the list of labels on a reminder spinner. - */ - private static void setReminderSpinnerLabels(Activity activity, Spinner spinner, - ArrayList<String> labels) { - 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); - } - - /** - * Adds a reminder to the displayed list of reminders. The values/labels - * arrays must not change after calling here, or the spinners we created - * might index into the wrong entry. Returns true if successfully added - * reminder, false if no reminders can be added. - * - * onItemSelected allows a listener to be set for any changes to the - * spinners in the reminder. If a listener is set it will store the - * initial position of the spinner into the spinner's tag for comparison - * with any new position setting. - */ - public static boolean addReminder(Activity activity, View view, View.OnClickListener listener, - ArrayList<LinearLayout> items, ArrayList<Integer> minuteValues, - ArrayList<String> minuteLabels, ArrayList<Integer> methodValues, - ArrayList<String> methodLabels, ReminderEntry newReminder, int maxReminders, - OnItemSelectedListener onItemSelected) { - - if (items.size() >= maxReminders) { - return false; - } - - LayoutInflater inflater = activity.getLayoutInflater(); - LinearLayout parent = (LinearLayout) view.findViewById(R.id.reminder_items_container); - LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, - null); - parent.addView(reminderItem); - - ImageButton reminderRemoveButton; - reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove); - reminderRemoveButton.setOnClickListener(listener); - - /* - * The spinner has the default set of labels from the string resource file, but we - * want to drop in our custom set of labels because it may have additional entries. - */ - Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_minutes_value); - setReminderSpinnerLabels(activity, spinner, minuteLabels); - - int index = findMinutesInReminderList(minuteValues, newReminder.getMinutes()); - spinner.setSelection(index); - - if (onItemSelected != null) { - spinner.setTag(index); - spinner.setOnItemSelectedListener(onItemSelected); - } - - /* - * Configure the alert-method spinner. Methods not supported by the current Calendar - * will not be shown. - */ - spinner = (Spinner) reminderItem.findViewById(R.id.reminder_method_value); - setReminderSpinnerLabels(activity, spinner, methodLabels); - - index = findMethodInReminderList(methodValues, newReminder.getMethod()); - spinner.setSelection(index); - - if (onItemSelected != null) { - spinner.setTag(index); - spinner.setOnItemSelectedListener(onItemSelected); - } - - items.add(reminderItem); - - return true; - } - - /** - * Enables/disables the 'add reminder' button depending on the current number of - * reminders. - */ - public static void updateAddReminderButton(View view, ArrayList<LinearLayout> reminders, - int maxReminders) { - View reminderAddButton = view.findViewById(R.id.reminder_add); - if (reminderAddButton != null) { - if (reminders.size() >= maxReminders) { - reminderAddButton.setEnabled(false); - reminderAddButton.setVisibility(View.GONE); - } else { - reminderAddButton.setEnabled(true); - reminderAddButton.setVisibility(View.VISIBLE); - } - } - } -} diff --git a/src/com/android/calendar/month/MonthByWeekAdapter.java b/src/com/android/calendar/month/MonthByWeekAdapter.java index 64b734fa..45a1bea1 100644 --- a/src/com/android/calendar/month/MonthByWeekAdapter.java +++ b/src/com/android/calendar/month/MonthByWeekAdapter.java @@ -80,9 +80,8 @@ public class MonthByWeekAdapter extends SimpleWeeksAdapter { // Minimal distance to move the finger in order to cancel the click animation private static float mMovedPixelToCancel; - public MonthByWeekAdapter(Context context, HashMap<String, Integer> params, Handler handler) { + public MonthByWeekAdapter(Context context, HashMap<String, Integer> params) { super(context, params); - mEventDialogHandler = handler; if (params.containsKey(WEEK_PARAMS_IS_MINI)) { mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0; } @@ -248,32 +247,9 @@ public class MonthByWeekAdapter extends SimpleWeeksAdapter { } v.setWeekParams(drawingParams, mSelectedDay.timezone); - sendEventsToView(v); return v; } - private void sendEventsToView(MonthWeekEventsView v) { - if (mEventDayList.size() == 0) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "No events loaded, did not pass any events to view."); - } - v.setEvents(null, null); - return; - } - int viewJulianDay = v.getFirstJulianDay(); - int start = viewJulianDay - mFirstJulianDay; - int end = start + v.mNumDays; - if (start < 0 || end > mEventDayList.size()) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Week is outside range of loaded events. viewStart: " + viewJulianDay - + " eventsStart: " + mFirstJulianDay); - } - v.setEvents(null, null); - return; - } - v.setEvents(mEventDayList.subList(start, end), mEvents); - } - @Override protected void refresh() { mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext); @@ -375,7 +351,6 @@ public class MonthByWeekAdapter extends SimpleWeeksAdapter { mLongClickedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); Message message = new Message(); message.obj = day; - mEventDialogHandler.sendMessage(message); } mLongClickedView.clearClickedDay(); mLongClickedView = null; diff --git a/src/com/android/calendar/month/MonthByWeekFragment.java b/src/com/android/calendar/month/MonthByWeekFragment.java index 4dd24275..f8a518d3 100644 --- a/src/com/android/calendar/month/MonthByWeekFragment.java +++ b/src/com/android/calendar/month/MonthByWeekFragment.java @@ -51,7 +51,6 @@ import com.android.calendar.CalendarController.ViewType; import com.android.calendar.Event; import com.android.calendar.R; import com.android.calendar.Utils; -import com.android.calendar.event.CreateEventDialogFragment; import java.util.ArrayList; import java.util.Calendar; @@ -64,8 +63,6 @@ public class MonthByWeekFragment extends SimpleDayPickerFragment implements private static final String TAG = "MonthFragment"; private static final String TAG_EVENT_DIALOG = "event_dialog"; - private CreateEventDialogFragment mEventDialog; - // Selection and selection args for adding event queries private static final String WHERE_CALENDARS_VISIBLE = Calendars.VISIBLE + "=1"; private static final String INSTANCES_SORT_ORDER = Instances.START_DAY + "," @@ -99,20 +96,6 @@ public class MonthByWeekFragment extends SimpleDayPickerFragment implements private boolean mShowCalendarControls; private boolean mIsDetached; - private Handler mEventDialogHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - final FragmentManager manager = getFragmentManager(); - if (manager != null) { - Time day = (Time) msg.obj; - mEventDialog = new CreateEventDialogFragment(day); - mEventDialog.show(manager, TAG_EVENT_DIALOG); - } - } - }; - - private final Runnable mTZUpdater = new Runnable() { @Override public void run() { @@ -275,7 +258,7 @@ public class MonthByWeekFragment extends SimpleDayPickerFragment implements Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)); weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek); if (mAdapter == null) { - mAdapter = new MonthByWeekAdapter(getActivity(), weekParams, mEventDialogHandler); + mAdapter = new MonthByWeekAdapter(getActivity(), weekParams); mAdapter.registerDataSetObserver(mObserver); } else { mAdapter.updateParams(weekParams); @@ -507,8 +490,5 @@ public class MonthByWeekFragment extends SimpleDayPickerFragment implements public boolean onTouch(View v, MotionEvent event) { mDesiredDay.setToNow(); return false; - // TODO post a cleanup to push us back onto the grid if something went - // wrong in a scroll such as the user stopping the view but not - // scrolling } } diff --git a/src/com/android/calendar/month/MonthListView.java b/src/com/android/calendar/month/MonthListView.java index 11aa3dac..f2621ccb 100644 --- a/src/com/android/calendar/month/MonthListView.java +++ b/src/com/android/calendar/month/MonthListView.java @@ -17,13 +17,8 @@ package com.android.calendar.month; import android.content.Context; -import android.graphics.Rect; -import android.os.SystemClock; -import android.text.format.Time; import android.util.AttributeSet; import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; import android.widget.ListView; import com.android.calendar.Utils; @@ -31,167 +26,26 @@ import com.android.calendar.Utils; public class MonthListView extends ListView { private static final String TAG = "MonthListView"; - VelocityTracker mTracker; - private static float mScale = 0; - - // These define the behavior of the fling. Below MIN_VELOCITY_FOR_FLING, do the system fling - // behavior. Between MIN_VELOCITY_FOR_FLING and MULTIPLE_MONTH_VELOCITY_THRESHOLD, do one month - // fling. Above MULTIPLE_MONTH_VELOCITY_THRESHOLD, do multiple month flings according to the - // fling strength. When doing multiple month fling, the velocity is reduced by this threshold - // to prevent moving from one month fling to 4 months and above flings. - private static int MIN_VELOCITY_FOR_FLING = 1500; - private static int MULTIPLE_MONTH_VELOCITY_THRESHOLD = 2000; - private static int FLING_VELOCITY_DIVIDER = 500; - private static int FLING_TIME = 1000; - - // disposable variable used for time calculations - protected Time mTempTime; - private long mDownActionTime; - private final Rect mFirstViewRect = new Rect(); - - Context mListContext; - - // Updates the time zone when it changes - private final Runnable mTimezoneUpdater = new Runnable() { - @Override - public void run() { - if (mTempTime != null && mListContext != null) { - mTempTime.timezone = - Utils.getTimeZone(mListContext, mTimezoneUpdater); - } - } - }; public MonthListView(Context context) { super(context); - init(context); } public MonthListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - init(context); } public MonthListView(Context context, AttributeSet attrs) { super(context, attrs); - init(context); - } - - private void init(Context c) { - mListContext = c; - mTracker = VelocityTracker.obtain(); - mTempTime = new Time(Utils.getTimeZone(c,mTimezoneUpdater)); - if (mScale == 0) { - mScale = c.getResources().getDisplayMetrics().density; - if (mScale != 1) { - MIN_VELOCITY_FOR_FLING *= mScale; - MULTIPLE_MONTH_VELOCITY_THRESHOLD *= mScale; - FLING_VELOCITY_DIVIDER *= mScale; - } - } } @Override public boolean onTouchEvent(MotionEvent ev) { - return processEvent(ev) || super.onTouchEvent(ev); + return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return processEvent(ev) || super.onInterceptTouchEvent(ev); - } - - private boolean processEvent (MotionEvent ev) { - switch (ev.getAction() & MotionEvent.ACTION_MASK) { - // Since doFling sends a cancel, make sure not to process it. - case MotionEvent.ACTION_CANCEL: - return false; - // Start tracking movement velocity - case MotionEvent.ACTION_DOWN: - mTracker.clear(); - mDownActionTime = SystemClock.uptimeMillis(); - break; - // Accumulate velocity and do a custom fling when above threshold - case MotionEvent.ACTION_UP: - mTracker.addMovement(ev); - mTracker.computeCurrentVelocity(1000); // in pixels per second - float vel = mTracker.getYVelocity (); - if (Math.abs(vel) > MIN_VELOCITY_FOR_FLING) { - doFling(vel); - return true; - } - break; - default: - mTracker.addMovement(ev); - break; - } - return false; - } - - // Do a "snap to start of month" fling - private void doFling(float velocityY) { - - // Stop the list-view movement and take over - MotionEvent cancelEvent = MotionEvent.obtain(mDownActionTime, SystemClock.uptimeMillis(), - MotionEvent.ACTION_CANCEL, 0, 0, 0); - onTouchEvent(cancelEvent); - - // Below the threshold, fling one month. Above the threshold , fling - // according to the speed of the fling. - int monthsToJump; - if (Math.abs(velocityY) < MULTIPLE_MONTH_VELOCITY_THRESHOLD) { - if (velocityY < 0) { - monthsToJump = 1; - } else { - // value here is zero and not -1 since by the time the fling is - // detected the list moved back one month. - monthsToJump = 0; - } - } else { - if (velocityY < 0) { - monthsToJump = 1 - (int) ((velocityY + MULTIPLE_MONTH_VELOCITY_THRESHOLD) - / FLING_VELOCITY_DIVIDER); - } else { - monthsToJump = -(int) ((velocityY - MULTIPLE_MONTH_VELOCITY_THRESHOLD) - / FLING_VELOCITY_DIVIDER); - } - } - - // Get the day at the top right corner - int day = getUpperRightJulianDay(); - // Get the day of the first day of the next/previous month - // (according to scroll direction) - mTempTime.setJulianDay(day); - mTempTime.monthDay = 1; - mTempTime.month += monthsToJump; - long timeInMillis = mTempTime.normalize(false); - // Since each view is 7 days, round the target day up to make sure the - // scroll will be at least one view. - int scrollToDay = Time.getJulianDay(timeInMillis, mTempTime.gmtoff) - + ((monthsToJump > 0) ? 6 : 0); - - // Since all views have the same height, scroll by pixels instead of - // "to position". - // Compensate for the top view offset from the top. - View firstView = getChildAt(0); - int firstViewHeight = firstView.getHeight(); - // Get visible part length - firstView.getLocalVisibleRect(mFirstViewRect); - int topViewVisiblePart = mFirstViewRect.bottom - mFirstViewRect.top; - int viewsToFling = (scrollToDay - day) / 7 - ((monthsToJump <= 0) ? 1 : 0); - int offset = (viewsToFling > 0) ? -(firstViewHeight - topViewVisiblePart - + SimpleDayPickerFragment.LIST_TOP_OFFSET) : (topViewVisiblePart - - SimpleDayPickerFragment.LIST_TOP_OFFSET); - // Fling - smoothScrollBy(viewsToFling * firstViewHeight + offset, FLING_TIME); - } - - // Returns the julian day of the day in the upper right corner - private int getUpperRightJulianDay() { - SimpleWeekView child = (SimpleWeekView) getChildAt(0); - if (child == null) { - return -1; - } - return child.getFirstJulianDay() + SimpleDayPickerFragment.DAYS_PER_WEEK - 1; + return super.onInterceptTouchEvent(ev); } } diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.java b/src/com/android/calendar/month/SimpleDayPickerFragment.java index 89f1e005..2efae6a9 100644 --- a/src/com/android/calendar/month/SimpleDayPickerFragment.java +++ b/src/com/android/calendar/month/SimpleDayPickerFragment.java @@ -217,9 +217,6 @@ public class SimpleDayPickerFragment extends ListFragment implements OnScrollLis @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TIME)) { - goTo(savedInstanceState.getLong(KEY_CURRENT_TIME), false, true, true); - } } @Override @@ -606,30 +603,6 @@ public class SimpleDayPickerFragment extends ListFragment implements OnScrollLis if (mNewState == OnScrollListener.SCROLL_STATE_IDLE && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { mPreviousScrollState = mNewState; - // Uncomment the below to add snap to week back -// int i = 0; -// View child = mView.getChildAt(i); -// while (child != null && child.getBottom() <= 0) { -// child = mView.getChildAt(++i); -// } -// if (child == null) { -// // The view is no longer visible, just return -// return; -// } -// int dist = child.getTop(); -// if (dist < LIST_TOP_OFFSET) { -// if (Log.isLoggable(TAG, Log.DEBUG)) { -// Log.d(TAG, "scrolling by " + dist + " up? " + mIsScrollingUp); -// } -// int firstPosition = mView.getFirstVisiblePosition(); -// int lastPosition = mView.getLastVisiblePosition(); -// boolean scroll = firstPosition != 0 && lastPosition != mView.getCount() - 1; -// if (mIsScrollingUp && scroll) { -// mView.smoothScrollBy(dist, 500); -// } else if (!mIsScrollingUp && scroll) { -// mView.smoothScrollBy(child.getHeight() + dist, 500); -// } -// } mAdapter.updateFocusMonth(mCurrentMonthDisplayed); } else { mPreviousScrollState = mNewState; diff --git a/src/com/android/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java b/src/com/android/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java deleted file mode 100644 index 455a039c..00000000 --- a/src/com/android/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.recurrencepicker; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -public class LinearLayoutWithMaxWidth extends LinearLayout { - - public LinearLayoutWithMaxWidth(Context context) { - super(context); - } - - public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - WeekButton.setSuggestedWidth((View.MeasureSpec.getSize(widthMeasureSpec)) / 7); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } -} diff --git a/src/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java b/src/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java deleted file mode 100644 index 73591dd9..00000000 --- a/src/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java +++ /dev/null @@ -1,1322 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.recurrencepicker; - -import android.app.Activity; -import android.app.DialogFragment; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Log; -import android.util.TimeFormatException; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Spinner; -import android.widget.Switch; -import android.widget.TableLayout; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ToggleButton; - -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendarcommon2.EventRecurrence; -import com.android.datetimepicker.date.DatePickerDialog; - -import java.text.DateFormatSymbols; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; - -public class RecurrencePickerDialog extends DialogFragment implements OnItemSelectedListener, - OnCheckedChangeListener, OnClickListener, - android.widget.RadioGroup.OnCheckedChangeListener, DatePickerDialog.OnDateSetListener { - - private static final String TAG = "RecurrencePickerDialog"; - - // in dp's - private static final int MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK = 450; - - // Update android:maxLength in EditText as needed - private static final int INTERVAL_MAX = 99; - private static final int INTERVAL_DEFAULT = 1; - // Update android:maxLength in EditText as needed - private static final int COUNT_MAX = 730; - private static final int COUNT_DEFAULT = 5; - - // Special cases in monthlyByNthDayOfWeek - private static final int FIFTH_WEEK_IN_A_MONTH = 5; - private static final int LAST_NTH_DAY_OF_WEEK = -1; - - private DatePickerDialog mDatePickerDialog; - - private class RecurrenceModel implements Parcelable { - - // Should match EventRecurrence.DAILY, etc - static final int FREQ_DAILY = 0; - static final int FREQ_WEEKLY = 1; - static final int FREQ_MONTHLY = 2; - static final int FREQ_YEARLY = 3; - - static final int END_NEVER = 0; - static final int END_BY_DATE = 1; - static final int END_BY_COUNT = 2; - - static final int MONTHLY_BY_DATE = 0; - static final int MONTHLY_BY_NTH_DAY_OF_WEEK = 1; - - static final int STATE_NO_RECURRENCE = 0; - static final int STATE_RECURRENCE = 1; - - int recurrenceState; - - /** - * FREQ: Repeat pattern - * - * @see FREQ_DAILY - * @see FREQ_WEEKLY - * @see FREQ_MONTHLY - * @see FREQ_YEARLY - */ - int freq = FREQ_WEEKLY; - - /** - * INTERVAL: Every n days/weeks/months/years. n >= 1 - */ - int interval = INTERVAL_DEFAULT; - - /** - * UNTIL and COUNT: How does the the event end? - * - * @see END_NEVER - * @see END_BY_DATE - * @see END_BY_COUNT - * @see untilDate - * @see untilCount - */ - int end; - - /** - * UNTIL: Date of the last recurrence. Used when until == END_BY_DATE - */ - Time endDate; - - /** - * COUNT: Times to repeat. Use when until == END_BY_COUNT - */ - int endCount = COUNT_DEFAULT; - - /** - * BYDAY: Days of the week to be repeated. Sun = 0, Mon = 1, etc - */ - boolean[] weeklyByDayOfWeek = new boolean[7]; - - /** - * BYDAY AND BYMONTHDAY: How to repeat monthly events? Same date of the - * month or Same nth day of week. - * - * @see MONTHLY_BY_DATE - * @see MONTHLY_BY_NTH_DAY_OF_WEEK - */ - int monthlyRepeat; - - /** - * Day of the month to repeat. Used when monthlyRepeat == - * MONTHLY_BY_DATE - */ - int monthlyByMonthDay; - - /** - * Day of the week to repeat. Used when monthlyRepeat == - * MONTHLY_BY_NTH_DAY_OF_WEEK - */ - int monthlyByDayOfWeek; - - /** - * Nth day of the week to repeat. Used when monthlyRepeat == - * MONTHLY_BY_NTH_DAY_OF_WEEK 0=undefined, -1=Last, 1=1st, 2=2nd, ..., 5=5th - * - * We support 5th, just to handle backwards capabilities with old bug, but it - * gets converted to -1 once edited. - */ - int monthlyByNthDayOfWeek; - - /* - * (generated method) - */ - @Override - public String toString() { - return "Model [freq=" + freq + ", interval=" + interval + ", end=" + end + ", endDate=" - + endDate + ", endCount=" + endCount + ", weeklyByDayOfWeek=" - + Arrays.toString(weeklyByDayOfWeek) + ", monthlyRepeat=" + monthlyRepeat - + ", monthlyByMonthDay=" + monthlyByMonthDay + ", monthlyByDayOfWeek=" - + monthlyByDayOfWeek + ", monthlyByNthDayOfWeek=" + monthlyByNthDayOfWeek + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - public RecurrenceModel() { - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(freq); - dest.writeInt(interval); - dest.writeInt(end); - dest.writeInt(endDate.year); - dest.writeInt(endDate.month); - dest.writeInt(endDate.monthDay); - dest.writeInt(endCount); - dest.writeBooleanArray(weeklyByDayOfWeek); - dest.writeInt(monthlyRepeat); - dest.writeInt(monthlyByMonthDay); - dest.writeInt(monthlyByDayOfWeek); - dest.writeInt(monthlyByNthDayOfWeek); - dest.writeInt(recurrenceState); - } - } - - class minMaxTextWatcher implements TextWatcher { - private int mMin; - private int mMax; - private int mDefault; - - public minMaxTextWatcher(int min, int defaultInt, int max) { - mMin = min; - mMax = max; - mDefault = defaultInt; - } - - @Override - public void afterTextChanged(Editable s) { - - boolean updated = false; - int value; - try { - value = Integer.parseInt(s.toString()); - } catch (NumberFormatException e) { - value = mDefault; - } - - if (value < mMin) { - value = mMin; - updated = true; - } else if (value > mMax) { - updated = true; - value = mMax; - } - - // Update UI - if (updated) { - s.clear(); - s.append(Integer.toString(value)); - } - - updateDoneButtonState(); - onChange(value); - } - - /** Override to be called after each key stroke */ - void onChange(int value) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - } - - private Resources mResources; - private EventRecurrence mRecurrence = new EventRecurrence(); - private Time mTime = new Time(); // TODO timezone? - private RecurrenceModel mModel = new RecurrenceModel(); - private Toast mToast; - - private final int[] TIME_DAY_TO_CALENDAR_DAY = new int[] { - Calendar.SUNDAY, - Calendar.MONDAY, - Calendar.TUESDAY, - Calendar.WEDNESDAY, - Calendar.THURSDAY, - Calendar.FRIDAY, - Calendar.SATURDAY, - }; - - // Call mStringBuilder.setLength(0) before formatting any string or else the - // formatted text will accumulate. - // private final StringBuilder mStringBuilder = new StringBuilder(); - // private Formatter mFormatter = new Formatter(mStringBuilder); - - private View mView; - - private Spinner mFreqSpinner; - private static final int[] mFreqModelToEventRecurrence = { - EventRecurrence.DAILY, - EventRecurrence.WEEKLY, - EventRecurrence.MONTHLY, - EventRecurrence.YEARLY - }; - - public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time"; - public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone"; - public static final String BUNDLE_RRULE = "bundle_event_rrule"; - - private static final String BUNDLE_MODEL = "bundle_model"; - private static final String BUNDLE_END_COUNT_HAS_FOCUS = "bundle_end_count_has_focus"; - - private static final String FRAG_TAG_DATE_PICKER = "tag_date_picker_frag"; - - private Switch mRepeatSwitch; - - private EditText mInterval; - private TextView mIntervalPreText; - private TextView mIntervalPostText; - - private int mIntervalResId = -1; - - private Spinner mEndSpinner; - private TextView mEndDateTextView; - private EditText mEndCount; - private TextView mPostEndCount; - private boolean mHidePostEndCount; - - private ArrayList<CharSequence> mEndSpinnerArray = new ArrayList<CharSequence>(3); - private EndSpinnerAdapter mEndSpinnerAdapter; - private String mEndNeverStr; - private String mEndDateLabel; - private String mEndCountLabel; - - /** Hold toggle buttons in the order per user's first day of week preference */ - private LinearLayout mWeekGroup; - private LinearLayout mWeekGroup2; - // Sun = 0 - private ToggleButton[] mWeekByDayButtons = new ToggleButton[7]; - /** A double array of Strings to hold the 7x5 list of possible strings of the form: - * "on every [Nth] [DAY_OF_WEEK]", e.g. "on every second Monday", - * where [Nth] can be [first, second, third, fourth, last] */ - private String[][] mMonthRepeatByDayOfWeekStrs; - - private LinearLayout mMonthGroup; - private RadioGroup mMonthRepeatByRadioGroup; - private RadioButton mRepeatMonthlyByNthDayOfWeek; - private RadioButton mRepeatMonthlyByNthDayOfMonth; - private String mMonthRepeatByDayOfWeekStr; - - private Button mDone; - - private OnRecurrenceSetListener mRecurrenceSetListener; - - public RecurrencePickerDialog() { - } - - static public boolean isSupportedMonthlyByNthDayOfWeek(int num) { - // We only support monthlyByNthDayOfWeek when it is greater then 0 but less then 5. - // Or if -1 when it is the last monthly day of the week. - return (num > 0 && num <= FIFTH_WEEK_IN_A_MONTH) || num == LAST_NTH_DAY_OF_WEEK; - } - - static public boolean canHandleRecurrenceRule(EventRecurrence er) { - switch (er.freq) { - case EventRecurrence.DAILY: - case EventRecurrence.MONTHLY: - case EventRecurrence.YEARLY: - case EventRecurrence.WEEKLY: - break; - default: - return false; - } - - if (er.count > 0 && !TextUtils.isEmpty(er.until)) { - return false; - } - - // Weekly: For "repeat by day of week", the day of week to repeat is in - // er.byday[] - - /* - * Monthly: For "repeat by nth day of week" the day of week to repeat is - * in er.byday[] and the "nth" is stored in er.bydayNum[]. Currently we - * can handle only one and only in monthly - */ - int numOfByDayNum = 0; - for (int i = 0; i < er.bydayCount; i++) { - if (isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) { - ++numOfByDayNum; - } - } - - if (numOfByDayNum > 1) { - return false; - } - - if (numOfByDayNum > 0 && er.freq != EventRecurrence.MONTHLY) { - return false; - } - - // The UI only handle repeat by one day of month i.e. not 9th and 10th - // of every month - if (er.bymonthdayCount > 1) { - return false; - } - - if (er.freq == EventRecurrence.MONTHLY) { - if (er.bydayCount > 1) { - return false; - } - if (er.bydayCount > 0 && er.bymonthdayCount > 0) { - return false; - } - } - - return true; - } - - // TODO don't lose data when getting data that our UI can't handle - static private void copyEventRecurrenceToModel(final EventRecurrence er, - RecurrenceModel model) { - // Freq: - switch (er.freq) { - case EventRecurrence.DAILY: - model.freq = RecurrenceModel.FREQ_DAILY; - break; - case EventRecurrence.MONTHLY: - model.freq = RecurrenceModel.FREQ_MONTHLY; - break; - case EventRecurrence.YEARLY: - model.freq = RecurrenceModel.FREQ_YEARLY; - break; - case EventRecurrence.WEEKLY: - model.freq = RecurrenceModel.FREQ_WEEKLY; - break; - default: - throw new IllegalStateException("freq=" + er.freq); - } - - // Interval: - if (er.interval > 0) { - model.interval = er.interval; - } - - // End: - // End by count: - model.endCount = er.count; - if (model.endCount > 0) { - model.end = RecurrenceModel.END_BY_COUNT; - } - - // End by date: - if (!TextUtils.isEmpty(er.until)) { - if (model.endDate == null) { - model.endDate = new Time(); - } - - try { - model.endDate.parse(er.until); - } catch (TimeFormatException e) { - model.endDate = null; - } - - // LIMITATION: The UI can only handle END_BY_DATE or END_BY_COUNT - if (model.end == RecurrenceModel.END_BY_COUNT && model.endDate != null) { - throw new IllegalStateException("freq=" + er.freq); - } - - model.end = RecurrenceModel.END_BY_DATE; - } - - // Weekly: repeat by day of week or Monthly: repeat by nth day of week - // in the month - Arrays.fill(model.weeklyByDayOfWeek, false); - if (er.bydayCount > 0) { - int count = 0; - for (int i = 0; i < er.bydayCount; i++) { - int dayOfWeek = EventRecurrence.day2TimeDay(er.byday[i]); - model.weeklyByDayOfWeek[dayOfWeek] = true; - - if (model.freq == RecurrenceModel.FREQ_MONTHLY && - isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) { - // LIMITATION: Can handle only (one) weekDayNum in nth or last and only - // when - // monthly - model.monthlyByDayOfWeek = dayOfWeek; - model.monthlyByNthDayOfWeek = er.bydayNum[i]; - model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK; - count++; - } - } - - if (model.freq == RecurrenceModel.FREQ_MONTHLY) { - if (er.bydayCount != 1) { - // Can't handle 1st Monday and 2nd Wed - throw new IllegalStateException("Can handle only 1 byDayOfWeek in monthly"); - } - if (count != 1) { - throw new IllegalStateException( - "Didn't specify which nth day of week to repeat for a monthly"); - } - } - } - - // Monthly by day of month - if (model.freq == RecurrenceModel.FREQ_MONTHLY) { - if (er.bymonthdayCount == 1) { - if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { - throw new IllegalStateException( - "Can handle only by monthday or by nth day of week, not both"); - } - model.monthlyByMonthDay = er.bymonthday[0]; - model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE; - } else if (er.bymonthCount > 1) { - // LIMITATION: Can handle only one month day - throw new IllegalStateException("Can handle only one bymonthday"); - } - } - } - - static private void copyModelToEventRecurrence(final RecurrenceModel model, - EventRecurrence er) { - if (model.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { - throw new IllegalStateException("There's no recurrence"); - } - - // Freq - er.freq = mFreqModelToEventRecurrence[model.freq]; - - // Interval - if (model.interval <= 1) { - er.interval = 0; - } else { - er.interval = model.interval; - } - - // End - switch (model.end) { - case RecurrenceModel.END_BY_DATE: - if (model.endDate != null) { - model.endDate.switchTimezone(Time.TIMEZONE_UTC); - model.endDate.normalize(false); - er.until = model.endDate.format2445(); - er.count = 0; - } else { - throw new IllegalStateException("end = END_BY_DATE but endDate is null"); - } - break; - case RecurrenceModel.END_BY_COUNT: - er.count = model.endCount; - er.until = null; - if (er.count <= 0) { - throw new IllegalStateException("count is " + er.count); - } - break; - default: - er.count = 0; - er.until = null; - break; - } - - // Weekly && monthly repeat patterns - er.bydayCount = 0; - er.bymonthdayCount = 0; - - switch (model.freq) { - case RecurrenceModel.FREQ_MONTHLY: - if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) { - if (model.monthlyByMonthDay > 0) { - if (er.bymonthday == null || er.bymonthdayCount < 1) { - er.bymonthday = new int[1]; - } - er.bymonthday[0] = model.monthlyByMonthDay; - er.bymonthdayCount = 1; - } - } else if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { - if (!isSupportedMonthlyByNthDayOfWeek(model.monthlyByNthDayOfWeek)) { - throw new IllegalStateException("month repeat by nth week but n is " - + model.monthlyByNthDayOfWeek); - } - int count = 1; - if (er.bydayCount < count || er.byday == null || er.bydayNum == null) { - er.byday = new int[count]; - er.bydayNum = new int[count]; - } - er.bydayCount = count; - er.byday[0] = EventRecurrence.timeDay2Day(model.monthlyByDayOfWeek); - er.bydayNum[0] = model.monthlyByNthDayOfWeek; - } - break; - case RecurrenceModel.FREQ_WEEKLY: - int count = 0; - for (int i = 0; i < 7; i++) { - if (model.weeklyByDayOfWeek[i]) { - count++; - } - } - - if (er.bydayCount < count || er.byday == null || er.bydayNum == null) { - er.byday = new int[count]; - er.bydayNum = new int[count]; - } - er.bydayCount = count; - - for (int i = 6; i >= 0; i--) { - if (model.weeklyByDayOfWeek[i]) { - er.bydayNum[--count] = 0; - er.byday[count] = EventRecurrence.timeDay2Day(i); - } - } - break; - } - - if (!canHandleRecurrenceRule(er)) { - throw new IllegalStateException("UI generated recurrence that it can't handle. ER:" - + er.toString() + " Model: " + model.toString()); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mRecurrence.wkst = EventRecurrence.timeDay2Day(Utils.getFirstDayOfWeek(getActivity())); - - getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); - - boolean endCountHasFocus = false; - if (savedInstanceState != null) { - RecurrenceModel m = (RecurrenceModel) savedInstanceState.get(BUNDLE_MODEL); - if (m != null) { - mModel = m; - } - endCountHasFocus = savedInstanceState.getBoolean(BUNDLE_END_COUNT_HAS_FOCUS); - } else { - Bundle b = getArguments(); - if (b != null) { - mTime.set(b.getLong(BUNDLE_START_TIME_MILLIS)); - - String tz = b.getString(BUNDLE_TIME_ZONE); - if (!TextUtils.isEmpty(tz)) { - mTime.timezone = tz; - } - mTime.normalize(false); - - // Time days of week: Sun=0, Mon=1, etc - mModel.weeklyByDayOfWeek[mTime.weekDay] = true; - String rrule = b.getString(BUNDLE_RRULE); - if (!TextUtils.isEmpty(rrule)) { - mModel.recurrenceState = RecurrenceModel.STATE_RECURRENCE; - mRecurrence.parse(rrule); - copyEventRecurrenceToModel(mRecurrence, mModel); - // Leave today's day of week as checked by default in weekly view. - if (mRecurrence.bydayCount == 0) { - mModel.weeklyByDayOfWeek[mTime.weekDay] = true; - } - } - - } else { - mTime.setToNow(); - } - } - - mResources = getResources(); - mView = inflater.inflate(R.layout.recurrencepicker, container, true); - - final Activity activity = getActivity(); - final Configuration config = activity.getResources().getConfiguration(); - - mRepeatSwitch = (Switch) mView.findViewById(R.id.repeat_switch); - mRepeatSwitch.setChecked(mModel.recurrenceState == RecurrenceModel.STATE_RECURRENCE); - mRepeatSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mModel.recurrenceState = isChecked ? RecurrenceModel.STATE_RECURRENCE - : RecurrenceModel.STATE_NO_RECURRENCE; - togglePickerOptions(); - } - }); - - mFreqSpinner = (Spinner) mView.findViewById(R.id.freqSpinner); - mFreqSpinner.setOnItemSelectedListener(this); - ArrayAdapter<CharSequence> freqAdapter = ArrayAdapter.createFromResource(getActivity(), - R.array.recurrence_freq, R.layout.recurrencepicker_freq_item); - freqAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item); - mFreqSpinner.setAdapter(freqAdapter); - - mInterval = (EditText) mView.findViewById(R.id.interval); - mInterval.addTextChangedListener(new minMaxTextWatcher(1, INTERVAL_DEFAULT, INTERVAL_MAX) { - @Override - void onChange(int v) { - if (mIntervalResId != -1 && mInterval.getText().toString().length() > 0) { - mModel.interval = v; - updateIntervalText(); - mInterval.requestLayout(); - } - } - }); - mIntervalPreText = (TextView) mView.findViewById(R.id.intervalPreText); - mIntervalPostText = (TextView) mView.findViewById(R.id.intervalPostText); - - mEndNeverStr = mResources.getString(R.string.recurrence_end_continously); - mEndDateLabel = mResources.getString(R.string.recurrence_end_date_label); - mEndCountLabel = mResources.getString(R.string.recurrence_end_count_label); - - mEndSpinnerArray.add(mEndNeverStr); - mEndSpinnerArray.add(mEndDateLabel); - mEndSpinnerArray.add(mEndCountLabel); - mEndSpinner = (Spinner) mView.findViewById(R.id.endSpinner); - mEndSpinner.setOnItemSelectedListener(this); - mEndSpinnerAdapter = new EndSpinnerAdapter(getActivity(), mEndSpinnerArray, - R.layout.recurrencepicker_freq_item, R.layout.recurrencepicker_end_text); - mEndSpinnerAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item); - mEndSpinner.setAdapter(mEndSpinnerAdapter); - - mEndCount = (EditText) mView.findViewById(R.id.endCount); - mEndCount.addTextChangedListener(new minMaxTextWatcher(1, COUNT_DEFAULT, COUNT_MAX) { - @Override - void onChange(int v) { - if (mModel.endCount != v) { - mModel.endCount = v; - updateEndCountText(); - mEndCount.requestLayout(); - } - } - }); - mPostEndCount = (TextView) mView.findViewById(R.id.postEndCount); - - mEndDateTextView = (TextView) mView.findViewById(R.id.endDate); - mEndDateTextView.setOnClickListener(this); - if (mModel.endDate == null) { - mModel.endDate = new Time(mTime); - switch (mModel.freq) { - case RecurrenceModel.FREQ_DAILY: - case RecurrenceModel.FREQ_WEEKLY: - mModel.endDate.month += 1; - break; - case RecurrenceModel.FREQ_MONTHLY: - mModel.endDate.month += 3; - break; - case RecurrenceModel.FREQ_YEARLY: - mModel.endDate.year += 3; - break; - } - mModel.endDate.normalize(false); - } - - mWeekGroup = (LinearLayout) mView.findViewById(R.id.weekGroup); - mWeekGroup2 = (LinearLayout) mView.findViewById(R.id.weekGroup2); - - // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7 - String[] dayOfWeekString = new DateFormatSymbols().getWeekdays(); - - mMonthRepeatByDayOfWeekStrs = new String[7][]; - // from Time.SUNDAY as 0 through Time.SATURDAY as 6 - mMonthRepeatByDayOfWeekStrs[0] = mResources.getStringArray(R.array.repeat_by_nth_sun); - mMonthRepeatByDayOfWeekStrs[1] = mResources.getStringArray(R.array.repeat_by_nth_mon); - mMonthRepeatByDayOfWeekStrs[2] = mResources.getStringArray(R.array.repeat_by_nth_tues); - mMonthRepeatByDayOfWeekStrs[3] = mResources.getStringArray(R.array.repeat_by_nth_wed); - mMonthRepeatByDayOfWeekStrs[4] = mResources.getStringArray(R.array.repeat_by_nth_thurs); - mMonthRepeatByDayOfWeekStrs[5] = mResources.getStringArray(R.array.repeat_by_nth_fri); - mMonthRepeatByDayOfWeekStrs[6] = mResources.getStringArray(R.array.repeat_by_nth_sat); - - // In Time.java day of week order e.g. Sun = 0 - int idx = Utils.getFirstDayOfWeek(getActivity()); - - // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7 - dayOfWeekString = new DateFormatSymbols().getShortWeekdays(); - - int numOfButtonsInRow1; - int numOfButtonsInRow2; - - if (mResources.getConfiguration().screenWidthDp > MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK) { - numOfButtonsInRow1 = 7; - numOfButtonsInRow2 = 0; - mWeekGroup2.setVisibility(View.GONE); - mWeekGroup2.getChildAt(3).setVisibility(View.GONE); - } else { - numOfButtonsInRow1 = 4; - numOfButtonsInRow2 = 3; - - mWeekGroup2.setVisibility(View.VISIBLE); - // Set rightmost button on the second row invisible so it takes up - // space and everything centers properly - mWeekGroup2.getChildAt(3).setVisibility(View.INVISIBLE); - } - - /* First row */ - for (int i = 0; i < 7; i++) { - if (i >= numOfButtonsInRow1) { - mWeekGroup.getChildAt(i).setVisibility(View.GONE); - continue; - } - - mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup.getChildAt(i); - mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); - mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); - mWeekByDayButtons[idx].setOnCheckedChangeListener(this); - - if (++idx >= 7) { - idx = 0; - } - } - - /* 2nd Row */ - for (int i = 0; i < 3; i++) { - if (i >= numOfButtonsInRow2) { - mWeekGroup2.getChildAt(i).setVisibility(View.GONE); - continue; - } - mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup2.getChildAt(i); - mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); - mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); - mWeekByDayButtons[idx].setOnCheckedChangeListener(this); - - if (++idx >= 7) { - idx = 0; - } - } - - mMonthGroup = (LinearLayout) mView.findViewById(R.id.monthGroup); - mMonthRepeatByRadioGroup = (RadioGroup) mView.findViewById(R.id.monthGroup); - mMonthRepeatByRadioGroup.setOnCheckedChangeListener(this); - mRepeatMonthlyByNthDayOfWeek = (RadioButton) mView - .findViewById(R.id.repeatMonthlyByNthDayOfTheWeek); - mRepeatMonthlyByNthDayOfMonth = (RadioButton) mView - .findViewById(R.id.repeatMonthlyByNthDayOfMonth); - - mDone = (Button) mView.findViewById(R.id.done); - mDone.setOnClickListener(this); - - togglePickerOptions(); - updateDialog(); - if (endCountHasFocus) { - mEndCount.requestFocus(); - } - return mView; - } - - private void togglePickerOptions() { - if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { - mFreqSpinner.setEnabled(false); - mEndSpinner.setEnabled(false); - mIntervalPreText.setEnabled(false); - mInterval.setEnabled(false); - mIntervalPostText.setEnabled(false); - mMonthRepeatByRadioGroup.setEnabled(false); - mEndCount.setEnabled(false); - mPostEndCount.setEnabled(false); - mEndDateTextView.setEnabled(false); - mRepeatMonthlyByNthDayOfWeek.setEnabled(false); - mRepeatMonthlyByNthDayOfMonth.setEnabled(false); - for (Button button : mWeekByDayButtons) { - button.setEnabled(false); - } - } else { - mView.findViewById(R.id.options).setEnabled(true); - mFreqSpinner.setEnabled(true); - mEndSpinner.setEnabled(true); - mIntervalPreText.setEnabled(true); - mInterval.setEnabled(true); - mIntervalPostText.setEnabled(true); - mMonthRepeatByRadioGroup.setEnabled(true); - mEndCount.setEnabled(true); - mPostEndCount.setEnabled(true); - mEndDateTextView.setEnabled(true); - mRepeatMonthlyByNthDayOfWeek.setEnabled(true); - mRepeatMonthlyByNthDayOfMonth.setEnabled(true); - for (Button button : mWeekByDayButtons) { - button.setEnabled(true); - } - } - updateDoneButtonState(); - } - - private void updateDoneButtonState() { - if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { - mDone.setEnabled(true); - return; - } - - if (mInterval.getText().toString().length() == 0) { - mDone.setEnabled(false); - return; - } - - if (mEndCount.getVisibility() == View.VISIBLE && - mEndCount.getText().toString().length() == 0) { - mDone.setEnabled(false); - return; - } - - if (mModel.freq == RecurrenceModel.FREQ_WEEKLY) { - for (CompoundButton b : mWeekByDayButtons) { - if (b.isChecked()) { - mDone.setEnabled(true); - return; - } - } - mDone.setEnabled(false); - return; - } - - mDone.setEnabled(true); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(BUNDLE_MODEL, mModel); - if (mEndCount.hasFocus()) { - outState.putBoolean(BUNDLE_END_COUNT_HAS_FOCUS, true); - } - } - - public void updateDialog() { - // Interval - // Checking before setting because this causes infinite recursion - // in afterTextWatcher - final String intervalStr = Integer.toString(mModel.interval); - if (!intervalStr.equals(mInterval.getText().toString())) { - mInterval.setText(intervalStr); - } - - mFreqSpinner.setSelection(mModel.freq); - mWeekGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE); - mWeekGroup2.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE); - mMonthGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_MONTHLY ? View.VISIBLE : View.GONE); - - switch (mModel.freq) { - case RecurrenceModel.FREQ_DAILY: - mIntervalResId = R.plurals.recurrence_interval_daily; - break; - - case RecurrenceModel.FREQ_WEEKLY: - mIntervalResId = R.plurals.recurrence_interval_weekly; - for (int i = 0; i < 7; i++) { - mWeekByDayButtons[i].setChecked(mModel.weeklyByDayOfWeek[i]); - } - break; - - case RecurrenceModel.FREQ_MONTHLY: - mIntervalResId = R.plurals.recurrence_interval_monthly; - - if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) { - mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfMonth); - } else if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { - mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfTheWeek); - } - - if (mMonthRepeatByDayOfWeekStr == null) { - if (mModel.monthlyByNthDayOfWeek == 0) { - mModel.monthlyByNthDayOfWeek = (mTime.monthDay + 6) / 7; - // Since not all months have 5 weeks, we convert 5th NthDayOfWeek to - // -1 for last monthly day of the week - if (mModel.monthlyByNthDayOfWeek >= FIFTH_WEEK_IN_A_MONTH) { - mModel.monthlyByNthDayOfWeek = LAST_NTH_DAY_OF_WEEK; - } - mModel.monthlyByDayOfWeek = mTime.weekDay; - } - - String[] monthlyByNthDayOfWeekStrs = - mMonthRepeatByDayOfWeekStrs[mModel.monthlyByDayOfWeek]; - - // TODO(psliwowski): Find a better way handle -1 indexes - int msgIndex = mModel.monthlyByNthDayOfWeek < 0 ? FIFTH_WEEK_IN_A_MONTH : - mModel.monthlyByNthDayOfWeek; - mMonthRepeatByDayOfWeekStr = - monthlyByNthDayOfWeekStrs[msgIndex - 1]; - mRepeatMonthlyByNthDayOfWeek.setText(mMonthRepeatByDayOfWeekStr); - } - break; - - case RecurrenceModel.FREQ_YEARLY: - mIntervalResId = R.plurals.recurrence_interval_yearly; - break; - } - updateIntervalText(); - updateDoneButtonState(); - - mEndSpinner.setSelection(mModel.end); - if (mModel.end == RecurrenceModel.END_BY_DATE) { - final String dateStr = DateUtils.formatDateTime(getActivity(), - mModel.endDate.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE); - mEndDateTextView.setText(dateStr); - } else { - if (mModel.end == RecurrenceModel.END_BY_COUNT) { - // Checking before setting because this causes infinite - // recursion - // in afterTextWatcher - final String countStr = Integer.toString(mModel.endCount); - if (!countStr.equals(mEndCount.getText().toString())) { - mEndCount.setText(countStr); - } - } - } - } - - /** - * @param endDateString - */ - private void setEndSpinnerEndDateStr(final String endDateString) { - mEndSpinnerArray.set(1, endDateString); - mEndSpinnerAdapter.notifyDataSetChanged(); - } - - private void doToast() { - Log.e(TAG, "Model = " + mModel.toString()); - String rrule; - if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { - rrule = "Not repeating"; - } else { - copyModelToEventRecurrence(mModel, mRecurrence); - rrule = mRecurrence.toString(); - } - - if (mToast != null) { - mToast.cancel(); - } - mToast = Toast.makeText(getActivity(), rrule, - Toast.LENGTH_LONG); - mToast.show(); - } - - // TODO Test and update for Right-to-Left - private void updateIntervalText() { - if (mIntervalResId == -1) { - return; - } - - final String INTERVAL_COUNT_MARKER = "%d"; - String intervalString = mResources.getQuantityString(mIntervalResId, mModel.interval); - int markerStart = intervalString.indexOf(INTERVAL_COUNT_MARKER); - - if (markerStart != -1) { - int postTextStart = markerStart + INTERVAL_COUNT_MARKER.length(); - mIntervalPostText.setText(intervalString.substring(postTextStart, - intervalString.length()).trim()); - mIntervalPreText.setText(intervalString.substring(0, markerStart).trim()); - } - } - - /** - * Update the "Repeat for N events" end option with the proper string values - * based on the value that has been entered for N. - */ - private void updateEndCountText() { - final String END_COUNT_MARKER = "%d"; - String endString = mResources.getQuantityString(R.plurals.recurrence_end_count, - mModel.endCount); - int markerStart = endString.indexOf(END_COUNT_MARKER); - - if (markerStart != -1) { - if (markerStart == 0) { - Log.e(TAG, "No text to put in to recurrence's end spinner."); - } else { - int postTextStart = markerStart + END_COUNT_MARKER.length(); - mPostEndCount.setText(endString.substring(postTextStart, - endString.length()).trim()); - } - } - } - - // Implements OnItemSelectedListener interface - // Freq spinner - // End spinner - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - if (parent == mFreqSpinner) { - mModel.freq = position; - } else if (parent == mEndSpinner) { - switch (position) { - case RecurrenceModel.END_NEVER: - mModel.end = RecurrenceModel.END_NEVER; - break; - case RecurrenceModel.END_BY_DATE: - mModel.end = RecurrenceModel.END_BY_DATE; - break; - case RecurrenceModel.END_BY_COUNT: - mModel.end = RecurrenceModel.END_BY_COUNT; - - if (mModel.endCount <= 1) { - mModel.endCount = 1; - } else if (mModel.endCount > COUNT_MAX) { - mModel.endCount = COUNT_MAX; - } - updateEndCountText(); - break; - } - mEndCount.setVisibility(mModel.end == RecurrenceModel.END_BY_COUNT ? View.VISIBLE - : View.GONE); - mEndDateTextView.setVisibility(mModel.end == RecurrenceModel.END_BY_DATE ? View.VISIBLE - : View.GONE); - mPostEndCount.setVisibility( - mModel.end == RecurrenceModel.END_BY_COUNT && !mHidePostEndCount? - View.VISIBLE : View.GONE); - - } - updateDialog(); - } - - // Implements OnItemSelectedListener interface - @Override - public void onNothingSelected(AdapterView<?> arg0) { - } - - @Override - public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { - if (mModel.endDate == null) { - mModel.endDate = new Time(mTime.timezone); - mModel.endDate.hour = mModel.endDate.minute = mModel.endDate.second = 0; - } - mModel.endDate.year = year; - mModel.endDate.month = monthOfYear; - mModel.endDate.monthDay = dayOfMonth; - mModel.endDate.normalize(false); - updateDialog(); - } - - // Implements OnCheckedChangeListener interface - // Week repeat by day of week - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - int itemIdx = -1; - for (int i = 0; i < 7; i++) { - if (itemIdx == -1 && buttonView == mWeekByDayButtons[i]) { - itemIdx = i; - mModel.weeklyByDayOfWeek[i] = isChecked; - } - } - updateDialog(); - } - - // Implements android.widget.RadioGroup.OnCheckedChangeListener interface - // Month repeat by radio buttons - @Override - public void onCheckedChanged(RadioGroup group, int checkedId) { - if (checkedId == R.id.repeatMonthlyByNthDayOfMonth) { - mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE; - } else if (checkedId == R.id.repeatMonthlyByNthDayOfTheWeek) { - mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK; - } - updateDialog(); - } - - // Implements OnClickListener interface - // EndDate button - // Done button - @Override - public void onClick(View v) { - if (mEndDateTextView == v) { - if (mDatePickerDialog != null) { - mDatePickerDialog.dismiss(); - } - mDatePickerDialog = DatePickerDialog.newInstance(this, mModel.endDate.year, - mModel.endDate.month, mModel.endDate.monthDay); - mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(getActivity())); - mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX); - mDatePickerDialog.show(getFragmentManager(), FRAG_TAG_DATE_PICKER); - } else if (mDone == v) { - String rrule; - if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { - rrule = null; - } else { - copyModelToEventRecurrence(mModel, mRecurrence); - rrule = mRecurrence.toString(); - } - mRecurrenceSetListener.onRecurrenceSet(rrule); - dismiss(); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mDatePickerDialog = (DatePickerDialog) getFragmentManager() - .findFragmentByTag(FRAG_TAG_DATE_PICKER); - if (mDatePickerDialog != null) { - mDatePickerDialog.setOnDateSetListener(this); - } - } - - public interface OnRecurrenceSetListener { - void onRecurrenceSet(String rrule); - } - - public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) { - mRecurrenceSetListener = l; - } - - private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> { - final String END_DATE_MARKER = "%s"; - final String END_COUNT_MARKER = "%d"; - - private LayoutInflater mInflater; - private int mItemResourceId; - private int mTextResourceId; - private ArrayList<CharSequence> mStrings; - private String mEndDateString; - private boolean mUseFormStrings; - - /** - * @param context - * @param textViewResourceId - * @param objects - */ - public EndSpinnerAdapter(Context context, ArrayList<CharSequence> strings, - int itemResourceId, int textResourceId) { - super(context, itemResourceId, strings); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mItemResourceId = itemResourceId; - mTextResourceId = textResourceId; - mStrings = strings; - mEndDateString = getResources().getString(R.string.recurrence_end_date); - - // If either date or count strings don't translate well, such that we aren't assured - // to have some text available to be placed in the spinner, then we'll have to use - // the more form-like versions of both strings instead. - int markerStart = mEndDateString.indexOf(END_DATE_MARKER); - if (markerStart <= 0) { - // The date string does not have any text before the "%s" so we'll have to use the - // more form-like strings instead. - mUseFormStrings = true; - } else { - String countEndStr = getResources().getQuantityString( - R.plurals.recurrence_end_count, 1); - markerStart = countEndStr.indexOf(END_COUNT_MARKER); - if (markerStart <= 0) { - // The count string does not have any text before the "%d" so we'll have to use - // the more form-like strings instead. - mUseFormStrings = true; - } - } - - if (mUseFormStrings) { - // We'll have to set the layout for the spinner to be weight=0 so it doesn't - // take up too much space. - mEndSpinner.setLayoutParams( - new TableLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v; - // Check if we can recycle the view - if (convertView == null) { - v = mInflater.inflate(mTextResourceId, parent, false); - } else { - v = convertView; - } - - TextView item = (TextView) v.findViewById(R.id.spinner_item); - int markerStart; - switch (position) { - case RecurrenceModel.END_NEVER: - item.setText(mStrings.get(RecurrenceModel.END_NEVER)); - break; - case RecurrenceModel.END_BY_DATE: - markerStart = mEndDateString.indexOf(END_DATE_MARKER); - - if (markerStart != -1) { - if (mUseFormStrings || markerStart == 0) { - // If we get here, the translation of "Until" doesn't work correctly, - // so we'll just set the whole "Until a date" string. - item.setText(mEndDateLabel); - } else { - item.setText(mEndDateString.substring(0, markerStart).trim()); - } - } - break; - case RecurrenceModel.END_BY_COUNT: - String endString = mResources.getQuantityString(R.plurals.recurrence_end_count, - mModel.endCount); - markerStart = endString.indexOf(END_COUNT_MARKER); - - if (markerStart != -1) { - if (mUseFormStrings || markerStart == 0) { - // If we get here, the translation of "For" doesn't work correctly, - // so we'll just set the whole "For a number of events" string. - item.setText(mEndCountLabel); - // Also, we'll hide the " events" that would have been at the end. - mPostEndCount.setVisibility(View.GONE); - // Use this flag so the onItemSelected knows whether to show it later. - mHidePostEndCount = true; - } else { - int postTextStart = markerStart + END_COUNT_MARKER.length(); - mPostEndCount.setText(endString.substring(postTextStart, - endString.length()).trim()); - // In case it's a recycled view that wasn't visible. - if (mModel.end == RecurrenceModel.END_BY_COUNT) { - mPostEndCount.setVisibility(View.VISIBLE); - } - if (endString.charAt(markerStart - 1) == ' ') { - markerStart--; - } - item.setText(endString.substring(0, markerStart).trim()); - } - } - break; - default: - v = null; - break; - } - - return v; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - View v; - // Check if we can recycle the view - if (convertView == null) { - v = mInflater.inflate(mItemResourceId, parent, false); - } else { - v = convertView; - } - - TextView item = (TextView) v.findViewById(R.id.spinner_item); - item.setText(mStrings.get(position)); - - return v; - } - } -} diff --git a/src/com/android/calendar/recurrencepicker/WeekButton.java b/src/com/android/calendar/recurrencepicker/WeekButton.java deleted file mode 100644 index 5a8171eb..00000000 --- a/src/com/android/calendar/recurrencepicker/WeekButton.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.recurrencepicker; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -public class WeekButton extends android.widget.ToggleButton { - - private static int mWidth; - - public WeekButton(Context context) { - super(context); - } - - public WeekButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public WeekButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public static void setSuggestedWidth(int w) { - mWidth = w; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int h = getMeasuredHeight(); - int w = getMeasuredWidth(); - if (h > 0 && w > 0) { - if (w < h) { - if (View.MeasureSpec.getMode(getMeasuredHeightAndState()) != MeasureSpec.EXACTLY) { - h = w; - } - } else if (h < w) { - if (View.MeasureSpec.getMode(getMeasuredWidthAndState()) != MeasureSpec.EXACTLY) { - w = h; - } - } - } - setMeasuredDimension(w, h); - } -} diff --git a/src/com/android/calendar/selectcalendars/CalendarColorCache.java b/src/com/android/calendar/selectcalendars/CalendarColorCache.java deleted file mode 100644 index 0f9d14e2..00000000 --- a/src/com/android/calendar/selectcalendars/CalendarColorCache.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.content.Context; -import android.database.Cursor; -import android.provider.CalendarContract.Colors; - -import com.android.calendar.AsyncQueryService; - -import java.util.HashSet; - -/** - * CalendarColorCache queries the provider and stores the account identifiers (name and type) - * of the accounts which contain optional calendar colors, and thus should allow for the - * user to choose calendar colors. - */ -public class CalendarColorCache { - - private HashSet<String> mCache = new HashSet<String>(); - - private static final String SEPARATOR = "::"; - - private AsyncQueryService mService; - private OnCalendarColorsLoadedListener mListener; - - private StringBuffer mStringBuffer = new StringBuffer(); - - private static String[] PROJECTION = new String[] {Colors.ACCOUNT_NAME, Colors.ACCOUNT_TYPE }; - - /** - * Interface which provides callback after provider query of calendar colors. - */ - public interface OnCalendarColorsLoadedListener { - - /** - * Callback after the set of accounts with additional calendar colors are loaded. - */ - void onCalendarColorsLoaded(); - } - - public CalendarColorCache(Context context, OnCalendarColorsLoadedListener listener) { - mListener = listener; - mService = new AsyncQueryService(context) { - - @Override - public void onQueryComplete(int token, Object cookie, Cursor c) { - if (c == null) { - return; - } - if (c.moveToFirst()) { - clear(); - do { - insert(c.getString(0), c.getString(1)); - } while (c.moveToNext()); - mListener.onCalendarColorsLoaded(); - } - if (c != null) { - c.close(); - } - } - }; - mService.startQuery(0, null, Colors.CONTENT_URI, PROJECTION, - Colors.COLOR_TYPE + "=" + Colors.TYPE_CALENDAR, null, null); - } - - /** - * Inserts a specified account into the set. - */ - private void insert(String accountName, String accountType) { - mCache.add(generateKey(accountName, accountType)); - } - - /** - * Does a set lookup to determine if a specified account has more optional calendar colors. - */ - public boolean hasColors(String accountName, String accountType) { - return mCache.contains(generateKey(accountName, accountType)); - } - - /** - * Clears the cached set. - */ - private void clear() { - mCache.clear(); - } - - /** - * Generates a single key based on account name and account type for map lookup/insertion. - */ - private String generateKey(String accountName, String accountType) { - mStringBuffer.setLength(0); - return mStringBuffer.append(accountName).append(SEPARATOR).append(accountType).toString(); - } -} diff --git a/src/com/android/calendar/selectcalendars/CalendarColorSquare.java b/src/com/android/calendar/selectcalendars/CalendarColorSquare.java deleted file mode 100644 index 3631e11c..00000000 --- a/src/com/android/calendar/selectcalendars/CalendarColorSquare.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.QuickContactBadge; - -import com.android.calendar.CalendarColorPickerDialog; -import com.android.calendar.R; -import com.android.colorpicker.ColorStateDrawable; - -/** - * The color square used as an entry point to launching the {@link CalendarColorPickerDialog}. - */ -public class CalendarColorSquare extends QuickContactBadge { - - public CalendarColorSquare(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CalendarColorSquare(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void setBackgroundColor(int color) { - Drawable[] colorDrawable = new Drawable[] { - getContext().getResources().getDrawable(R.drawable.calendar_color_square) }; - setImageDrawable(new ColorStateDrawable(colorDrawable, color)); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java deleted file mode 100644 index 6a75d2b3..00000000 --- a/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.app.FragmentManager; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.provider.CalendarContract.Calendars; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.TouchDelegate; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.calendar.CalendarColorPickerDialog; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.selectcalendars.CalendarColorCache.OnCalendarColorsLoadedListener; - -public class SelectCalendarsSimpleAdapter extends BaseAdapter implements ListAdapter, - OnCalendarColorsLoadedListener { - private static final String TAG = "SelectCalendarsAdapter"; - private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog"; - - private static int BOTTOM_ITEM_HEIGHT = 64; - private static int NORMAL_ITEM_HEIGHT = 48; - - private static final int IS_SELECTED = 1 << 0; - private static final int IS_TOP = 1 << 1; - private static final int IS_BOTTOM = 1 << 2; - private static final int IS_BELOW_SELECTED = 1 << 3; - - private CalendarColorPickerDialog mColorPickerDialog; - - private LayoutInflater mInflater; - Resources mRes; - private int mLayout; - private int mOrientation; - private CalendarRow[] mData; - private Cursor mCursor; - private int mRowCount = 0; - - private FragmentManager mFragmentManager; - private boolean mIsTablet; - private int mColorViewTouchAreaIncrease; - - private int mIdColumn; - private int mNameColumn; - private int mColorColumn; - private int mVisibleColumn; - private int mOwnerAccountColumn; - private int mAccountNameColumn; - private int mAccountTypeColumn; - private static float mScale = 0; - private int mColorCalendarVisible; - private int mColorCalendarHidden; - private int mColorCalendarSecondaryVisible; - private int mColorCalendarSecondaryHidden; - - private CalendarColorCache mCache; - - private class CalendarRow { - long id; - String displayName; - String ownerAccount; - String accountName; - String accountType; - int color; - boolean selected; - } - - public SelectCalendarsSimpleAdapter(Context context, int layout, Cursor c, FragmentManager fm) { - super(); - mLayout = layout; - mOrientation = context.getResources().getConfiguration().orientation; - initData(c); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mRes = context.getResources(); - mColorCalendarVisible = mRes.getColor(R.color.calendar_visible); - mColorCalendarHidden = mRes.getColor(R.color.calendar_hidden); - mColorCalendarSecondaryVisible = mRes.getColor(R.color.calendar_secondary_visible); - mColorCalendarSecondaryHidden = mRes.getColor(R.color.calendar_secondary_hidden); - - if (mScale == 0) { - mScale = mRes.getDisplayMetrics().density; - BOTTOM_ITEM_HEIGHT *= mScale; - NORMAL_ITEM_HEIGHT *= mScale; - } - - mCache = new CalendarColorCache(context, this); - - mFragmentManager = fm; - mColorPickerDialog = (CalendarColorPickerDialog) - fm.findFragmentByTag(COLOR_PICKER_DIALOG_TAG); - mIsTablet = Utils.getConfigBool(context, R.bool.tablet_config); - mColorViewTouchAreaIncrease = context.getResources() - .getDimensionPixelSize(R.dimen.color_view_touch_area_increase); - } - - private static class TabletCalendarItemBackgrounds { - static private int[] mBackgrounds = null; - - /** - * Sets up the background drawables for the calendars list - * - * @param res The context's resources - */ - static int[] getBackgrounds() { - // Not thread safe. Ok if called only from main thread - if (mBackgrounds != null) { - return mBackgrounds; - } - - mBackgrounds = new int[16]; - - mBackgrounds[0] = R.drawable.calname_unselected; - - mBackgrounds[IS_SELECTED] = R.drawable.calname_select_underunselected; - - mBackgrounds[IS_SELECTED | IS_BOTTOM] = - R.drawable.calname_bottom_select_underunselected; - - mBackgrounds[IS_SELECTED | IS_BOTTOM | IS_BELOW_SELECTED] = - R.drawable.calname_bottom_select_underselect; - mBackgrounds[IS_SELECTED | IS_TOP | IS_BOTTOM | IS_BELOW_SELECTED] = mBackgrounds[ - IS_SELECTED | IS_BOTTOM | IS_BELOW_SELECTED]; - mBackgrounds[IS_SELECTED | IS_TOP | IS_BOTTOM] = mBackgrounds[IS_SELECTED | IS_BOTTOM - | IS_BELOW_SELECTED]; - - mBackgrounds[IS_SELECTED | IS_BELOW_SELECTED] = R.drawable.calname_select_underselect; - mBackgrounds[IS_SELECTED | IS_TOP | IS_BELOW_SELECTED] = mBackgrounds[IS_SELECTED - | IS_BELOW_SELECTED]; - mBackgrounds[IS_SELECTED | IS_TOP] = mBackgrounds[IS_SELECTED | IS_BELOW_SELECTED]; - - mBackgrounds[IS_BOTTOM] = R.drawable.calname_bottom_unselected; - - mBackgrounds[IS_BOTTOM | IS_BELOW_SELECTED] = - R.drawable.calname_bottom_unselected_underselect; - mBackgrounds[IS_TOP | IS_BOTTOM | IS_BELOW_SELECTED] = mBackgrounds[IS_BOTTOM - | IS_BELOW_SELECTED]; - mBackgrounds[IS_TOP | IS_BOTTOM] = mBackgrounds[IS_BOTTOM | IS_BELOW_SELECTED]; - - mBackgrounds[IS_BELOW_SELECTED] = R.drawable.calname_unselected_underselect; - mBackgrounds[IS_TOP | IS_BELOW_SELECTED] = mBackgrounds[IS_BELOW_SELECTED]; - mBackgrounds[IS_TOP] = mBackgrounds[IS_BELOW_SELECTED]; - return mBackgrounds; - } - } - - private void initData(Cursor c) { - if (mCursor != null && c != mCursor) { - mCursor.close(); - } - if (c == null) { - mCursor = c; - mRowCount = 0; - mData = null; - return; - } - // TODO create a broadcast listener for ACTION_PROVIDER_CHANGED to update the cursor - mCursor = c; - mIdColumn = c.getColumnIndexOrThrow(Calendars._ID); - mNameColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); - mColorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); - mVisibleColumn = c.getColumnIndexOrThrow(Calendars.VISIBLE); - mOwnerAccountColumn = c.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); - mAccountNameColumn = c.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - mAccountTypeColumn = c.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - - mRowCount = c.getCount(); - mData = new CalendarRow[(c.getCount())]; - c.moveToPosition(-1); - int p = 0; - while (c.moveToNext()) { - mData[p] = new CalendarRow(); - mData[p].id = c.getLong(mIdColumn); - mData[p].displayName = c.getString(mNameColumn); - mData[p].color = c.getInt(mColorColumn); - mData[p].selected = c.getInt(mVisibleColumn) != 0; - mData[p].ownerAccount = c.getString(mOwnerAccountColumn); - mData[p].accountName = c.getString(mAccountNameColumn); - mData[p].accountType = c.getString(mAccountTypeColumn); - p++; - } - } - - public void changeCursor(Cursor c) { - initData(c); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - if (position >= mRowCount) { - return null; - } - String name = mData[position].displayName; - boolean selected = mData[position].selected; - - int color = Utils.getDisplayColorFromColor(mData[position].color); - View view; - if (convertView == null) { - view = mInflater.inflate(mLayout, parent, false); - final View delegate = view.findViewById(R.id.color); - final View delegateParent = (View) delegate.getParent(); - delegateParent.post(new Runnable() { - - @Override - public void run() { - final Rect r = new Rect(); - delegate.getHitRect(r); - r.top -= mColorViewTouchAreaIncrease; - r.bottom += mColorViewTouchAreaIncrease; - r.left -= mColorViewTouchAreaIncrease; - r.right += mColorViewTouchAreaIncrease; - delegateParent.setTouchDelegate(new TouchDelegate(r, delegate)); - } - }); - } else { - view = convertView; - } - - TextView calendarName = (TextView) view.findViewById(R.id.calendar); - calendarName.setText(name); - - View colorView = view.findViewById(R.id.color); - colorView.setBackgroundColor(color); - colorView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // Purely for sanity check--view should be disabled if account has no more colors - if (!hasMoreColors(position)) { - return; - } - - if (mColorPickerDialog == null) { - mColorPickerDialog = CalendarColorPickerDialog.newInstance(mData[position].id, - mIsTablet); - } else { - mColorPickerDialog.setCalendarId(mData[position].id); - } - mFragmentManager.executePendingTransactions(); - if (!mColorPickerDialog.isAdded()) { - mColorPickerDialog.show(mFragmentManager, COLOR_PICKER_DIALOG_TAG); - } - } - }); - - int textColor; - if (selected) { - textColor = mColorCalendarVisible; - } else { - textColor = mColorCalendarHidden; - } - calendarName.setTextColor(textColor); - - CheckBox syncCheckBox = (CheckBox) view.findViewById(R.id.sync); - if (syncCheckBox != null) { - - // Full screen layout - syncCheckBox.setChecked(selected); - - colorView.setEnabled(hasMoreColors(position)); - LayoutParams layoutParam = calendarName.getLayoutParams(); - TextView secondaryText = (TextView) view.findViewById(R.id.status); - if (!TextUtils.isEmpty(mData[position].ownerAccount) - && !mData[position].ownerAccount.equals(name) - && !mData[position].ownerAccount.endsWith("calendar.google.com")) { - int secondaryColor; - if (selected) { - secondaryColor = mColorCalendarSecondaryVisible; - } else { - secondaryColor = mColorCalendarSecondaryHidden; - } - secondaryText.setText(mData[position].ownerAccount); - secondaryText.setTextColor(secondaryColor); - secondaryText.setVisibility(View.VISIBLE); - layoutParam.height = LayoutParams.WRAP_CONTENT; - } else { - secondaryText.setVisibility(View.GONE); - layoutParam.height = LayoutParams.MATCH_PARENT; - } - - calendarName.setLayoutParams(layoutParam); - - } else { - // Tablet layout - view.findViewById(R.id.color).setEnabled(selected && hasMoreColors(position)); - view.setBackgroundDrawable(getBackground(position, selected)); - ViewGroup.LayoutParams newParams = view.getLayoutParams(); - if (position == mData.length - 1) { - newParams.height = BOTTOM_ITEM_HEIGHT; - } else { - newParams.height = NORMAL_ITEM_HEIGHT; - } - view.setLayoutParams(newParams); - CheckBox visibleCheckBox = (CheckBox) view.findViewById(R.id.visible_check_box); - if (visibleCheckBox != null) { - visibleCheckBox.setChecked(selected); - } - } - view.invalidate(); - return view; - } - - private boolean hasMoreColors(int position) { - return mCache.hasColors(mData[position].accountName, mData[position].accountType); - } - - /** - * @param position position of the calendar item - * @param selected whether it is selected or not - * @return the drawable to use for this view - */ - protected Drawable getBackground(int position, boolean selected) { - int bg; - bg = selected ? IS_SELECTED : 0; - bg |= (position == 0 && mOrientation == Configuration.ORIENTATION_LANDSCAPE) ? IS_TOP : 0; - bg |= position == mData.length - 1 ? IS_BOTTOM : 0; - bg |= (position > 0 && mData[position - 1].selected) ? IS_BELOW_SELECTED : 0; - return mRes.getDrawable(TabletCalendarItemBackgrounds.getBackgrounds()[bg]); - } - - @Override - public int getCount() { - return mRowCount; - } - - @Override - public Object getItem(int position) { - if (position >= mRowCount) { - return null; - } - CalendarRow item = mData[position]; - return item; - } - - @Override - public long getItemId(int position) { - if (position >= mRowCount) { - return 0; - } - return mData[position].id; - } - - public void setVisible(int position, int visible) { - mData[position].selected = visible != 0; - notifyDataSetChanged(); - } - - public int getVisible(int position) { - return mData[position].selected ? 1 : 0; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public void onCalendarColorsLoaded() { - notifyDataSetChanged(); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java deleted file mode 100644 index 6e740bbf..00000000 --- a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.app.FragmentManager; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Rect; -import android.graphics.drawable.shapes.RectShape; -import android.provider.CalendarContract.Calendars; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.TouchDelegate; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.calendar.CalendarColorPickerDialog; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.selectcalendars.CalendarColorCache.OnCalendarColorsLoadedListener; - -import java.util.HashMap; - -public class SelectCalendarsSyncAdapter extends BaseAdapter - implements ListAdapter, AdapterView.OnItemClickListener, OnCalendarColorsLoadedListener { - private static final String TAG = "SelCalsAdapter"; - private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog"; - - private static int COLOR_CHIP_SIZE = 30; - private RectShape r = new RectShape(); - - private CalendarColorPickerDialog mColorPickerDialog; - private CalendarColorCache mCache; - - private LayoutInflater mInflater; - private static final int LAYOUT = R.layout.calendar_sync_item; - private CalendarRow[] mData; - private HashMap<Long, CalendarRow> mChanges = new HashMap<Long, CalendarRow>(); - private int mRowCount = 0; - - private int mIdColumn; - private int mNameColumn; - private int mColorColumn; - private int mSyncedColumn; - private int mAccountNameColumn; - private int mAccountTypeColumn; - - private boolean mIsTablet; - private FragmentManager mFragmentManager; - private int mColorViewTouchAreaIncrease; - - - private final String mSyncedString; - private final String mNotSyncedString; - - public class CalendarRow { - long id; - String displayName; - int color; - boolean synced; - boolean originalSynced; - String accountName; - String accountType; - } - - public SelectCalendarsSyncAdapter(Context context, Cursor c, FragmentManager manager) { - super(); - initData(c); - mCache = new CalendarColorCache(context, this); - mFragmentManager = manager; - mColorPickerDialog = (CalendarColorPickerDialog) - manager.findFragmentByTag(COLOR_PICKER_DIALOG_TAG); - mColorViewTouchAreaIncrease = context.getResources() - .getDimensionPixelSize(R.dimen.color_view_touch_area_increase); - mIsTablet = Utils.getConfigBool(context, R.bool.tablet_config); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - COLOR_CHIP_SIZE *= context.getResources().getDisplayMetrics().density; - r.resize(COLOR_CHIP_SIZE, COLOR_CHIP_SIZE); - Resources res = context.getResources(); - mSyncedString = res.getString(R.string.synced); - mNotSyncedString = res.getString(R.string.not_synced); - } - - private void initData(Cursor c) { - if (c == null) { - mRowCount = 0; - mData = null; - return; - } - - mIdColumn = c.getColumnIndexOrThrow(Calendars._ID); - mNameColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); - mColorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); - mSyncedColumn = c.getColumnIndexOrThrow(Calendars.SYNC_EVENTS); - mAccountNameColumn = c.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - mAccountTypeColumn = c.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - - mRowCount = c.getCount(); - mData = new CalendarRow[mRowCount]; - c.moveToPosition(-1); - int p = 0; - while (c.moveToNext()) { - long id = c.getLong(mIdColumn); - mData[p] = new CalendarRow(); - mData[p].id = id; - mData[p].displayName = c.getString(mNameColumn); - mData[p].color = c.getInt(mColorColumn); - mData[p].originalSynced = c.getInt(mSyncedColumn) != 0; - mData[p].accountName = c.getString(mAccountNameColumn); - mData[p].accountType = c.getString(mAccountTypeColumn); - if (mChanges.containsKey(id)) { - mData[p].synced = mChanges.get(id).synced; - } else { - mData[p].synced = mData[p].originalSynced; - } - p++; - } - } - - public void changeCursor(Cursor c) { - initData(c); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - if (position >= mRowCount) { - return null; - } - String name = mData[position].displayName; - boolean selected = mData[position].synced; - int color = Utils.getDisplayColorFromColor(mData[position].color); - View view; - if (convertView == null) { - view = mInflater.inflate(LAYOUT, parent, false); - final View delegate = view.findViewById(R.id.color); - final View delegateParent = (View) delegate.getParent(); - delegateParent.post(new Runnable() { - - @Override - public void run() { - final Rect r = new Rect(); - delegate.getHitRect(r); - r.top -= mColorViewTouchAreaIncrease; - r.bottom += mColorViewTouchAreaIncrease; - r.left -= mColorViewTouchAreaIncrease; - r.right += mColorViewTouchAreaIncrease; - delegateParent.setTouchDelegate(new TouchDelegate(r, delegate)); - } - }); - } else { - view = convertView; - } - - view.setTag(mData[position]); - - CheckBox cb = (CheckBox) view.findViewById(R.id.sync); - cb.setChecked(selected); - - if (selected) { - setText(view, R.id.status, mSyncedString); - } else { - setText(view, R.id.status, mNotSyncedString); - } - - View colorView = view.findViewById(R.id.color); - colorView.setEnabled(hasMoreColors(position)); - colorView.setBackgroundColor(color); - colorView.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - // Purely for sanity check--view should be disabled if account has no more colors - if (!hasMoreColors(position)) { - return; - } - - if (mColorPickerDialog == null) { - mColorPickerDialog = CalendarColorPickerDialog.newInstance(mData[position].id, - mIsTablet); - } else { - mColorPickerDialog.setCalendarId(mData[position].id); - } - mFragmentManager.executePendingTransactions(); - if (!mColorPickerDialog.isAdded()) { - mColorPickerDialog.show(mFragmentManager, COLOR_PICKER_DIALOG_TAG); - } - } - }); - - setText(view, R.id.calendar, name); - return view; - } - - private boolean hasMoreColors(int position) { - return mCache.hasColors(mData[position].accountName, mData[position].accountType); - } - - private static void setText(View view, int id, String text) { - if (TextUtils.isEmpty(text)) { - return; - } - TextView textView = (TextView) view.findViewById(id); - textView.setText(text); - } - - @Override - public int getCount() { - return mRowCount; - } - - @Override - public Object getItem(int position) { - if (position >= mRowCount) { - return null; - } - CalendarRow item = mData[position]; - return item; - } - - @Override - public long getItemId(int position) { - if (position >= mRowCount) { - return 0; - } - return mData[position].id; - } - - @Override - public boolean hasStableIds() { - return true; - } - - public int getSynced(int position) { - return mData[position].synced ? 1 : 0; - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - CalendarRow row = (CalendarRow) view.getTag(); - row.synced = !row.synced; - - String status; - if (row.synced) { - status = mSyncedString; - } else { - status = mNotSyncedString; - } - setText(view, R.id.status, status); - - CheckBox cb = (CheckBox) view.findViewById(R.id.sync); - cb.setChecked(row.synced); - - // There is some data loss in long -> int, but we should never see it in - // practice regarding calendar ids. - mChanges.put(row.id, row); - } - - public HashMap<Long, CalendarRow> getChanges() { - return mChanges; - } - - @Override - public void onCalendarColorsLoaded() { - notifyDataSetChanged(); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java deleted file mode 100644 index abdd50d2..00000000 --- a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.accounts.Account; -import android.app.Activity; -import android.app.ListFragment; -import android.app.LoaderManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.CursorLoader; -import android.content.Intent; -import android.content.Loader; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Calendars; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.calendar.AsyncQueryService; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.selectcalendars.SelectCalendarsSyncAdapter.CalendarRow; - -import java.util.HashMap; - -public class SelectCalendarsSyncFragment extends ListFragment - implements View.OnClickListener, LoaderManager.LoaderCallbacks<Cursor> { - - private static final String TAG = "SelectCalendarSync"; - - private static final String COLLATE_NOCASE = " COLLATE NOCASE"; - private static final String SELECTION = Calendars.ACCOUNT_NAME + "=? AND " - + Calendars.ACCOUNT_TYPE + "=?"; - // is primary lets us sort the user's main calendar to the top of the list - private static final String IS_PRIMARY = "\"primary\""; - private static final String SORT_ORDER = IS_PRIMARY + " DESC," + Calendars.CALENDAR_DISPLAY_NAME - + COLLATE_NOCASE; - - private static final String[] PROJECTION = new String[] { - Calendars._ID, - Calendars.CALENDAR_DISPLAY_NAME, - Calendars.CALENDAR_COLOR, - Calendars.SYNC_EVENTS, - Calendars.ACCOUNT_NAME, - Calendars.ACCOUNT_TYPE, - "(" + Calendars.ACCOUNT_NAME + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY, }; - - private TextView mSyncStatus; - private Button mAccountsButton; - private Account mAccount; - private final String[] mArgs = new String[2]; - private AsyncQueryService mService; - private Handler mHandler = new Handler(); - private ContentObserver mCalendarsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - // We don't need our own sync changes to trigger refreshes. - if (!selfChange) { - getLoaderManager().initLoader(0, null, SelectCalendarsSyncFragment.this); - } - } - }; - - public SelectCalendarsSyncFragment() { - } - - public SelectCalendarsSyncFragment(Bundle bundle) { - mAccount = new Account(bundle.getString(Calendars.ACCOUNT_NAME), - bundle.getString(Calendars.ACCOUNT_TYPE)); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.account_calendars, null); - mSyncStatus = (TextView) v.findViewById(R.id.account_status); - mSyncStatus.setVisibility(View.GONE); - - mAccountsButton = (Button) v.findViewById(R.id.sync_settings); - mAccountsButton.setVisibility(View.GONE); - mAccountsButton.setOnClickListener(this); - - return v; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - // Give some text to display if there is no data. In a real - // application this would come from a resource. - setEmptyText(getActivity().getText(R.string.no_syncable_calendars)); - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onResume() { - super.onResume(); - if (!ContentResolver.getMasterSyncAutomatically() - || !ContentResolver.getSyncAutomatically(mAccount, CalendarContract.AUTHORITY)) { - Resources res = getActivity().getResources(); - mSyncStatus.setText(res.getString(R.string.acct_not_synced)); - mSyncStatus.setVisibility(View.VISIBLE); - mAccountsButton.setText(res.getString(R.string.accounts)); - mAccountsButton.setVisibility(View.VISIBLE); - } else { - mSyncStatus.setVisibility(View.GONE); - mAccountsButton.setVisibility(View.GONE); - - // Start a background sync to get the list of calendars from the server. - Utils.startCalendarMetafeedSync(mAccount); - getActivity().getContentResolver().registerContentObserver( - Calendars.CONTENT_URI, true, mCalendarsObserver); - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mService = new AsyncQueryService(activity); - - Bundle bundle = getArguments(); - if (bundle != null && bundle.containsKey(Calendars.ACCOUNT_NAME) - && bundle.containsKey(Calendars.ACCOUNT_TYPE)) { - mAccount = new Account(bundle.getString(Calendars.ACCOUNT_NAME), - bundle.getString(Calendars.ACCOUNT_TYPE)); - } - } - - @Override - public void onPause() { - final ListAdapter listAdapter = getListAdapter(); - if (listAdapter != null) { - HashMap<Long, CalendarRow> changes = ((SelectCalendarsSyncAdapter) listAdapter) - .getChanges(); - if (changes != null && changes.size() > 0) { - for (CalendarRow row : changes.values()) { - if (row.synced == row.originalSynced) { - continue; - } - long id = row.id; - mService.cancelOperation((int) id); - // Use the full long id in case it makes a difference - Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, row.id); - ContentValues values = new ContentValues(); - // Toggle the current setting - int synced = row.synced ? 1 : 0; - values.put(Calendars.SYNC_EVENTS, synced); - values.put(Calendars.VISIBLE, synced); - mService.startUpdate((int) id, null, uri, values, null, null, 0); - } - changes.clear(); - } - } - getActivity().getContentResolver().unregisterContentObserver(mCalendarsObserver); - super.onPause(); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - mArgs[0] = mAccount.name; - mArgs[1] = mAccount.type; - return new CursorLoader( - getActivity(), Calendars.CONTENT_URI, PROJECTION, SELECTION, mArgs, SORT_ORDER); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - SelectCalendarsSyncAdapter adapter = (SelectCalendarsSyncAdapter) getListAdapter(); - if (adapter == null) { - adapter = new SelectCalendarsSyncAdapter(getActivity(), data, getFragmentManager()); - setListAdapter(adapter); - } else { - adapter.changeCursor(data); - } - getListView().setOnItemClickListener(adapter); - } - - public void onLoaderReset(Loader<Cursor> loader) { - setListAdapter(null); - } - - // Called when the Accounts button is pressed. Takes the user to the - // Accounts and Sync settings page. - @Override - public void onClick(View v) { - Intent intent = new Intent(); - intent.setAction("android.settings.SYNC_SETTINGS"); - getActivity().startActivity(intent); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountActivity.java b/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountActivity.java deleted file mode 100644 index 84fc67df..00000000 --- a/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountActivity.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.app.ActionBar; -import android.app.ExpandableListActivity; -import android.content.AsyncQueryHandler; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.os.Bundle; -import android.provider.CalendarContract.Calendars; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ExpandableListView; - -import com.android.calendar.R; -import com.android.calendar.Utils; - -public class SelectSyncedCalendarsMultiAccountActivity extends ExpandableListActivity - implements View.OnClickListener { - - private static final String TAG = "Calendar"; - private static final String EXPANDED_KEY = "is_expanded"; - private static final String ACCOUNT_UNIQUE_KEY = "ACCOUNT_KEY"; - private MatrixCursor mAccountsCursor = null; - private ExpandableListView mList; - private SelectSyncedCalendarsMultiAccountAdapter mAdapter; - private static final String[] PROJECTION = new String[] { - Calendars._ID, - Calendars.ACCOUNT_TYPE, - Calendars.ACCOUNT_NAME, - Calendars.ACCOUNT_TYPE + " || " + Calendars.ACCOUNT_NAME + " AS " + - ACCOUNT_UNIQUE_KEY, - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.select_calendars_multi_accounts_fragment); - mList = getExpandableListView(); - mList.setEmptyView(findViewById(R.id.loading)); - // Start a background sync to get the list of calendars from the server. - Utils.startCalendarMetafeedSync(null); - - findViewById(R.id.btn_done).setOnClickListener(this); - findViewById(R.id.btn_discard).setOnClickListener(this); - } - - @Override - public void onClick(View view) { - if (view.getId() == R.id.btn_done) { - if (mAdapter != null) { - mAdapter.doSaveAction(); - } - finish(); - } else if (view.getId() == R.id.btn_discard) { - finish(); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mAdapter != null) { - mAdapter.startRefreshStopDelay(); - } - new AsyncQueryHandler(getContentResolver()) { - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - mAccountsCursor = Utils.matrixCursorFromCursor(cursor); - - mAdapter = new SelectSyncedCalendarsMultiAccountAdapter( - findViewById(R.id.calendars).getContext(), mAccountsCursor, - SelectSyncedCalendarsMultiAccountActivity.this); - mList.setAdapter(mAdapter); - - // TODO initialize from sharepref - int count = mList.getCount(); - for(int i = 0; i < count; i++) { - mList.expandGroup(i); - } - } - }.startQuery(0, null, Calendars.CONTENT_URI, PROJECTION, - "1) GROUP BY (" + ACCOUNT_UNIQUE_KEY, //Cheap hack to make WHERE a GROUP BY query - null /* selectionArgs */, - Calendars.ACCOUNT_NAME /*sort order*/); - //TODO change to something that supports group by queries. - } - - @Override - protected void onPause() { - super.onPause(); - if (mAdapter != null) { - mAdapter.cancelRefreshStopDelay(); - } - } - - @Override - protected void onStop() { - super.onStop(); - if (mAdapter != null) { - mAdapter.closeChildrenCursors(); - } - if (mAccountsCursor != null && !mAccountsCursor.isClosed()) { - mAccountsCursor.close(); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - boolean[] isExpanded; - mList = getExpandableListView(); - if(mList != null) { - int count = mList.getCount(); - isExpanded = new boolean[count]; - for(int i = 0; i < count; i++) { - isExpanded[i] = mList.isGroupExpanded(i); - } - } else { - isExpanded = null; - } - outState.putBooleanArray(EXPANDED_KEY, isExpanded); - //TODO Store this to preferences instead so it remains on restart - } - - @Override - protected void onRestoreInstanceState(Bundle state) { - super.onRestoreInstanceState(state); - mList = getExpandableListView(); - boolean[] isExpanded = state.getBooleanArray(EXPANDED_KEY); - if(mList != null && isExpanded != null && mList.getCount() >= isExpanded.length) { - for(int i = 0; i < isExpanded.length; i++) { - if(isExpanded[i] && !mList.isGroupExpanded(i)) { - mList.expandGroup(i); - } else if(!isExpanded[i] && mList.isGroupExpanded(i)){ - mList.collapseGroup(i); - } - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getActionBar() - .setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Utils.returnToCalendarHome(this); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountAdapter.java b/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountAdapter.java deleted file mode 100644 index 58cdbdb8..00000000 --- a/src/com/android/calendar/selectcalendars/SelectSyncedCalendarsMultiAccountAdapter.java +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.accounts.AccountManager; -import android.accounts.AuthenticatorDescription; -import android.app.FragmentManager; -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.graphics.Rect; -import android.net.Uri; -import android.provider.CalendarContract.Calendars; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.TouchDelegate; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CursorTreeAdapter; -import android.widget.TextView; - -import com.android.calendar.CalendarColorPickerDialog; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.selectcalendars.CalendarColorCache.OnCalendarColorsLoadedListener; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class SelectSyncedCalendarsMultiAccountAdapter extends CursorTreeAdapter implements - View.OnClickListener, OnCalendarColorsLoadedListener { - - private static final String TAG = "Calendar"; - private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog"; - - private static final String IS_PRIMARY = "\"primary\""; - private static final String CALENDARS_ORDERBY = IS_PRIMARY + " DESC," - + Calendars.CALENDAR_DISPLAY_NAME + " COLLATE NOCASE"; - private static final String ACCOUNT_SELECTION = Calendars.ACCOUNT_NAME + "=?" - + " AND " + Calendars.ACCOUNT_TYPE + "=?"; - - private final LayoutInflater mInflater; - private final ContentResolver mResolver; - private final SelectSyncedCalendarsMultiAccountActivity mActivity; - private final FragmentManager mFragmentManager; - private final boolean mIsTablet; - private CalendarColorPickerDialog mColorPickerDialog; - private final View mView; - private final static Runnable mStopRefreshing = new Runnable() { - @Override - public void run() { - mRefresh = false; - } - }; - private Map<String, AuthenticatorDescription> mTypeToAuthDescription - = new HashMap<String, AuthenticatorDescription>(); - protected AuthenticatorDescription[] mAuthDescs; - - // These track changes to the synced state of calendars - private Map<Long, Boolean> mCalendarChanges - = new HashMap<Long, Boolean>(); - private Map<Long, Boolean> mCalendarInitialStates - = new HashMap<Long, Boolean>(); - - // Flag for when the cursors have all been closed to ensure no race condition with queries. - private boolean mClosedCursorsFlag; - - // This is for keeping MatrixCursor copies so that we can requery in the background. - private Map<String, Cursor> mChildrenCursors - = new HashMap<String, Cursor>(); - - private AsyncCalendarsUpdater mCalendarsUpdater; - // This is to keep our update tokens separate from other tokens. Since we cancel old updates - // when a new update comes in, we'd like to leave a token space that won't be canceled. - private static final int MIN_UPDATE_TOKEN = 1000; - private static int mUpdateToken = MIN_UPDATE_TOKEN; - // How long to wait between requeries of the calendars to see if anything has changed. - private static final int REFRESH_DELAY = 5000; - // How long to keep refreshing for - private static final int REFRESH_DURATION = 60000; - private static boolean mRefresh = true; - - private static String mSyncedText; - private static String mNotSyncedText; - - // This is to keep track of whether or not multiple calendars have the same display name - private static HashMap<String, Boolean> mIsDuplicateName = new HashMap<String, Boolean>(); - - private int mColorViewTouchAreaIncrease; - - private static final String[] PROJECTION = new String[] { - Calendars._ID, - Calendars.ACCOUNT_NAME, - Calendars.OWNER_ACCOUNT, - Calendars.CALENDAR_DISPLAY_NAME, - Calendars.CALENDAR_COLOR, - Calendars.VISIBLE, - Calendars.SYNC_EVENTS, - "(" + Calendars.ACCOUNT_NAME + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY, - Calendars.ACCOUNT_TYPE - }; - //Keep these in sync with the projection - private static final int ID_COLUMN = 0; - private static final int ACCOUNT_COLUMN = 1; - private static final int OWNER_COLUMN = 2; - private static final int NAME_COLUMN = 3; - private static final int COLOR_COLUMN = 4; - private static final int SELECTED_COLUMN = 5; - private static final int SYNCED_COLUMN = 6; - private static final int PRIMARY_COLUMN = 7; - private static final int ACCOUNT_TYPE_COLUMN = 8; - - private static final int TAG_ID_CALENDAR_ID = R.id.calendar; - private static final int TAG_ID_SYNC_CHECKBOX = R.id.sync; - - private CalendarColorCache mCache; - - private class AsyncCalendarsUpdater extends AsyncQueryHandler { - - public AsyncCalendarsUpdater(ContentResolver cr) { - super(cr); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if(cursor == null) { - return; - } - synchronized(mChildrenCursors) { - if (mClosedCursorsFlag || (mActivity != null && mActivity.isFinishing())) { - cursor.close(); - return; - } - } - - Cursor currentCursor = mChildrenCursors.get(cookie); - // Check if the new cursor has the same content as our old cursor - if (currentCursor != null) { - if (Utils.compareCursors(currentCursor, cursor)) { - cursor.close(); - return; - } - } - // If not then make a new matrix cursor for our Map - MatrixCursor newCursor = Utils.matrixCursorFromCursor(cursor); - cursor.close(); - // And update our list of duplicated names - Utils.checkForDuplicateNames(mIsDuplicateName, newCursor, NAME_COLUMN); - - mChildrenCursors.put((String)cookie, newCursor); - try { - setChildrenCursor(token, newCursor); - } catch (NullPointerException e) { - Log.w(TAG, "Adapter expired, try again on the next query: " + e); - } - // Clean up our old cursor if we had one. We have to do this after setting the new - // cursor so that our view doesn't throw on an invalid cursor. - if (currentCursor != null) { - currentCursor.close(); - } - } - } - - /** - * Method for changing the sync state when a calendar's button is pressed. - * - * This gets called when the CheckBox for a calendar is clicked. It toggles - * the sync state for the associated calendar and saves a change of state to - * a hashmap. It also compares against the original value and removes any - * changes from the hashmap if this is back at its initial state. - */ - @Override - public void onClick(View v) { - long id = (Long) v.getTag(TAG_ID_CALENDAR_ID); - boolean newState; - boolean initialState = mCalendarInitialStates.get(id); - if (mCalendarChanges.containsKey(id)) { - // Negate to reflect the click - newState = !mCalendarChanges.get(id); - } else { - // Negate to reflect the click - newState = !initialState; - } - - if (newState == initialState) { - mCalendarChanges.remove(id); - } else { - mCalendarChanges.put(id, newState); - } - - ((CheckBox) v.getTag(TAG_ID_SYNC_CHECKBOX)).setChecked(newState); - setText(v, R.id.status, newState ? mSyncedText : mNotSyncedText); - } - - public SelectSyncedCalendarsMultiAccountAdapter(Context context, Cursor acctsCursor, - SelectSyncedCalendarsMultiAccountActivity act) { - super(acctsCursor, context); - mSyncedText = context.getString(R.string.synced); - mNotSyncedText = context.getString(R.string.not_synced); - - mCache = new CalendarColorCache(context, this); - - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mResolver = context.getContentResolver(); - mActivity = act; - mFragmentManager = act.getFragmentManager(); - mColorPickerDialog = (CalendarColorPickerDialog) - mFragmentManager.findFragmentByTag(COLOR_PICKER_DIALOG_TAG); - mIsTablet = Utils.getConfigBool(context, R.bool.tablet_config); - - if (mCalendarsUpdater == null) { - mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver); - } - - if (acctsCursor == null || acctsCursor.getCount() == 0) { - Log.i(TAG, "SelectCalendarsAdapter: No accounts were returned!"); - } - // Collect proper description for account types - mAuthDescs = AccountManager.get(context).getAuthenticatorTypes(); - for (int i = 0; i < mAuthDescs.length; i++) { - mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); - } - mView = mActivity.getExpandableListView(); - mRefresh = true; - mClosedCursorsFlag = false; - - mColorViewTouchAreaIncrease = context.getResources() - .getDimensionPixelSize(R.dimen.color_view_touch_area_increase); - } - - public void startRefreshStopDelay() { - mRefresh = true; - mView.postDelayed(mStopRefreshing, REFRESH_DURATION); - } - - public void cancelRefreshStopDelay() { - mView.removeCallbacks(mStopRefreshing); - } - - /* - * Write back the changes that have been made. The sync code will pick up any changes and - * do updates on its own. - */ - public void doSaveAction() { - // Cancel the previous operation - mCalendarsUpdater.cancelOperation(mUpdateToken); - mUpdateToken++; - // This is to allow us to do queries and updates with the same AsyncQueryHandler without - // accidently canceling queries. - if(mUpdateToken < MIN_UPDATE_TOKEN) { - mUpdateToken = MIN_UPDATE_TOKEN; - } - - Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator(); - while (changeKeys.hasNext()) { - long id = changeKeys.next(); - boolean newSynced = mCalendarChanges.get(id); - - Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id); - ContentValues values = new ContentValues(); - values.put(Calendars.VISIBLE, newSynced ? 1 : 0); - values.put(Calendars.SYNC_EVENTS, newSynced ? 1 : 0); - mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null); - } - } - - private static void setText(View view, int id, String text) { - if (TextUtils.isEmpty(text)) { - return; - } - TextView textView = (TextView) view.findViewById(id); - textView.setText(text); - } - - /** - * Gets the label associated with a particular account type. If none found, return null. - * @param accountType the type of account - * @return a CharSequence for the label or null if one cannot be found. - */ - protected CharSequence getLabelForType(final String accountType) { - CharSequence label = null; - if (mTypeToAuthDescription.containsKey(accountType)) { - try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = mActivity.createPackageContext(desc.packageName, 0); - label = authContext.getResources().getText(desc.labelId); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "No label for account type " + ", type " + accountType); - } - } - return label; - } - - @Override - protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { - final long id = cursor.getLong(ID_COLUMN); - String name = cursor.getString(NAME_COLUMN); - String owner = cursor.getString(OWNER_COLUMN); - final String accountName = cursor.getString(ACCOUNT_COLUMN); - final String accountType = cursor.getString(ACCOUNT_TYPE_COLUMN); - int color = Utils.getDisplayColorFromColor(cursor.getInt(COLOR_COLUMN)); - - final View colorSquare = view.findViewById(R.id.color); - colorSquare.setEnabled(mCache.hasColors(accountName, accountType)); - colorSquare.setBackgroundColor(color); - final View delegateParent = (View) colorSquare.getParent(); - delegateParent.post(new Runnable() { - - @Override - public void run() { - final Rect r = new Rect(); - colorSquare.getHitRect(r); - r.top -= mColorViewTouchAreaIncrease; - r.bottom += mColorViewTouchAreaIncrease; - r.left -= mColorViewTouchAreaIncrease; - r.right += mColorViewTouchAreaIncrease; - delegateParent.setTouchDelegate(new TouchDelegate(r, colorSquare)); - } - }); - colorSquare.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (!mCache.hasColors(accountName, accountType)) { - return; - } - if (mColorPickerDialog == null) { - mColorPickerDialog = CalendarColorPickerDialog.newInstance(id, mIsTablet); - } else { - mColorPickerDialog.setCalendarId(id); - } - mFragmentManager.executePendingTransactions(); - if (!mColorPickerDialog.isAdded()) { - mColorPickerDialog.show(mFragmentManager, COLOR_PICKER_DIALOG_TAG); - } - } - }); - if (mIsDuplicateName.containsKey(name) && mIsDuplicateName.get(name) && - !name.equalsIgnoreCase(owner)) { - name = new StringBuilder(name) - .append(Utils.OPEN_EMAIL_MARKER) - .append(owner) - .append(Utils.CLOSE_EMAIL_MARKER) - .toString(); - } - setText(view, R.id.calendar, name); - - // First see if the user has already changed the state of this calendar - Boolean sync = mCalendarChanges.get(id); - if (sync == null) { - sync = cursor.getInt(SYNCED_COLUMN) == 1; - mCalendarInitialStates.put(id, sync); - } - - CheckBox button = (CheckBox) view.findViewById(R.id.sync); - button.setChecked(sync); - setText(view, R.id.status, sync ? mSyncedText : mNotSyncedText); - - view.setTag(TAG_ID_CALENDAR_ID, id); - view.setTag(TAG_ID_SYNC_CHECKBOX, button); - view.setOnClickListener(this); - } - - @Override - protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { - int accountColumn = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - String account = cursor.getString(accountColumn); - String accountType = cursor.getString(accountTypeColumn); - CharSequence accountLabel = getLabelForType(accountType); - setText(view, R.id.account, account); - if (accountLabel != null) { - setText(view, R.id.account_type, accountLabel.toString()); - } - } - - @Override - protected Cursor getChildrenCursor(Cursor groupCursor) { - int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); - int accountTypeColumn = groupCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); - String account = groupCursor.getString(accountColumn); - String accountType = groupCursor.getString(accountTypeColumn); - //Get all the calendars for just this account. - Cursor childCursor = mChildrenCursors.get(accountType + "#" + account); - new RefreshCalendars(groupCursor.getPosition(), account, accountType).run(); - return childCursor; - } - - @Override - protected View newChildView(Context context, Cursor cursor, boolean isLastChild, - ViewGroup parent) { - return mInflater.inflate(R.layout.calendar_sync_item, parent, false); - } - - @Override - protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, - ViewGroup parent) { - return mInflater.inflate(R.layout.account_item, parent, false); - } - - public void closeChildrenCursors() { - synchronized (mChildrenCursors) { - for (String key : mChildrenCursors.keySet()) { - Cursor cursor = mChildrenCursors.get(key); - if (!cursor.isClosed()) { - cursor.close(); - } - } - mChildrenCursors.clear(); - mClosedCursorsFlag = true; - } - } - - private class RefreshCalendars implements Runnable { - - int mToken; - String mAccount; - String mAccountType; - - public RefreshCalendars(int token, String account, String accountType) { - mToken = token; - mAccount = account; - mAccountType = accountType; - } - - @Override - public void run() { - mCalendarsUpdater.cancelOperation(mToken); - // Set up a refresh for some point in the future if we haven't stopped updates yet - if(mRefresh) { - mView.postDelayed(new RefreshCalendars(mToken, mAccount, mAccountType), - REFRESH_DELAY); - } - mCalendarsUpdater.startQuery(mToken, - mAccountType + "#" + mAccount, - Calendars.CONTENT_URI, PROJECTION, - ACCOUNT_SELECTION, - new String[] { mAccount, mAccountType } /*selectionArgs*/, - CALENDARS_ORDERBY); - } - } - - @Override - public void onCalendarColorsLoaded() { - notifyDataSetChanged(); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsActivity.java b/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsActivity.java deleted file mode 100644 index b8a9ba3f..00000000 --- a/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsActivity.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.app.ActionBar; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.database.ContentObserver; -import android.os.Bundle; -import android.os.Handler; -import android.provider.CalendarContract; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; - -import com.android.calendar.AbstractCalendarActivity; -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.CalendarController.ViewType; -import com.android.calendar.R; -import com.android.calendar.Utils; - -public class SelectVisibleCalendarsActivity extends AbstractCalendarActivity { - private SelectVisibleCalendarsFragment mFragment; - private CalendarController mController; - - // Create an observer so that we can update the views whenever a - // Calendar event changes. - private final ContentObserver mObserver = new ContentObserver(new Handler()) { - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(boolean selfChange) { - mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - setContentView(R.layout.simple_frame_layout); - - mController = CalendarController.getInstance(this); - mFragment = (SelectVisibleCalendarsFragment) getFragmentManager().findFragmentById( - R.id.main_frame); - - if (mFragment == null) { - mFragment = new SelectVisibleCalendarsFragment(R.layout.calendar_sync_item); - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(R.id.main_frame, mFragment); - ft.show(mFragment); - ft.commit(); - } - } - - @Override - public void onResume() { - super.onResume(); - getContentResolver().registerContentObserver(CalendarContract.Events.CONTENT_URI, - true, mObserver); - } - - @Override - public void onPause() { - super.onPause(); - getContentResolver().unregisterContentObserver(mObserver); - } - - // Needs to be in proguard whitelist - // Specified as listener via android:onClick in a layout xml - public void handleSelectSyncedCalendarsClicked(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setClass(this, SelectSyncedCalendarsMultiAccountActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); - startActivity(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getActionBar() - .setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Utils.returnToCalendarHome(this); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java b/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java deleted file mode 100644 index 53028968..00000000 --- a/src/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar.selectcalendars; - -import android.app.Activity; -import android.app.Fragment; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.CalendarContract.Calendars; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; - -import com.android.calendar.AsyncQueryService; -import com.android.calendar.CalendarController; -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarController.EventType; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.calendar.selectcalendars.CalendarColorCache.OnCalendarColorsLoadedListener; - -public class SelectVisibleCalendarsFragment extends Fragment - implements AdapterView.OnItemClickListener, CalendarController.EventHandler, - OnCalendarColorsLoadedListener { - - private static final String TAG = "Calendar"; - private static final String IS_PRIMARY = "\"primary\""; - private static final String SELECTION = Calendars.SYNC_EVENTS + "=?"; - private static final String[] SELECTION_ARGS = new String[] {"1"}; - - private static final String[] PROJECTION = new String[] { - Calendars._ID, - Calendars.ACCOUNT_NAME, - Calendars.ACCOUNT_TYPE, - Calendars.OWNER_ACCOUNT, - Calendars.CALENDAR_DISPLAY_NAME, - Calendars.CALENDAR_COLOR, - Calendars.VISIBLE, - Calendars.SYNC_EVENTS, - "(" + Calendars.ACCOUNT_NAME + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY, - }; - private static int mUpdateToken; - private static int mQueryToken; - private static int mCalendarItemLayout = R.layout.mini_calendar_item; - - private View mView = null; - private CalendarController mController; - private ListView mList; - private SelectCalendarsSimpleAdapter mAdapter; - private Activity mContext; - private AsyncQueryService mService; - private Cursor mCursor; - - public SelectVisibleCalendarsFragment() { - } - - public SelectVisibleCalendarsFragment(int itemLayout) { - mCalendarItemLayout = itemLayout; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mContext = activity; - mController = CalendarController.getInstance(activity); - mController.registerEventHandler(R.layout.select_calendars_fragment, this); - mService = new AsyncQueryService(activity) { - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - mAdapter.changeCursor(cursor); - mCursor = cursor; - } - }; - } - - @Override - public void onDetach() { - super.onDetach(); - mController.deregisterEventHandler(R.layout.select_calendars_fragment); - if (mCursor != null) { - mAdapter.changeCursor(null); - mCursor.close(); - mCursor = null; - } - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - mView = inflater.inflate(R.layout.select_calendars_fragment, null); - mList = (ListView)mView.findViewById(R.id.list); - - // Hide the Calendars to Sync button on tablets for now. - // Long terms stick it in the list of calendars - if (Utils.getConfigBool(getActivity(), R.bool.multiple_pane_config)) { - // Don't show dividers on tablets - mList.setDivider(null); - View v = mView.findViewById(R.id.manage_sync_set); - if (v != null) { - v.setVisibility(View.GONE); - } - } - return mView; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mAdapter = new SelectCalendarsSimpleAdapter(mContext, mCalendarItemLayout, null, - getFragmentManager()); - mList.setAdapter(mAdapter); - mList.setOnItemClickListener(this); - } - - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (mAdapter == null || mAdapter.getCount() <= position) { - return; - } - toggleVisibility(position); - } - - @Override - public void onResume() { - super.onResume(); - mQueryToken = mService.getNextToken(); - mService.startQuery(mQueryToken, null, Calendars.CONTENT_URI, PROJECTION, SELECTION, - SELECTION_ARGS, Calendars.ACCOUNT_NAME); - } - - /* - * Write back the changes that have been made. - */ - public void toggleVisibility(int position) { - mUpdateToken = mService.getNextToken(); - Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, mAdapter.getItemId(position)); - ContentValues values = new ContentValues(); - // Toggle the current setting - int visibility = mAdapter.getVisible(position)^1; - values.put(Calendars.VISIBLE, visibility); - mService.startUpdate(mUpdateToken, null, uri, values, null, null, 0); - mAdapter.setVisible(position, visibility); - } - - @Override - public void eventsChanged() { - if (mService != null) { - mService.cancelOperation(mQueryToken); - mQueryToken = mService.getNextToken(); - mService.startQuery(mQueryToken, null, Calendars.CONTENT_URI, PROJECTION, SELECTION, - SELECTION_ARGS, Calendars.ACCOUNT_NAME); - } - } - - @Override - public long getSupportedEventTypes() { - return EventType.EVENTS_CHANGED; - } - - @Override - public void handleEvent(EventInfo event) { - eventsChanged(); - } - - @Override - public void onCalendarColorsLoaded() { - if (mAdapter != null) { - mAdapter.notifyDataSetChanged(); - } - } -} diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java index 7da2ba17..2864d47a 100644 --- a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java +++ b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java @@ -227,14 +227,4 @@ public class CalendarAppWidgetProvider extends AppWidgetProvider { return fillInIntent; } - -// private static PendingIntent getNewEventPendingIntent(Context context) { -// Intent newEventIntent = new Intent(Intent.ACTION_EDIT); -// newEventIntent.setClass(context, EditEventActivity.class); -// Builder builder = CalendarContract.CONTENT_URI.buildUpon(); -// builder.appendPath("events"); -// newEventIntent.setData(builder.build()); -// return PendingIntent.getActivity(context, 0, newEventIntent, -// PendingIntent.FLAG_UPDATE_CURRENT); -// } } diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.java b/src/com/android/calendar/widget/CalendarAppWidgetService.java index b51b004b..6329463b 100644 --- a/src/com/android/calendar/widget/CalendarAppWidgetService.java +++ b/src/com/android/calendar/widget/CalendarAppWidgetService.java @@ -286,8 +286,6 @@ public class CalendarAppWidgetService extends RemoteViewsService { views.setInt(R.id.title, "setTextColor", mDeclinedColor); views.setInt(R.id.when, "setTextColor", mDeclinedColor); views.setInt(R.id.where, "setTextColor", mDeclinedColor); - // views.setInt(R.id.agenda_item_color, "setDrawStyle", - // ColorChipView.DRAW_CROSS_HATCHED); views.setInt(R.id.agenda_item_color, "setImageResource", R.drawable.widget_chip_responded_bg); // 40% opacity diff --git a/tests/src/com/android/calendar/AsyncQueryServiceTest.java b/tests/src/com/android/calendar/AsyncQueryServiceTest.java deleted file mode 100644 index 70dbde34..00000000 --- a/tests/src/com/android/calendar/AsyncQueryServiceTest.java +++ /dev/null @@ -1,661 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.AsyncQueryService.Operation; -import com.android.calendar.AsyncQueryServiceHelper.OperationInfo; - -import android.content.ComponentName; -import android.content.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.test.IsolatedContext; -import android.test.RenamingDelegatingContext; -import android.test.ServiceTestCase; -import android.test.mock.MockContentResolver; -import android.test.mock.MockContext; -import android.test.mock.MockCursor; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Smoke; -import android.util.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * Unit tests for {@link android.text.format.DateUtils#formatDateRange}. - */ -public class AsyncQueryServiceTest extends ServiceTestCase<AsyncQueryServiceHelper> { - private static final String TAG = "AsyncQueryServiceTest"; - - private static final String AUTHORITY_URI = "content://AsyncQueryAuthority/"; - - private static final String AUTHORITY = "AsyncQueryAuthority"; - - private static final int MIN_DELAY = 50; - - private static final int BASE_TEST_WAIT_TIME = MIN_DELAY * 5; - - private static int mId = 0; - - private static final String[] TEST_PROJECTION = new String[] { - "col1", "col2", "col3" - }; - - private static final String TEST_SELECTION = "selection"; - - private static final String[] TEST_SELECTION_ARGS = new String[] { - "arg1", "arg2", "arg3" - }; - - public AsyncQueryServiceTest() { - super(AsyncQueryServiceHelper.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - private class MockContext2 extends MockContext { - @Override - public Resources getResources() { - return getContext().getResources(); - } - - @Override - public File getDir(String name, int mode) { - return getContext().getDir("mockcontext2_+" + name, mode); - } - - @Override - public Context getApplicationContext() { - return this; - } - } - - @Smoke - @SmallTest - public void testQuery() throws Exception { - int index = 0; - final OperationInfo[] work = new OperationInfo[1]; - work[index] = new OperationInfo(); - work[index].op = Operation.EVENT_ARG_QUERY; - - work[index].token = ++mId; - work[index].cookie = ++mId; - work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); - work[index].projection = TEST_PROJECTION; - work[index].selection = TEST_SELECTION; - work[index].selectionArgs = TEST_SELECTION_ARGS; - work[index].orderBy = "order"; - - work[index].delayMillis = 0; - work[index].result = new TestCursor(); - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); - aqs.startQuery(work[index].token, work[index].cookie, work[index].uri, - work[index].projection, work[index].selection, work[index].selectionArgs, - work[index].orderBy); - - Log.d(TAG, "testQuery Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testQuery Done <<<<<<<<<<<<<<"); - } - - @SmallTest - public void testInsert() throws Exception { - int index = 0; - final OperationInfo[] work = new OperationInfo[1]; - work[index] = new OperationInfo(); - work[index].op = Operation.EVENT_ARG_INSERT; - - work[index].token = ++mId; - work[index].cookie = ++mId; - work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); - work[index].values = new ContentValues(); - work[index].values.put("key", ++mId); - - work[index].delayMillis = 0; - work[index].result = Uri.parse(AUTHORITY_URI + "Result=" + ++mId); - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); - aqs.startInsert(work[index].token, work[index].cookie, work[index].uri, work[index].values, - work[index].delayMillis); - - Log.d(TAG, "testInsert Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testInsert Done <<<<<<<<<<<<<<"); - } - - @SmallTest - public void testUpdate() throws Exception { - int index = 0; - final OperationInfo[] work = new OperationInfo[1]; - work[index] = new OperationInfo(); - work[index].op = Operation.EVENT_ARG_UPDATE; - - work[index].token = ++mId; - work[index].cookie = ++mId; - work[index].uri = Uri.parse(AUTHORITY_URI + ++mId); - work[index].values = new ContentValues(); - work[index].values.put("key", ++mId); - work[index].selection = TEST_SELECTION; - work[index].selectionArgs = TEST_SELECTION_ARGS; - - work[index].delayMillis = 0; - work[index].result = ++mId; - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); - aqs.startUpdate(work[index].token, work[index].cookie, work[index].uri, work[index].values, - work[index].selection, work[index].selectionArgs, work[index].delayMillis); - - Log.d(TAG, "testUpdate Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testUpdate Done <<<<<<<<<<<<<<"); - } - - @SmallTest - public void testDelete() throws Exception { - int index = 0; - final OperationInfo[] work = new OperationInfo[1]; - work[index] = new OperationInfo(); - work[index].op = Operation.EVENT_ARG_DELETE; - - work[index].token = ++mId; - work[index].cookie = ++mId; - work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); - work[index].selection = TEST_SELECTION; - work[index].selectionArgs = TEST_SELECTION_ARGS; - - work[index].delayMillis = 0; - work[index].result = ++mId; - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); - aqs.startDelete(work[index].token, - work[index].cookie, - work[index].uri, - work[index].selection, - work[index].selectionArgs, - work[index].delayMillis); - - Log.d(TAG, "testDelete Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testDelete Done <<<<<<<<<<<<<<"); - } - - @SmallTest - public void testBatch() throws Exception { - int index = 0; - final OperationInfo[] work = new OperationInfo[1]; - work[index] = new OperationInfo(); - work[index].op = Operation.EVENT_ARG_BATCH; - - work[index].token = ++mId; - work[index].cookie = ++mId; - work[index].authority = AUTHORITY; - work[index].cpo = new ArrayList<ContentProviderOperation>(); - work[index].cpo.add(ContentProviderOperation.newInsert(Uri.parse(AUTHORITY_URI + ++mId)) - .build()); - - work[index].delayMillis = 0; - ContentProviderResult[] resultArray = new ContentProviderResult[1]; - resultArray[0] = new ContentProviderResult(++mId); - work[index].result = resultArray; - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); - aqs.startBatch(work[index].token, - work[index].cookie, - work[index].authority, - work[index].cpo, - work[index].delayMillis); - - Log.d(TAG, "testBatch Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testBatch Done <<<<<<<<<<<<<<"); - } - - @LargeTest - public void testDelay() throws Exception { - // Tests the ordering of the workqueue - int index = 0; - OperationInfo[] work = new OperationInfo[5]; - work[index++] = generateWork(MIN_DELAY * 2); - work[index++] = generateWork(0); - work[index++] = generateWork(MIN_DELAY * 1); - work[index++] = generateWork(0); - work[index++] = generateWork(MIN_DELAY * 3); - - OperationInfo[] sorted = generateSortedWork(work, work.length); - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(sorted), sorted); - startWork(aqs, work); - - Log.d(TAG, "testDelay Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", work.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testDelay Done <<<<<<<<<<<<<<"); - } - - @LargeTest - public void testCancel_simpleCancelLastTest() throws Exception { - int index = 0; - OperationInfo[] work = new OperationInfo[5]; - work[index++] = generateWork(MIN_DELAY * 2); - work[index++] = generateWork(0); - work[index++] = generateWork(MIN_DELAY); - work[index++] = generateWork(0); - work[index] = generateWork(MIN_DELAY * 3); - - // Not part of the expected as it will be canceled - OperationInfo toBeCancelled1 = work[index]; - OperationInfo[] expected = generateSortedWork(work, work.length - 1); - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); - startWork(aqs, work); - Operation lastOne = aqs.getLastCancelableOperation(); - // Log.d(TAG, "lastOne = " + lastOne.toString()); - // Log.d(TAG, "toBeCancelled1 = " + toBeCancelled1.toString()); - assertTrue("1) delay=3 is not last", toBeCancelled1.equivalent(lastOne)); - assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(lastOne.token)); - - Log.d(TAG, "testCancel_simpleCancelLastTest Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", expected.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testCancel_simpleCancelLastTest Done <<<<<<<<<<<<<<"); - } - - @LargeTest - public void testCancel_cancelSecondToLast() throws Exception { - int index = 0; - OperationInfo[] work = new OperationInfo[5]; - work[index++] = generateWork(MIN_DELAY * 2); - work[index++] = generateWork(0); - work[index++] = generateWork(MIN_DELAY); - work[index++] = generateWork(0); - work[index] = generateWork(MIN_DELAY * 3); - - // Not part of the expected as it will be canceled - OperationInfo toBeCancelled1 = work[index]; - OperationInfo[] expected = new OperationInfo[4]; - expected[0] = work[1]; // delay = 0 - expected[1] = work[3]; // delay = 0 - expected[2] = work[2]; // delay = MIN_DELAY - expected[3] = work[4]; // delay = MIN_DELAY * 3 - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); - startWork(aqs, work); - - Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3 - assertTrue("2) delay=3 is not last", toBeCancelled1.equivalent(lastOne)); - assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token)); - assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token)); - - Log.d(TAG, "testCancel_cancelSecondToLast Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", expected.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testCancel_cancelSecondToLast Done <<<<<<<<<<<<<<"); - } - - @LargeTest - public void testCancel_multipleCancels() throws Exception { - int index = 0; - OperationInfo[] work = new OperationInfo[5]; - work[index++] = generateWork(MIN_DELAY * 2); - work[index++] = generateWork(0); - work[index++] = generateWork(MIN_DELAY); - work[index++] = generateWork(0); - work[index] = generateWork(MIN_DELAY * 3); - - // Not part of the expected as it will be canceled - OperationInfo[] expected = new OperationInfo[3]; - expected[0] = work[1]; // delay = 0 - expected[1] = work[3]; // delay = 0 - expected[2] = work[2]; // delay = MIN_DELAY - - TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); - startWork(aqs, work); - - Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3 - assertTrue("3) delay=3 is not last", work[4].equivalent(lastOne)); - assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token)); - assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token)); - assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(work[4].token)); - assertEquals("Delay 3 should be gone", 0, aqs.cancelOperation(work[4].token)); - - Log.d(TAG, "testCancel_multipleCancels Waiting >>>>>>>>>>>"); - assertEquals("Not all operations were executed.", expected.length, aqs - .waitForCompletion(BASE_TEST_WAIT_TIME)); - Log.d(TAG, "testCancel_multipleCancels Done <<<<<<<<<<<<<<"); - } - - private OperationInfo generateWork(long delayMillis) { - OperationInfo work = new OperationInfo(); - work.op = Operation.EVENT_ARG_DELETE; - - work.token = ++mId; - work.cookie = 100 + work.token; - work.uri = Uri.parse(AUTHORITY_URI + "blah"); - work.selection = TEST_SELECTION; - work.selectionArgs = TEST_SELECTION_ARGS; - - work.delayMillis = delayMillis; - work.result = 1000 + work.token; - return work; - } - - private void startWork(TestAsyncQueryService aqs, OperationInfo[] work) { - for (OperationInfo w : work) { - if (w != null) { - aqs.startDelete(w.token, w.cookie, w.uri, w.selection, w.selectionArgs, - w.delayMillis); - } - } - } - - OperationInfo[] generateSortedWork(OperationInfo[] work, int length) { - OperationInfo[] sorted = new OperationInfo[length]; - System.arraycopy(work, 0, sorted, 0, length); - - // Set the scheduled time so they get sorted properly - for (OperationInfo w : sorted) { - if (w != null) { - w.calculateScheduledTime(); - } - } - - // Stable sort by scheduled time - Arrays.sort(sorted); - - Log.d(TAG, "Unsorted work: " + work.length); - for (OperationInfo w : work) { - if (w != null) { - Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis); - } - } - Log.d(TAG, "Sorted work: " + sorted.length); - for (OperationInfo w : sorted) { - if (w != null) { - Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis); - } - } - - return sorted; - } - - private Context buildTestContext(final OperationInfo[] work) { - MockContext context = new MockContext() { - MockContentResolver mResolver; - - @Override - public ContentResolver getContentResolver() { - if (mResolver == null) { - mResolver = new MockContentResolver(); - - final String filenamePrefix = "test."; - RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( - new MockContext2(), getContext(), filenamePrefix); - IsolatedContext providerContext = - new IsolatedContext(mResolver, targetContextWrapper); - - ContentProvider provider = new TestProvider(work); - provider.attachInfo(providerContext, null); - - mResolver.addProvider(AUTHORITY, provider); - } - return mResolver; - } - - @Override - public String getPackageName() { - return AsyncQueryServiceTest.class.getPackage().getName(); - } - - @Override - public ComponentName startService(Intent service) { - AsyncQueryServiceTest.this.startService(service); - return service.getComponent(); - } - }; - - return context; - } - - private final class TestCursor extends MockCursor { - int mUnique = ++mId; - - @Override - public int getCount() { - return mUnique; - } - } - - /** - * TestAsyncQueryService takes the expected results in the constructor. They - * are used to verify the data passed to the callbacks. - */ - class TestAsyncQueryService extends AsyncQueryService { - int mIndex = 0; - - private OperationInfo[] mWork; - - private Semaphore mCountingSemaphore; - - public TestAsyncQueryService(Context context, OperationInfo[] work) { - super(context); - mCountingSemaphore = new Semaphore(0); - - // run in a separate thread but call the same code - HandlerThread thread = new HandlerThread("TestAsyncQueryService"); - thread.start(); - super.setTestHandler(new Handler(thread.getLooper()) { - @Override - public void handleMessage(Message msg) { - TestAsyncQueryService.this.handleMessage(msg); - } - }); - - mWork = work; - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - Log.d(TAG, "onQueryComplete tid=" + Thread.currentThread().getId()); - Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); - - assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_QUERY); - assertEquals(mWork[mIndex].token, token); - /* - * Even though our TestProvider returned mWork[mIndex].result, it is - * wrapped with new'ed CursorWrapperInner and there's no equal() in - * CursorWrapperInner. assertEquals the two cursor will always fail. - * So just compare the count which will be unique in our TestCursor; - */ - assertEquals(((Cursor) mWork[mIndex].result).getCount(), cursor.getCount()); - - mIndex++; - mCountingSemaphore.release(); - } - - @Override - protected void onInsertComplete(int token, Object cookie, Uri uri) { - Log.d(TAG, "onInsertComplete tid=" + Thread.currentThread().getId()); - Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); - - assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_INSERT); - assertEquals(mWork[mIndex].token, token); - assertEquals(mWork[mIndex].result, uri); - - mIndex++; - mCountingSemaphore.release(); - } - - @Override - protected void onUpdateComplete(int token, Object cookie, int result) { - Log.d(TAG, "onUpdateComplete tid=" + Thread.currentThread().getId()); - Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); - - assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_UPDATE); - assertEquals(mWork[mIndex].token, token); - assertEquals(mWork[mIndex].result, result); - - mIndex++; - mCountingSemaphore.release(); - } - - @Override - protected void onDeleteComplete(int token, Object cookie, int result) { - Log.d(TAG, "onDeleteComplete tid=" + Thread.currentThread().getId()); - Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); - - assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_DELETE); - assertEquals(mWork[mIndex].token, token); - assertEquals(mWork[mIndex].result, result); - - mIndex++; - mCountingSemaphore.release(); - } - - @Override - protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) { - Log.d(TAG, "onBatchComplete tid=" + Thread.currentThread().getId()); - Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); - - assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_BATCH); - assertEquals(mWork[mIndex].token, token); - - ContentProviderResult[] expected = (ContentProviderResult[]) mWork[mIndex].result; - assertEquals(expected.length, results.length); - for (int i = 0; i < expected.length; ++i) { - assertEquals(expected[i].count, results[i].count); - assertEquals(expected[i].uri, results[i].uri); - } - - mIndex++; - mCountingSemaphore.release(); - } - - public int waitForCompletion(long timeoutMills) { - Log.d(TAG, "waitForCompletion tid=" + Thread.currentThread().getId()); - int count = 0; - try { - while (count < mWork.length) { - if (!mCountingSemaphore.tryAcquire(timeoutMills, TimeUnit.MILLISECONDS)) { - break; - } - count++; - } - } catch (InterruptedException e) { - } - return count; - } - } - - /** - * This gets called by AsyncQueryServiceHelper to read or write the data. It - * also verifies the data against the data passed in the constructor - */ - class TestProvider extends ContentProvider { - OperationInfo[] mWork; - - int index = 0; - - public TestProvider(OperationInfo[] work) { - mWork = work; - } - - @Override - public final Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String orderBy) { - Log.d(TAG, "Provider query index=" + index); - assertEquals(mWork[index].op, Operation.EVENT_ARG_QUERY); - assertEquals(mWork[index].uri, uri); - assertEquals(mWork[index].projection, projection); - assertEquals(mWork[index].selection, selection); - assertEquals(mWork[index].selectionArgs, selectionArgs); - assertEquals(mWork[index].orderBy, orderBy); - return (Cursor) mWork[index++].result; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - Log.d(TAG, "Provider insert index=" + index); - assertEquals(mWork[index].op, Operation.EVENT_ARG_INSERT); - assertEquals(mWork[index].uri, uri); - assertEquals(mWork[index].values, values); - return (Uri) mWork[index++].result; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - Log.d(TAG, "Provider update index=" + index); - assertEquals(mWork[index].op, Operation.EVENT_ARG_UPDATE); - assertEquals(mWork[index].uri, uri); - assertEquals(mWork[index].values, values); - assertEquals(mWork[index].selection, selection); - assertEquals(mWork[index].selectionArgs, selectionArgs); - return (Integer) mWork[index++].result; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - Log.d(TAG, "Provider delete index=" + index); - assertEquals(mWork[index].op, Operation.EVENT_ARG_DELETE); - assertEquals(mWork[index].uri, uri); - assertEquals(mWork[index].selection, selection); - assertEquals(mWork[index].selectionArgs, selectionArgs); - return (Integer) mWork[index++].result; - } - - @Override - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { - Log.d(TAG, "Provider applyBatch index=" + index); - assertEquals(mWork[index].op, Operation.EVENT_ARG_BATCH); - assertEquals(mWork[index].cpo, operations); - return (ContentProviderResult[]) mWork[index++].result; - } - - @Override - public String getType(Uri uri) { - return null; - } - - @Override - public boolean onCreate() { - return false; - } - } -} diff --git a/tests/src/com/android/calendar/DbTestUtils.java b/tests/src/com/android/calendar/DbTestUtils.java index ed9b5e91..508dc2ef 100644 --- a/tests/src/com/android/calendar/DbTestUtils.java +++ b/tests/src/com/android/calendar/DbTestUtils.java @@ -66,6 +66,10 @@ class DbTestUtils { return resources; } + public int getUserId() { + return 0; + } + public void setSharedPreferences(SharedPreferences sharedPreferences) { this.sharedPreferences = sharedPreferences; } diff --git a/tests/src/com/android/calendar/FormatDateRangeTest.java b/tests/src/com/android/calendar/FormatDateRangeTest.java index 1ba3ecc8..7736bdc3 100644 --- a/tests/src/com/android/calendar/FormatDateRangeTest.java +++ b/tests/src/com/android/calendar/FormatDateRangeTest.java @@ -73,29 +73,29 @@ public class FormatDateRangeTest extends AndroidTestCase { 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"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8 \u2013 11 AM"), new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 11, 0, - DateUtils.FORMAT_SHOW_TIME, "8:00AM \u2013 11:00AM"), + DateUtils.FORMAT_SHOW_TIME, "8:00 \u2013 11:00 AM"), 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"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8 AM \u2013 12 PM"), 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"), + "8 AM \u2013 12 PM"), 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"), + "8 AM \u2013 12 PM"), new DateTest(0, 10, 9, 10, 30, 0, 10, 9, 13, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "10:30AM \u2013 1pm"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "10:30 AM \u2013 1:00 PM"), new DateTest(0, 10, 9, 13, 0, 0, 10, 9, 14, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "1pm \u2013 2pm"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "1 \u2013 2 PM"), new DateTest(0, 10, 9, 0, 0, 0, 10, 9, 14, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 2pm"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12 AM \u2013 2 PM"), new DateTest(0, 10, 9, 20, 0, 0, 10, 10, 0, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8pm \u2013 midnight"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8 PM \u2013 12 AM"), new DateTest(0, 10, 10, 0, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12 AM"), 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"), @@ -114,15 +114,15 @@ public class FormatDateRangeTest extends AndroidTestCase { "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"), + "8 PM \u2013 12 AM"), 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"), + "8 PM \u2013 12 AM"), new DateTest(0, 10, 9, 0, 0, 0, 10, 10, 0, 0, - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 midnight"), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "Nov 9, 12 AM \u2013 Nov 10, 12 AM"), 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"), + "Nov 9, 00:00 \u2013 Nov 10, 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, @@ -143,19 +143,19 @@ public class FormatDateRangeTest extends AndroidTestCase { 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"), + "Dec 29, 2007, 8 AM \u2013 Jan 2, 2008, 5 PM"), 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"), + "Nov 9, 8 AM \u2013 Nov 11, 5 PM"), 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"), + "Fri, Nov 9 \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"), + "Fri, Nov 9, 2007, 8 AM \u2013 Sun, Nov 11, 2007, 5 PM"), 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"), + "December 3, 2007, 1:00 \u2013 2:00 PM"), // 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, diff --git a/tests/src/com/android/calendar/UtilsTests.java b/tests/src/com/android/calendar/UtilsTests.java index 2a2bfbac..bf501d9d 100644 --- a/tests/src/com/android/calendar/UtilsTests.java +++ b/tests/src/com/android/calendar/UtilsTests.java @@ -190,13 +190,6 @@ public class UtilsTests extends TestCase { @Smoke @SmallTest - public void testCheckForDuplicateNames() { - Utils.checkForDuplicateNames(mIsDuplicateName, mDuplicateNameCursor, NAME_COLUMN); - assertEquals(mIsDuplicateNameExpected, mIsDuplicateName); - } - - @Smoke - @SmallTest public void testGetWeeksSinceEpochFromJulianDay() { for (int i = 0; i < JULIAN_DAYS.length; i++) { assertEquals(EXPECTED_WEEK_MONDAY_START[i], @@ -217,20 +210,6 @@ public class UtilsTests extends TestCase { } } - @Smoke - @SmallTest - public void testEquals() { - assertTrue(Utils.equals(null, null)); - assertFalse(Utils.equals("", null)); - assertFalse(Utils.equals(null, "")); - assertTrue(Utils.equals("","")); - - Integer int1 = new Integer(1); - Integer int2 = new Integer(1); - assertTrue(Utils.equals(int1, int2)); - } - - // Helper function to create test events for BusyBits testing Event buildTestEvent(int startTime, int endTime, boolean allDay, int startDay, int endDay) { Event e = new Event(); @@ -244,263 +223,6 @@ public class UtilsTests extends TestCase { return e; } - @Smoke - @SmallTest - public void testCreateBusyBitSegments() { - - /* ArrayList<Event> events = new ArrayList<Event>(); - - // Test cases that should return null - // Empty events list - assertEquals(null, Utils.createBusyBitSegments(10, 30, 100, 200, 0, events)); - // No events list - assertEquals(null, Utils.createBusyBitSegments(10, 30, 100, 200, 0, null)); - - events.add(buildTestEvent(100, 130, false, 1, 1)); - events.add(buildTestEvent(1000, 1030, false, 1, 1)); - // Illegal pixel positions - assertEquals(null, Utils.createBusyBitSegments(30, 10, 100, 200, 1, events)); - // Illegal start and end times - assertEquals(null, Utils.createBusyBitSegments(10, 30, 200, 100, 1, events)); - assertEquals(null, Utils.createBusyBitSegments(10, 30, -10, 100, 1, events)); - assertEquals(null, Utils.createBusyBitSegments(10, 30, 24 * 60 + 100, 24 * 60 + 200, 1, - events)); - assertEquals(null, Utils.createBusyBitSegments(10, 30, 200, 24 * 60 + 100, 1, events)); - assertEquals(null, Utils.createBusyBitSegments(10, 30, 200, -100, 1, events)); - // No Events in time frame - assertEquals(null, Utils.createBusyBitSegments(10, 30, 500, 900, 1, events)); - - // Test event that spans over the day - events.clear(); - events.add(buildTestEvent(100, 300, false, 1, 5)); - ArrayList<BusyBitsSegment> segments = new ArrayList<BusyBitsSegment>(); - assertEquals(null, Utils.createBusyBitSegments(0, 250, 200, 1200, 3, events)); - - // test zero times events, events that are partially in the time span - // and all day events - events.clear(); - events.add(buildTestEvent(100, 300, false, 1, 1)); - events.add(buildTestEvent(1100, 1300, false, 1, 1)); - events.add(buildTestEvent(500, 600, true, 1, 1)); - events.add(buildTestEvent(700, 700, false, 1, 1)); - segments.clear(); - segments.add(new BusyBitsSegment(0, 10, false)); - segments.add(new BusyBitsSegment(90, 100, false)); - assertEquals(segments, Utils.createBusyBitSegments(0, 100, 200, 1200, 1, events)); - - // Test event that spans over 2 days but start and end time do not - // overlap fully with tested time span - - events.clear(); - events.add(buildTestEvent(23 * 60, 120, false, 1, 2)); - segments.clear(); - segments.add(new BusyBitsSegment(0, 120, false)); - assertEquals(segments, Utils.createBusyBitSegments(0, 240, 60, 180, 2, events)); - - // Test overlapped events (two draw sizes) - events.clear(); - events.add(buildTestEvent(10, 200, false, 1, 1)); - events.add(buildTestEvent(150, 250, false, 1, 1)); - events.add(buildTestEvent(150, 250, false, 1, 1)); - events.add(buildTestEvent(200, 400, false, 1, 1)); - events.add(buildTestEvent(500, 700, false, 1, 1)); - events.add(buildTestEvent(550, 600, false, 1, 1)); - events.add(buildTestEvent(550, 580, false, 1, 1)); - events.add(buildTestEvent(560, 570, false, 1, 1)); - events.add(buildTestEvent(600, 700, false, 1, 1)); - events.add(buildTestEvent(620, 700, false, 1, 1)); - events.add(buildTestEvent(650, 700, false, 1, 1)); - events.add(buildTestEvent(800, 900, false, 1, 1)); - events.add(buildTestEvent(800, 900, false, 1, 1)); - events.add(buildTestEvent(800, 850, false, 1, 1)); - events.add(buildTestEvent(1000, 1200, false, 1, 1)); - events.add(buildTestEvent(1000, 1200, false, 1, 1)); - segments.clear(); - segments.add(new BusyBitsSegment(100, 149, false)); - segments.add(new BusyBitsSegment(150, 250, true)); - segments.add(new BusyBitsSegment(251, 400, false)); - segments.add(new BusyBitsSegment(500, 549, false)); - segments.add(new BusyBitsSegment(550, 700, true)); - segments.add(new BusyBitsSegment(800, 900, true)); - segments.add(new BusyBitsSegment(1000, 1100, true)); - assertEquals(segments, Utils.createBusyBitSegments(100, 1100, 100, 1100, 1, events)); - segments.clear(); - segments.add(new BusyBitsSegment(100, 111, false)); - segments.add(new BusyBitsSegment(112, 137, true)); - segments.add(new BusyBitsSegment(138, 175, false)); - segments.add(new BusyBitsSegment(200, 211, false)); - segments.add(new BusyBitsSegment(212, 250, true)); - segments.add(new BusyBitsSegment(275, 300, true)); - segments.add(new BusyBitsSegment(325, 350, true)); - assertEquals(segments, Utils.createBusyBitSegments(100, 350, 100, 1100, 1, events)); -*/ - } - - /** - * Tests the findNanpPhoneNumbers function. - */ - @SmallTest - public void testFindNanpPhoneNumber() { - final String[] NO_NUMBERS = new String[] {}; - - findPhoneNumber("", NO_NUMBERS); - findPhoneNumber(" ", NO_NUMBERS); - findPhoneNumber("123", NO_NUMBERS); - findPhoneNumber("how much wood", NO_NUMBERS); - findPhoneNumber("abc1-650-555-1212", NO_NUMBERS); - findPhoneNumber("abc 5551212 def", new String[] { "5551212" }); - findPhoneNumber("1234567", NO_NUMBERS); - findPhoneNumber(" 2345678 ", new String[] { "2345678" }); - findPhoneNumber("1234567890", NO_NUMBERS); - findPhoneNumber("12345678901", new String[] { "12345678901" }); - findPhoneNumber("123456789012", NO_NUMBERS); - findPhoneNumber("+1-555-1212", NO_NUMBERS); - findPhoneNumber("+1 (650) 555-1212", new String[] { "+1 (650) 555-1212" }); - findPhoneNumber("(650) 555-1212, (650) 555-1213", - new String[] { "(650) 555-1212", "(650) 555-1213" }); - findPhoneNumber("Call 555-1212, 555-1213 and also 555-1214.", - new String[] { "555-1212", "555-1213", "555-1214." }); - findPhoneNumber("555-1212,555-1213,555-1214", new String[] { "555-1212" }); - findPhoneNumber("123 (650) 555-1212", new String[] { "(650) 555-1212" }); - findPhoneNumber("1-650-555-1212", new String[] { "1-650-555-1212" }); - findPhoneNumber("1650-555-1212", new String[] { "1650-555-1212" }); - findPhoneNumber("1650 555-1212", new String[] { "1650 555-1212" }); - findPhoneNumber("1650/555-1212", NO_NUMBERS); - findPhoneNumber("1650-555 1212", NO_NUMBERS); - findPhoneNumber("8-650-555-1212", NO_NUMBERS); - findPhoneNumber("8 650-555-1212", new String[] { "650-555-1212" }); - findPhoneNumber("650.555.1212", new String[] { "650.555.1212" }); - findPhoneNumber(" *#650.555.1212#*!", new String[] { "*#650.555.1212#*" }); - findPhoneNumber("555.1212", new String[] { "555.1212" }); - findPhoneNumber("6505551212 x123, 555-1212", new String[] { "6505551212", "555-1212" }); - findPhoneNumber("6505551212x123", new String[] { "6505551212" }); - findPhoneNumber("http://example.com/6505551212/", NO_NUMBERS); - findPhoneNumber("Mountain View, CA 94043 (650) 555-1212", new String[]{ "(650) 555-1212" }); - findPhoneNumber("New York, NY 10001-0001", NO_NUMBERS); - } - - /** - * Finds the numbers in a block of text, and checks to see if the positions of the numbers - * match the expected values. - * - * @param text The text to search. - * @param matches Pairs of start/end positions. - */ - private static void findPhoneNumber(String text, String[] matches) { - int[] results = Utils.findNanpPhoneNumbers(text); - - assertEquals(0, results.length % 2); - - if (results.length / 2 != matches.length) { - fail("Text '" + text + "': expected " + matches.length - + " matches, found " + results.length / 2); - } - - for (int i = 0; i < results.length / 2; i++) { - CharSequence seq = text.subSequence(results[i*2], results[i*2 + 1]); - assertEquals(matches[i], seq); - } - } - - /** - * Tests the linkify section of event locations. - */ - @SmallTest - public void testExtendedLinkify() { - final URLSpan[] NO_LINKS = new URLSpan[] {}; - URLSpan span_tel01 = new URLSpan("tel:6505551234"); - URLSpan span_tel02 = new URLSpan("tel:5555678"); - URLSpan span_tel03 = new URLSpan("tel:+16505551234"); - URLSpan span_tel04 = new URLSpan("tel:16505551234"); - URLSpan span_web = new URLSpan("http://www.google.com"); - URLSpan span_geo01 = - new URLSpan("geo:0,0?q=1600+Amphitheatre+Parkway%2C+Mountain+View+CA+94043"); - URLSpan span_geo02 = - new URLSpan("geo:0,0?q=37.422081°, -122.084576°"); - URLSpan span_geo03 = - new URLSpan("geo:0,0?q=37.422081,-122.084576"); - URLSpan span_geo04 = - new URLSpan("geo:0,0?q=+37°25'19.49\", -122°5'4.47\""); - URLSpan span_geo05 = - new URLSpan("geo:0,0?q=37°25'19.49\"N, 122°5'4.47\"W"); - URLSpan span_geo06 = - new URLSpan("geo:0,0?q=N 37° 25' 19.49\", W 122° 5' 4.47\""); - URLSpan span_geo07 = new URLSpan("geo:0,0?q=non-specified address"); - - - // First test without the last-ditch geo attempt. - // Phone spans. - findLinks("", NO_LINKS, false); - findLinks("(650) 555-1234", new URLSpan[]{span_tel01}, false); - findLinks("94043", NO_LINKS, false); - findLinks("123456789012", NO_LINKS, false); - findLinks("+1 (650) 555-1234", new URLSpan[]{span_tel03}, false); - findLinks("(650) 555 1234", new URLSpan[]{span_tel01}, false); - findLinks("1-650-555-1234", new URLSpan[]{span_tel04}, false); - findLinks("*#650.555.1234#*!", new URLSpan[]{span_tel01}, false); - findLinks("555.5678", new URLSpan[]{span_tel02}, false); - - // Web spans. - findLinks("http://www.google.com", new URLSpan[]{span_web}, false); - - // Geo spans. - findLinks("1600 Amphitheatre Parkway, Mountain View CA 94043", - new URLSpan[]{span_geo01}, false); - findLinks("37.422081°, -122.084576°", new URLSpan[]{span_geo02}, false); - findLinks("37.422081,-122.084576", new URLSpan[]{span_geo03}, false); - findLinks("+37°25'19.49\", -122°5'4.47\"", new URLSpan[]{span_geo04}, false); - findLinks("37°25'19.49\"N, 122°5'4.47\"W", new URLSpan[]{span_geo05}, false); - findLinks("N 37° 25' 19.49\", W 122° 5' 4.47\"", new URLSpan[]{span_geo06}, false); - - // Multiple spans. - findLinks("(650) 555-1234 1600 Amphitheatre Parkway, Mountain View CA 94043", - new URLSpan[]{span_tel01, span_geo01}, false); - findLinks("(650) 555-1234, 555-5678", new URLSpan[]{span_tel01, span_tel02}, false); - - - // Now test using the last-ditch geo attempt. - findLinks("", NO_LINKS, true); - findLinks("(650) 555-1234", new URLSpan[]{span_tel01}, true); - findLinks("http://www.google.com", new URLSpan[]{span_web}, true); - findLinks("1600 Amphitheatre Parkway, Mountain View CA 94043", - new URLSpan[]{span_geo01}, true); - findLinks("37.422081°, -122.084576°", new URLSpan[]{span_geo02}, true); - findLinks("37.422081,-122.084576", new URLSpan[]{span_geo03}, true); - findLinks("+37°25'19.49\", -122°5'4.47\"", new URLSpan[]{span_geo04}, true); - findLinks("37°25'19.49\"N, 122°5'4.47\"W", new URLSpan[]{span_geo05}, true); - findLinks("N 37° 25' 19.49\", W 122° 5' 4.47\"", new URLSpan[]{span_geo06}, true); - findLinks("non-specified address", new URLSpan[]{span_geo07}, true); - } - - private static void findLinks(String text, URLSpan[] matches, boolean lastDitchGeo) { - Spannable spanText = Utils.extendedLinkify(text, lastDitchGeo); - URLSpan[] spansFound = spanText.getSpans(0, spanText.length(), URLSpan.class); - assertEquals(matches.length, spansFound.length); - - // Make sure the expected matches list of URLs is the same as that returned by linkify. - ArrayList<URLSpan> matchesArrayList = new ArrayList<URLSpan>(Arrays.asList(matches)); - for (URLSpan spanFound : spansFound) { - Iterator<URLSpan> matchesIt = matchesArrayList.iterator(); - boolean removed = false; - while (matchesIt.hasNext()) { - URLSpan match = matchesIt.next(); - if (match.getURL().equals(spanFound.getURL())) { - matchesIt.remove(); - removed = true; - break; - } - } - if (!removed) { - // If a match was not found for the current spanFound, the lists aren't equal. - fail("No match found for span: "+spanFound.getURL()); - } - } - - // As a sanity check, ensure the matches list is empty, as each item should have been - // removed by going through the spans returned by linkify. - assertTrue(matchesArrayList.isEmpty()); - } - @SmallTest public void testGetDisplayedDatetime_differentYear() { // 4/12/2000 5pm - 4/12/2000 6pm @@ -508,14 +230,14 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 18, 12, 3, 2000); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Wednesday, April 12, 2000, 5:00PM \u2013 6:00PM", result); + assertEquals("Wednesday, April 12, 2000, 5:00 \u2013 6:00 PM", result); // 12/31/2012 5pm - 1/1/2013 6pm start = createTimeInMillis(0, 0, 17, 31, 11, 2012); end = createTimeInMillis(0, 0, 18, 1, 0, 2013); result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Mon, Dec 31, 2012, 5:00PM – Tue, Jan 1, 2013, 6:00PM", result); + assertEquals("Mon, Dec 31, 2012, 5:00 PM – Tue, Jan 1, 2013, 6:00 PM", result); } @SmallTest @@ -525,7 +247,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 18, 12, 3, 2012); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Thursday, April 12, 2012, 5:00PM \u2013 6:00PM", result); + assertEquals("Thursday, April 12, 2012, 5:00 \u2013 6:00 PM", result); } @SmallTest @@ -535,7 +257,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 18, NOW_DAY, NOW_MONTH, NOW_YEAR); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Today at 5:00PM \u2013 6:00PM", result); + assertEquals("Today at 5:00 \u2013 6:00 PM", result); } @SmallTest @@ -545,7 +267,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 0, NOW_DAY + 1, NOW_MONTH, NOW_YEAR); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Today at 5:00PM \u2013 midnight", result); + assertEquals("Today at 5:00 PM \u2013 12:00 AM", result); } @SmallTest @@ -555,7 +277,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 59, 23, NOW_DAY + 1, NOW_MONTH, NOW_YEAR); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Tomorrow at 12:01AM \u2013 11:59PM", result); + assertEquals("Tomorrow at 12:01 AM \u2013 11:59 PM", result); } @SmallTest @@ -565,7 +287,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 18, 9, 3, 2012); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Monday, April 9, 2012, 5:00PM \u2013 6:00PM", result); + assertEquals("Monday, April 9, 2012, 5:00 \u2013 6:00 PM", result); } @SmallTest @@ -575,7 +297,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 1, 0, NOW_DAY + 1, NOW_MONTH, NOW_YEAR); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, false, dbUtils.getContext()); - assertEquals("Tue, Apr 10, 2012, 12:01AM \u2013 Wed, Apr 11, 2012, 12:01AM", result); + assertEquals("Tue, Apr 10, 2012, 12:01 AM \u2013 Wed, Apr 11, 2012, 12:01 AM", result); } @SmallTest @@ -605,7 +327,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 0, NOW_DAY + 3, NOW_MONTH, NOW_YEAR, Time.TIMEZONE_UTC); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, DEFAULT_TIMEZONE, true, dbUtils.getContext()); - assertEquals("Tuesday, April 10, 2012 \u2013 Thursday, April 12, 2012", result); + assertEquals("Tuesday, April 10 \u2013 Thursday, April 12, 2012", result); } @SmallTest @@ -619,7 +341,7 @@ public class UtilsTests extends TestCase { long end = createTimeInMillis(0, 0, 18, 12, 3, 2012, eventTz); String result = Utils.getDisplayedDatetime(start, end, NOW_TIME, localTz, false, dbUtils.getContext()); - assertEquals("Thursday, April 12, 2012, 8:00PM \u2013 9:00PM", result); + assertEquals("Thursday, April 12, 2012, 8:00 \u2013 9:00 PM", result); } @SmallTest diff --git a/tests/src/com/android/calendar/alerts/AlarmSchedulerTest.java b/tests/src/com/android/calendar/alerts/AlarmSchedulerTest.java deleted file mode 100644 index 1202e102..00000000 --- a/tests/src/com/android/calendar/alerts/AlarmSchedulerTest.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * 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.alerts; - -import android.app.AlarmManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Instances; -import android.provider.CalendarContract.Reminders; -import android.test.AndroidTestCase; -import android.test.IsolatedContext; -import android.test.mock.MockContentProvider; -import android.test.mock.MockContentResolver; -import android.test.suitebuilder.annotation.SmallTest; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Log; - -import junit.framework.Assert; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; - -@SmallTest -public class AlarmSchedulerTest extends AndroidTestCase { - private static final int BATCH_SIZE = 50; - private MockProvider mMockProvider; - private MockAlarmManager mMockAlarmManager; - private IsolatedContext mIsolatedContext; - - /** - * A helper class to mock query results from the test data. - */ - private static class MockProvider extends MockContentProvider { - private ArrayList<EventInfo> mEvents = new ArrayList<EventInfo>(); - private ArrayList<String> mExpectedRemindersQueries = new ArrayList<String>(); - private int mCurrentReminderQueryIndex = 0; - - /** - * Contains info for a test event and its reminder. - */ - private static class EventInfo { - long mEventId; - long mBegin; - boolean mAllDay; - int mReminderMinutes; - - public EventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) { - mEventId = eventId; - mAllDay = allDay; - mBegin = begin; - mReminderMinutes = reminderMinutes; - } - - } - - /** - * Adds event/reminder data for testing. These will always be returned in the mocked - * query result cursors. - */ - void addEventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) { - mEvents.add(new EventInfo(eventId, allDay, begin, reminderMinutes)); - } - - private MatrixCursor getInstancesCursor() { - MatrixCursor instancesCursor = new MatrixCursor(AlarmScheduler.INSTANCES_PROJECTION); - int i = 0; - HashSet<Long> eventIds = new HashSet<Long>(); - for (EventInfo event : mEvents) { - if (!eventIds.contains(event.mEventId)) { - Object[] ca = { - event.mEventId, - event.mBegin, - event.mAllDay ? 1 : 0, - }; - instancesCursor.addRow(ca); - eventIds.add(event.mEventId); - } - } - return instancesCursor; - } - - private MatrixCursor getRemindersCursor() { - MatrixCursor remindersCursor = new MatrixCursor(AlarmScheduler.REMINDERS_PROJECTION); - int i = 0; - for (EventInfo event : mEvents) { - Object[] ca = { - event.mEventId, - event.mReminderMinutes, - 1, - }; - remindersCursor.addRow(ca); - } - return remindersCursor; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - if (uri.toString().startsWith(Instances.CONTENT_URI.toString())) { - return getInstancesCursor(); - } else if (Reminders.CONTENT_URI.equals(uri)) { - if (mExpectedRemindersQueries.size() > 0) { - if (mExpectedRemindersQueries.size() <= mCurrentReminderQueryIndex || - !mExpectedRemindersQueries.get(mCurrentReminderQueryIndex).equals( - selection)) { - String msg = "Reminders query not as expected.\n"; - msg += " Expected:"; - msg += Arrays.deepToString(mExpectedRemindersQueries.toArray()); - msg += "\n Got in position " + mCurrentReminderQueryIndex + ": "; - msg += selection; - fail(msg); - } - mCurrentReminderQueryIndex++; - } - return getRemindersCursor(); - } else { - return super.query(uri, projection, selection, selectionArgs, sortOrder); - } - } - - /** - * Optionally set up expectation for the reminders query selection. - */ - public void addExpectedRemindersQuery(String expectedRemindersQuery) { - this.mExpectedRemindersQueries.add(expectedRemindersQuery); - } - } - - /** - * Expect an alarm for the specified time. - */ - private void expectAlarmAt(long millis) { - // AlarmScheduler adds a slight delay to the alarm so account for that here. - mMockAlarmManager.expectAlarmTime(AlarmManager.RTC_WAKEUP, - millis + AlarmScheduler.ALARM_DELAY_MS); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mMockProvider = new MockProvider(); - mMockAlarmManager = new MockAlarmManager(mContext); - MockContentResolver mockResolver = new MockContentResolver(); - mockResolver.addProvider(CalendarContract.AUTHORITY, mMockProvider); - mIsolatedContext = new IsolatedContext(mockResolver, mContext); - } - - public void testNoEvents() { - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, - BATCH_SIZE, System.currentTimeMillis()); - assertFalse(mMockAlarmManager.isAlarmSet()); - } - - public void testNonAllDayEvent() { - // Set up mock test data. - long currentMillis = System.currentTimeMillis(); - long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS; - int reminderMin = 10; - mMockProvider.addEventInfo(1, false, startMillis, reminderMin); - expectAlarmAt(startMillis - reminderMin * DateUtils.MINUTE_IN_MILLIS); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testAllDayEvent() { - // Set up mock allday data. - long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC); - long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, - Time.getCurrentTimezone()); - long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS; - int reminderMin = 15; - mMockProvider.addEventInfo(1, true, startMillisUtc, reminderMin); - expectAlarmAt(startMillisLocal - reminderMin * DateUtils.MINUTE_IN_MILLIS); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testAllDayAndNonAllDayEvents() { - // Set up mock test data. - long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC); - long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, - Time.getCurrentTimezone()); - long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS; - mMockProvider.addEventInfo(1, true, startMillisUtc, 15); - mMockProvider.addEventInfo(1, false, startMillisLocal, 10); - expectAlarmAt(startMillisLocal - 15 * DateUtils.MINUTE_IN_MILLIS); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testExpiredReminder() { - // Set up mock test data. - long currentMillis = System.currentTimeMillis(); - long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS; - int reminderMin = 61; - mMockProvider.addEventInfo(1, false, startMillis, reminderMin); - - // Invoke scheduleNextAlarm and verify no alarm was set. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertFalse(mMockAlarmManager.isAlarmSet()); - } - - public void testAlarmMax() { - // Set up mock test data for a reminder greater than 1 day in the future. - // This will be maxed out to 1 day out. - long currentMillis = System.currentTimeMillis(); - long startMillis = currentMillis + DateUtils.DAY_IN_MILLIS * 3; - int reminderMin = (int) DateUtils.DAY_IN_MILLIS / (1000 * 60); - mMockProvider.addEventInfo(1, false, startMillis, reminderMin); - expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testMultipleEvents() { - // Set up multiple events where a later event time has an earlier reminder time. - long currentMillis = System.currentTimeMillis(); - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 0); - mMockProvider.addEventInfo(2, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 60, 45); - mMockProvider.addEventInfo(3, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 30, 10); - - // Expect event 2's reminder. - expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 15); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testRecurringEvents() { - long currentMillis = System.currentTimeMillis(); - - // Event in 3 days, with a 2 day reminder - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS * 3, - (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */); - // Event for tomorrow, with a 2 day reminder - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, - (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */); - - // Expect the reminder for the top event because the reminder time for the bottom - // one already passed. - expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testMultipleRemindersForEvent() { - // Set up mock test data. - long currentMillis = System.currentTimeMillis(); - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 10); - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 20); - mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 15); - - // Expect earliest reminder. - expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS - DateUtils.MINUTE_IN_MILLIS * 20); - - // Invoke scheduleNextAlarm and verify alarm was set at the expected time. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, - currentMillis); - assertTrue(mMockAlarmManager.isAlarmSet()); - } - - public void testLargeBatch() { - // Add enough events to require several batches. - long currentMillis = System.currentTimeMillis(); - int batchSize = 5; - for (int i = 19; i > 0; i--) { - mMockProvider.addEventInfo(i, false, currentMillis + DateUtils.HOUR_IN_MILLIS * i, - 10); - } - - // Set up expectations for the batch queries. - expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 50); - mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (19,18,17,16,15)"); - mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (14,13,12,11,10)"); - mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (9,8,7,6,5)"); - mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (4,3,2,1)"); - - // Invoke scheduleNextAlarm and verify alarm and reminder query batches. - AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, batchSize, - currentMillis); - } -} diff --git a/tests/src/com/android/calendar/alerts/AlertServiceTest.java b/tests/src/com/android/calendar/alerts/AlertServiceTest.java deleted file mode 100644 index 52da47c4..00000000 --- a/tests/src/com/android/calendar/alerts/AlertServiceTest.java +++ /dev/null @@ -1,1033 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * 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.alerts; - -import static android.app.Notification.PRIORITY_DEFAULT; -import static android.app.Notification.PRIORITY_HIGH; -import static android.app.Notification.PRIORITY_MIN; - -import android.app.AlarmManager; -import android.content.SharedPreferences; -import android.database.MatrixCursor; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.CalendarAlerts; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Smoke; -import android.text.format.DateUtils; -import android.text.format.Time; - -import com.android.calendar.GeneralPreferences; -import com.android.calendar.alerts.AlertService.NotificationInfo; -import com.android.calendar.alerts.AlertService.NotificationWrapper; - -import junit.framework.Assert; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.Set; - -public class AlertServiceTest extends AndroidTestCase { - - class MockSharedPreferences implements SharedPreferences { - - private Boolean mVibrate; - private String mRingtone; - private Boolean mPopup; - - // Strict mode will fail if a preference key is queried more than once. - private boolean mStrict = false; - - MockSharedPreferences() { - this(false); - } - - MockSharedPreferences(boolean strict) { - super(); - init(); - this.mStrict = strict; - } - - void init() { - mVibrate = true; - mRingtone = "/some/cool/ringtone"; - mPopup = true; - } - - @Override - public boolean contains(String key) { - if (GeneralPreferences.KEY_ALERTS_VIBRATE.equals(key)) { - return true; - } - return false; - } - - @Override - public boolean getBoolean(String key, boolean defValue) { - if (GeneralPreferences.KEY_ALERTS_VIBRATE.equals(key)) { - if (mVibrate == null) { - Assert.fail(GeneralPreferences.KEY_ALERTS_VIBRATE - + " fetched more than once."); - } - boolean val = mVibrate; - if (mStrict) { - mVibrate = null; - } - return val; - } - if (GeneralPreferences.KEY_ALERTS_POPUP.equals(key)) { - if (mPopup == null) { - Assert.fail(GeneralPreferences.KEY_ALERTS_POPUP + " fetched more than once."); - } - boolean val = mPopup; - if (mStrict) { - mPopup = null; - } - return val; - } - throw new IllegalArgumentException(); - } - - @Override - public String getString(String key, String defValue) { - if (GeneralPreferences.KEY_ALERTS_RINGTONE.equals(key)) { - if (mRingtone == null) { - Assert.fail(GeneralPreferences.KEY_ALERTS_RINGTONE - + " fetched more than once."); - } - String val = mRingtone; - if (mStrict) { - mRingtone = null; - } - return val; - } - throw new IllegalArgumentException(); - } - - @Override - public Map<String, ?> getAll() { - throw new IllegalArgumentException(); - } - - @Override - public Set<String> getStringSet(String key, Set<String> defValues) { - throw new IllegalArgumentException(); - } - - @Override - public int getInt(String key, int defValue) { - throw new IllegalArgumentException(); - } - - @Override - public long getLong(String key, long defValue) { - throw new IllegalArgumentException(); - } - - @Override - public float getFloat(String key, float defValue) { - throw new IllegalArgumentException(); - } - - @Override - public Editor edit() { - throw new IllegalArgumentException(); - } - - @Override - public void registerOnSharedPreferenceChangeListener( - OnSharedPreferenceChangeListener listener) { - throw new IllegalArgumentException(); - } - - @Override - public void unregisterOnSharedPreferenceChangeListener( - OnSharedPreferenceChangeListener listener) { - throw new IllegalArgumentException(); - } - - } - - // Created these constants so the test cases are shorter - public static final int SCHEDULED = CalendarAlerts.STATE_SCHEDULED; - public static final int FIRED = CalendarAlerts.STATE_FIRED; - public static final int DISMISSED = CalendarAlerts.STATE_DISMISSED; - - public static final int ACCEPTED = Attendees.ATTENDEE_STATUS_ACCEPTED; - public static final int DECLINED = Attendees.ATTENDEE_STATUS_DECLINED; - public static final int INVITED = Attendees.ATTENDEE_STATUS_INVITED; - public static final int TENTATIVE = Attendees.ATTENDEE_STATUS_TENTATIVE; - - class NotificationInstance { - int mAlertId; - int[] mAlertIdsInDigest; - int mPriority; - - public NotificationInstance(int alertId, int priority) { - mAlertId = alertId; - mPriority = priority; - } - - public NotificationInstance(int[] alertIdsInDigest, int priority) { - mAlertIdsInDigest = alertIdsInDigest; - mPriority = priority; - } - } - - class Alert { - long mEventId; - int mAlertStatus; - int mResponseStatus; - int mAllDay; - long mBegin; - long mEnd; - int mMinute; - long mAlarmTime; - - public Alert(long eventId, int alertStatus, int responseStatus, int allDay, long begin, - long end, int minute, long alarmTime) { - mEventId = eventId; - mAlertStatus = alertStatus; - mResponseStatus = responseStatus; - mAllDay = allDay; - mBegin = begin; - mEnd = end; - mMinute = minute; - mAlarmTime = alarmTime; - } - - } - - class AlertsTable { - - ArrayList<Alert> mAlerts = new ArrayList<Alert>(); - - int addAlertRow(long eventId, int alertStatus, int responseStatus, int allDay, long begin, - long end, long alarmTime) { - Alert a = new Alert(eventId, alertStatus, responseStatus, allDay, begin, end, - 5 /* minute */, alarmTime); - int id = mAlerts.size(); - mAlerts.add(a); - return id; - } - - public MatrixCursor getAlertCursor() { - MatrixCursor alertCursor = new MatrixCursor(AlertService.ALERT_PROJECTION); - - int i = 0; - for (Alert a : mAlerts) { - Object[] ca = { - i++, - a.mEventId, - a.mAlertStatus, - "Title" + a.mEventId + " " + a.mMinute, - "Loc" + a.mEventId, - a.mResponseStatus, - a.mAllDay, - a.mAlarmTime > 0 ? a.mAlarmTime : a.mBegin - a.mMinute * 60 * 1000, - a.mMinute, - a.mBegin, - a.mEnd, - "Desc: " + a.mAlarmTime - }; - alertCursor.addRow(ca); - } - return alertCursor; - } - - } - - class NotificationTestManager extends NotificationMgr { - // Expected notifications - NotificationInstance[] mExpectedNotifications; - NotificationWrapper[] mActualNotifications; - boolean[] mCancelled; - - // CalendarAlerts table - private ArrayList<Alert> mAlerts; - - public NotificationTestManager(ArrayList<Alert> alerts, int maxNotifications) { - assertEquals(0, AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID); - mAlerts = alerts; - mExpectedNotifications = new NotificationInstance[maxNotifications + 1]; - mActualNotifications = new NotificationWrapper[mExpectedNotifications.length]; - mCancelled = new boolean[mExpectedNotifications.length]; - } - - public void expectTestNotification(int notificationId, int alertId, int highPriority) { - mExpectedNotifications[notificationId] = new NotificationInstance(alertId, - highPriority); - } - - public void expectTestNotification(int notificationId, int[] alertIds, int priority) { - mExpectedNotifications[notificationId] = new NotificationInstance(alertIds, priority); - } - - private <T> boolean nullContents(T[] array) { - for (T item : array) { - if (item != null) { - return false; - } - } - return true; - } - - public void validateNotificationsAndReset() { - if (nullContents(mExpectedNotifications)) { - return; - } - - String debugStr = printActualNotifications(); - for (int id = 0; id < mActualNotifications.length; id++) { - NotificationInstance expected = mExpectedNotifications[id]; - NotificationWrapper actual = mActualNotifications[id]; - if (expected == null) { - assertNull("Received unexpected notificationId " + id + debugStr, actual); - assertTrue("NotificationId " + id + " should have been cancelled." + debugStr, - mCancelled[id]); - } else { - assertNotNull("Expected notificationId " + id + " but it was not posted." - + debugStr, actual); - assertFalse("NotificationId " + id + " should not have been cancelled." - + debugStr, mCancelled[id]); - assertEquals("Priority not as expected for notification " + id + debugStr, - expected.mPriority, actual.mNotification.priority); - if (expected.mAlertIdsInDigest == null) { - Alert a = mAlerts.get(expected.mAlertId); - assertEquals("Event ID not expected for notification " + id + debugStr, - a.mEventId, actual.mEventId); - assertEquals("Begin time not expected for notification " + id + debugStr, - a.mBegin, actual.mBegin); - assertEquals("End time not expected for notification " + id + debugStr, - a.mEnd, actual.mEnd); - } else { - // Notification should be a digest. - assertNotNull("Posted notification not a digest as expected." + debugStr, - actual.mNw); - assertEquals("Number of notifications in digest not as expected." - + debugStr, expected.mAlertIdsInDigest.length, actual.mNw.size()); - for (int i = 0; i < actual.mNw.size(); i++) { - Alert a = mAlerts.get(expected.mAlertIdsInDigest[i]); - assertEquals("Digest item " + i + ": Event ID not as expected" - + debugStr, a.mEventId, actual.mNw.get(i).mEventId); - assertEquals("Digest item " + i + ": Begin time in digest not expected" - + debugStr, a.mBegin, actual.mNw.get(i).mBegin); - assertEquals("Digest item " + i + ": End time in digest not expected" - + debugStr, a.mEnd, actual.mNw.get(i).mEnd); - } - } - } - } - - Arrays.fill(mCancelled, false); - Arrays.fill(mExpectedNotifications, null); - Arrays.fill(mActualNotifications, null); - } - - private String printActualNotifications() { - StringBuilder s = new StringBuilder(); - s.append("\n\nNotifications actually posted:\n"); - for (int i = mActualNotifications.length - 1; i >= 0; i--) { - NotificationWrapper actual = mActualNotifications[i]; - if (actual == null) { - continue; - } - s.append("Notification " + i + " -- "); - s.append("priority:" + actual.mNotification.priority); - if (actual.mNw == null) { - s.append(", eventId:" + actual.mEventId); - } else { - s.append(", eventIds:{"); - for (int digestIndex = 0; digestIndex < actual.mNw.size(); digestIndex++) { - s.append(actual.mNw.get(digestIndex).mEventId + ","); - } - s.append("}"); - } - s.append("\n"); - } - return s.toString(); - } - - /////////////////////////////// - // NotificationMgr methods - @Override - public void cancel(int id) { - assertTrue("id out of bound: " + id, 0 <= id); - assertTrue("id out of bound: " + id, id < mCancelled.length); - assertNull("id already used", mActualNotifications[id]); - assertFalse("id already used", mCancelled[id]); - mCancelled[id] = true; - assertNull("Unexpected cancel for id " + id, mExpectedNotifications[id]); - } - - @Override - public void notify(int id, NotificationWrapper nw) { - assertTrue("id out of bound: " + id, 0 <= id); - assertTrue("id out of bound: " + id, id < mExpectedNotifications.length); - assertNull("id already used: " + id, mActualNotifications[id]); - mActualNotifications[id] = nw; - } - } - - // TODO - // Catch updates of new state, notify time, and received time - // Test ringer, vibrate, - // Test intents, action email - - @Smoke - @SmallTest - public void testGenerateAlerts_none() { - MockSharedPreferences prefs = new MockSharedPreferences(); - AlertsTable at = new AlertsTable(); - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, - AlertService.MAX_NOTIFICATIONS); - - // Test no alert - long currentTime = 1000000; - AlertService.generateAlerts(mContext, ntm, new MockAlarmManager(mContext), prefs, - at.getAlertCursor(), currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - } - - @Smoke - @SmallTest - public void testGenerateAlerts_single() { - MockSharedPreferences prefs = new MockSharedPreferences(); - MockAlarmManager alarmMgr = new MockAlarmManager(mContext); - AlertsTable at = new AlertsTable(); - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, - AlertService.MAX_NOTIFICATIONS); - - int id = at.addAlertRow(100, SCHEDULED, ACCEPTED, 0 /* all day */, 1300000, 2300000, 0); - - // Test one up coming alert - long currentTime = 1000000; - ntm.expectTestNotification(1, id, PRIORITY_HIGH); - - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), currentTime, - AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); // This wipes out notification - // tests added so far - - // Test half way into an event - currentTime = 2300000; - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_MIN); - - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), currentTime, - AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - - // Test event ended - currentTime = 4300000; - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_MIN); - - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), currentTime, - AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - } - - @SmallTest - public void testGenerateAlerts_multiple() { - int maxNotifications = 10; - MockSharedPreferences prefs = new MockSharedPreferences(); - MockAlarmManager alarmMgr = new MockAlarmManager(mContext); - AlertsTable at = new AlertsTable(); - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications); - - // Current time - 5:00 - long currentTime = createTimeInMillis(5, 0); - - // Set up future alerts. The real query implementation sorts by descending start - // time so simulate that here with our order of adds to AlertsTable. - int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0), - createTimeInMillis(10, 0), 0); - int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0), - createTimeInMillis(9, 0), 0); - int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0), - createTimeInMillis(8, 0), 0); - - // Set up concurrent alerts (that started recently). - int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0), - createTimeInMillis(5, 40), 0); - int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55), - createTimeInMillis(7, 30), 0); - int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50), - createTimeInMillis(4, 50), 0); - - // Set up past alerts. - int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0), - createTimeInMillis(4, 0), 0); - int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0), - createTimeInMillis(3, 0), 0); - int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0), - createTimeInMillis(2, 0), 0); - - // Check posted notifications. The order listed here is the order simulates the - // order in the real notification bar (last one posted appears on top), so these - // should be lowest start time on top. - ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future - ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future - ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id3, id2, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, maxNotifications); - ntm.validateNotificationsAndReset(); - - // Increase time by 15 minutes to check that some concurrent events dropped - // to the low priority bucket. - currentTime = createTimeInMillis(5, 15); - ntm.expectTestNotification(4, id5, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future - ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future - ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id6, id4, id3, id2, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, maxNotifications); - ntm.validateNotificationsAndReset(); - - // Increase time so some of the previously future ones change state. - currentTime = createTimeInMillis(8, 15); - ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id8, id7, id6, id5, id4, id3, id2, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, maxNotifications); - ntm.validateNotificationsAndReset(); - } - - @SmallTest - public void testGenerateAlerts_maxAlerts() { - MockSharedPreferences prefs = new MockSharedPreferences(); - MockAlarmManager alarmMgr = new MockAlarmManager(mContext); - AlertsTable at = new AlertsTable(); - - // Current time - 5:00 - long currentTime = createTimeInMillis(5, 0); - - // Set up future alerts. The real query implementation sorts by descending start - // time so simulate that here with our order of adds to AlertsTable. - int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0), - createTimeInMillis(10, 0), 0); - int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0), - createTimeInMillis(9, 0), 0); - int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0), - createTimeInMillis(8, 0), 0); - - // Set up concurrent alerts (that started recently). - int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0), - createTimeInMillis(5, 40), 0); - int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55), - createTimeInMillis(7, 30), 0); - int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50), - createTimeInMillis(4, 50), 0); - - // Set up past alerts. - int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0), - createTimeInMillis(4, 0), 0); - int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0), - createTimeInMillis(3, 0), 0); - int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0), - createTimeInMillis(2, 0), 0); - - // Test when # alerts = max. - int maxNotifications = 6; - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications); - ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future - ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future - ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id3, id2, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, maxNotifications); - ntm.validateNotificationsAndReset(); - - // Test when # alerts > max. - maxNotifications = 4; - ntm = new NotificationTestManager(at.mAlerts, maxNotifications); - ntm.expectTestNotification(4, id4, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(3, id5, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(2, id6, PRIORITY_HIGH); // concurrent - ntm.expectTestNotification(1, id7, PRIORITY_HIGH); // future - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id9, id8, id3, id2, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, maxNotifications); - ntm.validateNotificationsAndReset(); - } - - /** - * Test that the SharedPreferences are only fetched once for each setting. - */ - @SmallTest - public void testGenerateAlerts_sharedPreferences() { - MockSharedPreferences prefs = new MockSharedPreferences(true /* strict mode */); - AlertsTable at = new AlertsTable(); - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, - AlertService.MAX_NOTIFICATIONS); - - // Current time - 5:00 - long currentTime = createTimeInMillis(5, 0); - - // Set up future alerts. The real query implementation sorts by descending start - // time so simulate that here with our order of adds to AlertsTable. - at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0), - createTimeInMillis(10, 0), 0); - at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0), - createTimeInMillis(9, 0), 0); - at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0), - createTimeInMillis(8, 0), 0); - - // If this does not result in a failure (MockSharedPreferences fails for duplicate - // queries), then test passes. - AlertService.generateAlerts(mContext, ntm, new MockAlarmManager(mContext), prefs, - at.getAlertCursor(), currentTime, AlertService.MAX_NOTIFICATIONS); - } - - public void testGenerateAlerts_refreshTime() { - AlertsTable at = new AlertsTable(); - MockSharedPreferences prefs = new MockSharedPreferences(); - MockAlarmManager alarmMgr = new MockAlarmManager(mContext); - NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, - AlertService.MAX_NOTIFICATIONS); - - // Since AlertService.processQuery uses DateUtils.isToday instead of checking against - // the passed in currentTime (not worth allocating the extra Time objects to do so), use - // today's date for this test. - Time now = new Time(); - now.setToNow(); - int day = now.monthDay; - int month = now.month; - int year = now.year; - Time yesterday = new Time(); - yesterday.set(System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS); - Time tomorrow = new Time(); - tomorrow.set(System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS); - long allDayStart = Utils.createTimeInMillis(0, 0, 0, day, month, year, Time.TIMEZONE_UTC); - - /* today 10am - 10:30am */ - int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, - Utils.createTimeInMillis(0, 0, 10, day, month, year, Time.getCurrentTimezone()), - Utils.createTimeInMillis(0, 30, 10, day, month, year, Time.getCurrentTimezone()), - 0); - /* today 6am - 6am (0 duration event) */ - int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, - Utils.createTimeInMillis(0, 0, 6, day, month, year, Time.getCurrentTimezone()), - Utils.createTimeInMillis(0, 0, 6, day, month, year, Time.getCurrentTimezone()), 0); - /* today allDay */ - int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 1, allDayStart, - allDayStart + DateUtils.HOUR_IN_MILLIS * 24, 0); - /* yesterday 11pm - today 7am (multiday event) */ - int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, - Utils.createTimeInMillis(0, 0, 23, yesterday.monthDay, yesterday.month, - yesterday.year, Time.getCurrentTimezone()), - Utils.createTimeInMillis(0, 0, 7, day, month, year, Time.getCurrentTimezone()), 0); - - // Test at midnight - next refresh should be 15 min later (15 min into the all - // day event). - long currentTime = Utils.createTimeInMillis(0, 0, 0, day, month, year, - Time.getCurrentTimezone()); - alarmMgr.expectAlarmTime(AlarmManager.RTC, currentTime + 15 * DateUtils.MINUTE_IN_MILLIS); - ntm.expectTestNotification(4, id1, PRIORITY_HIGH); - ntm.expectTestNotification(3, id2, PRIORITY_HIGH); - ntm.expectTestNotification(2, id3, PRIORITY_HIGH); - ntm.expectTestNotification(1, id4, PRIORITY_HIGH); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - - // Test at 12:30am - next refresh should be 30 min later (1/4 into event 'id1'). - currentTime = Utils.createTimeInMillis(0, 30, 0, day, month, year, - Time.getCurrentTimezone()); - alarmMgr.expectAlarmTime(AlarmManager.RTC, currentTime + 30 * DateUtils.MINUTE_IN_MILLIS); - ntm.expectTestNotification(3, id1, PRIORITY_HIGH); - ntm.expectTestNotification(2, id3, PRIORITY_HIGH); - ntm.expectTestNotification(1, id4, PRIORITY_HIGH); - ntm.expectTestNotification(4, id2, PRIORITY_DEFAULT); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - - // Test at 5:55am - next refresh should be 20 min later (15 min after 'id3'). - currentTime = Utils.createTimeInMillis(0, 55, 5, day, month, year, - Time.getCurrentTimezone()); - alarmMgr.expectAlarmTime(AlarmManager.RTC, currentTime + 20 * DateUtils.MINUTE_IN_MILLIS); - ntm.expectTestNotification(2, id3, PRIORITY_HIGH); - ntm.expectTestNotification(1, id4, PRIORITY_HIGH); - ntm.expectTestNotification(3, id2, PRIORITY_DEFAULT); - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id1, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - - // Test at 10:14am - next refresh should be 1 min later (15 min into event 'id4'). - currentTime = Utils.createTimeInMillis(0, 14, 10, day, month, year, - Time.getCurrentTimezone()); - alarmMgr.expectAlarmTime(AlarmManager.RTC, currentTime + 1 * DateUtils.MINUTE_IN_MILLIS); - ntm.expectTestNotification(1, id4, PRIORITY_HIGH); - ntm.expectTestNotification(2, id2, PRIORITY_DEFAULT); - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, new int[] {id3, id1}, - PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - - // Test at 10:15am - next refresh should be tomorrow midnight (end of all day event 'id2'). - currentTime = Utils.createTimeInMillis(0, 15, 10, day, month, year, - Time.getCurrentTimezone()); - alarmMgr.expectAlarmTime(AlarmManager.RTC, Utils.createTimeInMillis(0, 0, 23, - tomorrow.monthDay, tomorrow.month, tomorrow.year, Time.getCurrentTimezone())); - ntm.expectTestNotification(1, id2, PRIORITY_DEFAULT); - ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, - new int[] {id4, id3, id1}, PRIORITY_MIN); - AlertService.generateAlerts(mContext, ntm, alarmMgr, prefs, at.getAlertCursor(), - currentTime, AlertService.MAX_NOTIFICATIONS); - ntm.validateNotificationsAndReset(); - } - - private NotificationInfo createNotificationInfo(long eventId) { - return new NotificationInfo("eventName", "location", "description", 100L, 200L, eventId, - false, false); - } - - private static long createTimeInMillis(int hour, int minute) { - return Utils.createTimeInMillis(0 /* second */, minute, hour, 1 /* day */, 1 /* month */, - 2012 /* year */, Time.getCurrentTimezone()); - } - - @SmallTest - public void testProcessQuery_skipDeclinedDismissed() { - int declinedEventId = 1; - int dismissedEventId = 2; - int acceptedEventId = 3; - long acceptedStartTime = createTimeInMillis(10, 0); - long acceptedEndTime = createTimeInMillis(10, 30); - - AlertsTable at = new AlertsTable(); - at.addAlertRow(declinedEventId, SCHEDULED, DECLINED, 0, createTimeInMillis(9, 0), - createTimeInMillis(10, 0), 0); - at.addAlertRow(dismissedEventId, SCHEDULED, DISMISSED, 0, createTimeInMillis(9, 30), - createTimeInMillis(11, 0), 0); - at.addAlertRow(acceptedEventId, SCHEDULED, ACCEPTED, 1, acceptedStartTime, acceptedEndTime, - 0); - - ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>(); - long currentTime = createTimeInMillis(5, 0); - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals(acceptedEventId, highPriority.get(0).eventId); - assertEquals(acceptedStartTime, highPriority.get(0).startMillis); - assertEquals(acceptedEndTime, highPriority.get(0).endMillis); - assertTrue(highPriority.get(0).allDay); - } - - @SmallTest - public void testProcessQuery_newAlert() { - int scheduledAlertEventId = 1; - int firedAlertEventId = 2; - - AlertsTable at = new AlertsTable(); - at.addAlertRow(scheduledAlertEventId, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0), - createTimeInMillis(10, 0), 0); - at.addAlertRow(firedAlertEventId, FIRED, ACCEPTED, 0, createTimeInMillis(4, 0), - createTimeInMillis(10, 30), 0); - - ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>(); - long currentTime = createTimeInMillis(5, 0); - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(2, highPriority.size()); - assertEquals(scheduledAlertEventId, highPriority.get(0).eventId); - assertTrue("newAlert should be ON for scheduled alerts", highPriority.get(0).newAlert); - assertEquals(firedAlertEventId, highPriority.get(1).eventId); - assertFalse("newAlert should be OFF for fired alerts", highPriority.get(1).newAlert); - } - - @SmallTest - public void testProcessQuery_recurringEvent() { - int eventId = 1; - long earlierStartTime = createTimeInMillis(10, 0); - long laterStartTime = createTimeInMillis(11, 0); - - ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>(); - - AlertsTable at = new AlertsTable(); - at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 0, laterStartTime, - laterStartTime + DateUtils.HOUR_IN_MILLIS, 0); - at.addAlertRow(eventId, FIRED, ACCEPTED, 0, earlierStartTime, - earlierStartTime + DateUtils.HOUR_IN_MILLIS, 0); - - // Both events in the future: the earliest one should be chosen. - long currentTime = earlierStartTime - DateUtils.DAY_IN_MILLIS * 5; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with earlier start time expected", earlierStartTime, - highPriority.get(0).startMillis); - - // Increment time just past the earlier event: the earlier one should be chosen. - highPriority.clear(); - currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 10; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with earlier start time expected", earlierStartTime, - highPriority.get(0).startMillis); - - // Increment time to 15 min past the earlier event: the later one should be chosen. - highPriority.clear(); - currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 15; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with later start time expected", laterStartTime, - highPriority.get(0).startMillis); - - // Both events in the past: the later one should be chosen (in the low priority bucket). - highPriority.clear(); - currentTime = laterStartTime + DateUtils.DAY_IN_MILLIS * 5; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, highPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, lowPriority.size()); - assertEquals("Recurring event with later start time expected", laterStartTime, - lowPriority.get(0).startMillis); - } - - @SmallTest - public void testProcessQuery_recurringAllDayEvent() { - int eventId = 1; - long day1 = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC); - long day2 = Utils.createTimeInMillis(0, 0, 0, 2, 5, 2012, Time.TIMEZONE_UTC); - - ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>(); - - AlertsTable at = new AlertsTable(); - at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day2, day2 + DateUtils.HOUR_IN_MILLIS * 24, - 0); - at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day1, day1 + DateUtils.HOUR_IN_MILLIS * 24, - 0); - - // Both events in the future: the earliest one should be chosen. - long currentTime = day1 - DateUtils.DAY_IN_MILLIS * 3; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with earlier start time expected", day1, - highPriority.get(0).startMillis); - - // Increment time just past the earlier event (to 12:10am). The earlier one should - // be chosen. - highPriority.clear(); - currentTime = Utils.createTimeInMillis(0, 10, 0, 1, 5, 2012, Time.getCurrentTimezone()); - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with earlier start time expected", day1, - highPriority.get(0).startMillis); - - // Increment time to 15 min past the earlier event: the later one should be chosen. - highPriority.clear(); - currentTime = Utils.createTimeInMillis(0, 15, 0, 1, 5, 2012, Time.getCurrentTimezone()); - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, lowPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, highPriority.size()); - assertEquals("Recurring event with earlier start time expected", day2, - highPriority.get(0).startMillis); - - // Both events in the past: the later one should be chosen (in the low priority bucket). - highPriority.clear(); - currentTime = day2 + DateUtils.DAY_IN_MILLIS * 1; - AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority, - mediumPriority, lowPriority); - assertEquals(0, highPriority.size()); - assertEquals(0, mediumPriority.size()); - assertEquals(1, lowPriority.size()); - assertEquals("Recurring event with later start time expected", day2, - lowPriority.get(0).startMillis); - } - - @SmallTest - public void testRedistributeBuckets_withinLimits() throws Exception { - int maxNotifications = 3; - ArrayList<NotificationInfo> threeItemList = new ArrayList<NotificationInfo>(); - threeItemList.add(createNotificationInfo(5)); - threeItemList.add(createNotificationInfo(4)); - threeItemList.add(createNotificationInfo(3)); - - // Test when max notifications at high priority. - ArrayList<NotificationInfo> high = threeItemList; - ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>(); - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - assertEquals(3, high.size()); - assertEquals(0, medium.size()); - assertEquals(0, low.size()); - - // Test when max notifications at medium priority. - high = new ArrayList<NotificationInfo>(); - medium = threeItemList; - low = new ArrayList<NotificationInfo>(); - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - assertEquals(0, high.size()); - assertEquals(3, medium.size()); - assertEquals(0, low.size()); - - // Test when max notifications at high and medium priority - high = new ArrayList<NotificationInfo>(threeItemList); - medium = new ArrayList<NotificationInfo>(); - medium.add(high.remove(1)); - low = new ArrayList<NotificationInfo>(); - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - assertEquals(2, high.size()); - assertEquals(1, medium.size()); - assertEquals(0, low.size()); - } - - @SmallTest - public void testRedistributeBuckets_tooManyHighPriority() throws Exception { - ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>(); - high.add(createNotificationInfo(5)); - high.add(createNotificationInfo(4)); - high.add(createNotificationInfo(3)); - high.add(createNotificationInfo(2)); - high.add(createNotificationInfo(1)); - - // Invoke the method under test. - int maxNotifications = 3; - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - - // Verify some high priority were kicked out. - assertEquals(3, high.size()); - assertEquals(3, high.get(0).eventId); - assertEquals(2, high.get(1).eventId); - assertEquals(1, high.get(2).eventId); - - // Verify medium priority untouched. - assertEquals(0, medium.size()); - - // Verify the extras went to low priority. - assertEquals(2, low.size()); - assertEquals(5, low.get(0).eventId); - assertEquals(4, low.get(1).eventId); - } - - @SmallTest - public void testRedistributeBuckets_tooManyMediumPriority() throws Exception { - ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>(); - high.add(createNotificationInfo(5)); - high.add(createNotificationInfo(4)); - medium.add(createNotificationInfo(3)); - medium.add(createNotificationInfo(2)); - medium.add(createNotificationInfo(1)); - - // Invoke the method under test. - int maxNotifications = 3; - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - - // Verify high priority untouched. - assertEquals(2, high.size()); - assertEquals(5, high.get(0).eventId); - assertEquals(4, high.get(1).eventId); - - // Verify some medium priority were kicked out (the ones near the end of the - // list). - assertEquals(1, medium.size()); - assertEquals(3, medium.get(0).eventId); - - // Verify the extras went to low priority. - assertEquals(2, low.size()); - assertEquals(2, low.get(0).eventId); - assertEquals(1, low.get(1).eventId); - } - - @SmallTest - public void testRedistributeBuckets_tooManyHighMediumPriority() throws Exception { - ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>(); - ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>(); - high.add(createNotificationInfo(8)); - high.add(createNotificationInfo(7)); - high.add(createNotificationInfo(6)); - high.add(createNotificationInfo(5)); - high.add(createNotificationInfo(4)); - medium.add(createNotificationInfo(3)); - medium.add(createNotificationInfo(2)); - medium.add(createNotificationInfo(1)); - - // Invoke the method under test. - int maxNotifications = 3; - AlertService.redistributeBuckets(high, medium, low, maxNotifications); - - // Verify high priority. - assertEquals(3, high.size()); - assertEquals(6, high.get(0).eventId); - assertEquals(5, high.get(1).eventId); - assertEquals(4, high.get(2).eventId); - - // Verify some medium priority. - assertEquals(0, medium.size()); - - // Verify low priority. - assertEquals(5, low.size()); - assertEquals(8, low.get(0).eventId); - assertEquals(7, low.get(1).eventId); - assertEquals(3, low.get(2).eventId); - assertEquals(2, low.get(3).eventId); - assertEquals(1, low.get(4).eventId); - } -} diff --git a/tests/src/com/android/calendar/alerts/MockAlarmManager.java b/tests/src/com/android/calendar/alerts/MockAlarmManager.java deleted file mode 100644 index 067a7865..00000000 --- a/tests/src/com/android/calendar/alerts/MockAlarmManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * 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.alerts; - -import android.app.PendingIntent; -import android.content.Context; -import android.text.format.DateUtils; - -import junit.framework.Assert; - -public class MockAlarmManager implements AlarmManagerInterface { - private Context context; - private int expectedAlarmType = -1; - private long expectedAlarmTime = -1; - private boolean alarmSet = false; - - MockAlarmManager(Context context) { - this.context = context; - } - - public void expectAlarmTime(int type, long millis) { - this.expectedAlarmType = type; - this.expectedAlarmTime = millis; - } - - @Override - public void set(int actualAlarmType, long actualAlarmTime, PendingIntent operation) { - Assert.assertNotNull(operation); - alarmSet = true; - if (expectedAlarmType != -1) { - Assert.assertEquals("Alarm type not expected.", expectedAlarmType, actualAlarmType); - Assert.assertEquals("Alarm time not expected. Expected:" + DateUtils.formatDateTime( - context, expectedAlarmTime, DateUtils.FORMAT_SHOW_TIME) + ", actual:" - + DateUtils.formatDateTime(context, actualAlarmTime, - DateUtils.FORMAT_SHOW_TIME), expectedAlarmTime, actualAlarmTime); - } - } - - /** - * Returns whether set() was invoked. - */ - public boolean isAlarmSet() { - return alarmSet; - } -} diff --git a/tests/src/com/android/calendar/alerts/Utils.java b/tests/src/com/android/calendar/alerts/Utils.java deleted file mode 100644 index b887c4b9..00000000 --- a/tests/src/com/android/calendar/alerts/Utils.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * 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.alerts; - -import android.text.format.Time; - -class Utils { - public static long createTimeInMillis(int second, int minute, int hour, int monthDay, - int month, int year, String timezone) { - Time t = new Time(timezone); - t.set(second, minute, hour, monthDay, month, year); - t.normalize(false); - return t.toMillis(false); - } -} diff --git a/tests/src/com/android/calendar/event/EditEventHelperTest.java b/tests/src/com/android/calendar/event/EditEventHelperTest.java deleted file mode 100644 index 46941fe2..00000000 --- a/tests/src/com/android/calendar/event/EditEventHelperTest.java +++ /dev/null @@ -1,1645 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.calendar.event; - -import android.content.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentValues; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.test.AndroidTestCase; -import android.test.mock.MockResources; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Smoke; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.text.util.Rfc822Token; - -import com.android.calendar.AbstractCalendarActivity; -import com.android.calendar.AsyncQueryService; -import com.android.calendar.CalendarEventModel; -import com.android.calendar.CalendarEventModel.ReminderEntry; -import com.android.calendar.R; -import com.android.calendar.Utils; -import com.android.common.Rfc822Validator; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.LinkedHashSet; -import java.util.TimeZone; - -public class EditEventHelperTest extends AndroidTestCase { - private static final int TEST_EVENT_ID = 1; - private static final int TEST_EVENT_INDEX_ID = 0; - private static final long TEST_END = 1272931200000L; - private static long TEST_END2 = 1272956400000L; - private static final long TEST_START = 1272844800000L; - private static long TEST_START2 = 1272870000000L; - private static final String LOCAL_TZ = TimeZone.getDefault().getID(); - - private static final int SAVE_EVENT_NEW_EVENT = 1; - private static final int SAVE_EVENT_MOD_RECUR = 2; - private static final int SAVE_EVENT_RECUR_TO_NORECUR = 3; - private static final int SAVE_EVENT_NORECUR_TO_RECUR= 4; - private static final int SAVE_EVENT_MOD_NORECUR = 5; - private static final int SAVE_EVENT_MOD_INSTANCE = 6; - private static final int SAVE_EVENT_ALLFOLLOW_TO_NORECUR = 7; - private static final int SAVE_EVENT_FIRST_TO_NORECUR = 8; - private static final int SAVE_EVENT_FIRST_TO_RECUR = 9; - private static final int SAVE_EVENT_ALLFOLLOW_TO_RECUR = 10; - - /* These should match up with EditEventHelper.EVENT_PROJECTION. - * Note that spaces and commas have been removed to allow for easier sanitation. - */ - private static String[] TEST_CURSOR_DATA = new String[] { - Integer.toString(TEST_EVENT_ID), // 0 _id - "The_Question", // 1 title - "Evaluating_Life_the_Universe_and_Everything", // 2 description - "Earth_Mk2", // 3 location - "1", // 4 All Day - "0", // 5 Has alarm - "2", // 6 Calendar id - "1272844800000", // 7 dtstart, Monday, May 3rd midnight UTC - "1272931200000", // 8 dtend, Tuesday, May 4th midnight UTC - "P3652421990D", // 9 duration, (10 million years) - "UTC", // 10 event timezone - "FREQ=DAILY;WKST=SU", // 11 rrule - "unique_per_calendar_stuff", // 12 sync id - "0", // 13 transparency/availability - "3", // 14 visibility/access level - "steve@gmail.com", // 15 owner account - "1", // 16 has attendee data - null, //17 originalSyncId - "organizer@gmail.com", // 18 organizer - "0", // 19 guest can modify - "-1", // 20 original id - "1", // 21 event status - "-339611", // 22 calendar color - "-2350809", // 23 event color - "11" // 24 event color key - }; - - private static final String AUTHORITY_URI = "content://EditEventHelperAuthority/"; - private static final String AUTHORITY = "EditEventHelperAuthority"; - - private static final String TEST_ADDRESSES = - "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " + - "one.two.three@email.grue"; - private static final String TEST_ADDRESSES2 = - "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " + - "different@email.bit"; - private static final String TEST_ADDRESSES3 = - "ad1@email.com, \"First Last\" <first@email.com> (comment), " + - "different@email.bit"; - private static final String TEST_ADDRESSES4 = - "ad1@email.com, \"First Last\" <first@email.com> (comment), " + - "one.two.three@email.grue"; - - - private static final String TAG = "EEHTest"; - - private Rfc822Validator mEmailValidator; - private CalendarEventModel mModel1; - private CalendarEventModel mModel2; - - private ContentValues mValues; - private ContentValues mExpectedValues; - - private EditEventHelper mHelper; - private AbstractCalendarActivity mActivity; - private int mCurrentSaveTest = 0; - - @Override - public void setUp() { - Time time = new Time(Time.TIMEZONE_UTC); - time.set(TEST_START); - time.timezone = LOCAL_TZ; - TEST_START2 = time.normalize(true); - - time.timezone = Time.TIMEZONE_UTC; - time.set(TEST_END); - time.timezone = LOCAL_TZ; - TEST_END2 = time.normalize(true); - - mEmailValidator = new Rfc822Validator(null); - } - - private class MockAbsCalendarActivity extends AbstractCalendarActivity { - @Override - public AsyncQueryService getAsyncQueryService() { - if (mService == null) { - mService = new AsyncQueryService(this) { - @Override - public void startBatch(int token, Object cookie, String authority, - ArrayList<ContentProviderOperation> cpo, long delayMillis) { - mockApplyBatch(authority, cpo); - } - }; - } - return mService; - } - - @Override - public Resources getResources() { - Resources res = new MockResources() { - @Override - // The actual selects singular vs plural as well and in the given language - public String getQuantityString(int id, int quantity) { - if (id == R.plurals.Nmins) { - return quantity + " mins"; - } - if (id == R.plurals.Nminutes) { - return quantity + " minutes"; - } - if (id == R.plurals.Nhours) { - return quantity + " hours"; - } - if (id == R.plurals.Ndays) { - return quantity + " days"; - } - return id + " " + quantity; - } - }; - return res; - } - } - - private AbstractCalendarActivity buildTestContext() { - MockAbsCalendarActivity context = new MockAbsCalendarActivity(); - return context; - } - - private ContentProviderResult[] mockApplyBatch(String authority, - ArrayList<ContentProviderOperation> operations) { - switch (mCurrentSaveTest) { - case SAVE_EVENT_NEW_EVENT: - // new recurring event - verifySaveEventNewEvent(operations); - break; - case SAVE_EVENT_MOD_RECUR: - // update to recurring event - verifySaveEventModifyRecurring(operations); - break; - case SAVE_EVENT_RECUR_TO_NORECUR: - // replace recurring event with non-recurring event - verifySaveEventRecurringToNonRecurring(operations); - break; - case SAVE_EVENT_NORECUR_TO_RECUR: - // update non-recurring event with recurring event - verifySaveEventNonRecurringToRecurring(operations); - break; - case SAVE_EVENT_MOD_NORECUR: - // update to non-recurring - verifySaveEventUpdateNonRecurring(operations); - break; - case SAVE_EVENT_MOD_INSTANCE: - // update to single instance of recurring event - verifySaveEventModifySingleInstance(operations); - break; - case SAVE_EVENT_ALLFOLLOW_TO_NORECUR: - // update all following with non-recurring event - verifySaveEventModifyAllFollowingWithNonRecurring(operations); - break; - case SAVE_EVENT_FIRST_TO_NORECUR: - // update all following with non-recurring event on first event in series - verifySaveEventModifyAllFollowingFirstWithNonRecurring(operations); - break; - case SAVE_EVENT_FIRST_TO_RECUR: - // update all following with recurring event on first event in series - verifySaveEventModifyAllFollowingFirstWithRecurring(operations); - break; - case SAVE_EVENT_ALLFOLLOW_TO_RECUR: - // update all following with recurring event on second event in series - verifySaveEventModifyAllFollowingWithRecurring(operations); - break; - } - return new ContentProviderResult[] {new ContentProviderResult(5)}; - } - - private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps, int id) { - addOwnerAttendee(); - ContentProviderOperation.Builder b; - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues); - b.withValueBackReference(Reminders.EVENT_ID, id); - expectedOps.add(b.build()); - } - - private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps) { - addOwnerAttendee(); - mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID); - ContentProviderOperation.Builder b; - b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues); - expectedOps.add(b.build()); - } - - - // Some tests set the time values to one day later, this does that move in the values - private void moveExpectedTimeValuesForwardOneDay() { - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mExpectedValues.put(Events.DTSTART, TEST_START + dayInMs); - mExpectedValues.put(Events.DTEND, TEST_END + dayInMs); - } - - // Duplicates the delete and add for changing a single email address - private void addAttendeeChangesOps(ArrayList<ContentProviderOperation> expectedOps) { - ContentProviderOperation.Builder b = - ContentProviderOperation.newDelete(Attendees.CONTENT_URI); - b.withSelection(EditEventHelper.ATTENDEES_DELETE_PREFIX + "?)", - new String[] {"one.two.three@email.grue"}); - expectedOps.add(b.build()); - - mExpectedValues.clear(); - mExpectedValues.put(Attendees.ATTENDEE_NAME, "different@email.bit"); - mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit"); - mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); - mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); - mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID); - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - expectedOps.add(b.build()); - } - - // This is a commonly added set of values - private void addOwnerAttendee() { - mExpectedValues.clear(); - mExpectedValues.put(Attendees.ATTENDEE_EMAIL, mModel1.mOwnerAccount); - mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); - mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); - } - - /** Some tests add all the attendees to the db, the names and emails should match - * with {@link #TEST_ADDRESSES2} minus the 'no good' - */ - private void addTestAttendees(ArrayList<ContentProviderOperation> ops, - boolean newEvent, int id) { - ContentProviderOperation.Builder b; - mExpectedValues.clear(); - mExpectedValues.put(Attendees.ATTENDEE_NAME, "ad1@email.com"); - mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "ad1@email.com"); - mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); - mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); - - if (newEvent) { - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - b.withValueBackReference(Attendees.EVENT_ID, id); - } else { - mExpectedValues.put(Attendees.EVENT_ID, id); - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - } - ops.add(b.build()); - - mExpectedValues.clear(); - mExpectedValues.put(Attendees.ATTENDEE_NAME, "First Last"); - mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "first@email.com"); - mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); - mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); - - if (newEvent) { - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - b.withValueBackReference(Attendees.EVENT_ID, id); - } else { - mExpectedValues.put(Attendees.EVENT_ID, id); - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - } - ops.add(b.build()); - - mExpectedValues.clear(); - mExpectedValues.put(Attendees.ATTENDEE_NAME, "different@email.bit"); - mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit"); - mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); - mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); - mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); - - if (newEvent) { - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - b.withValueBackReference(Attendees.EVENT_ID, id); - } else { - mExpectedValues.put(Attendees.EVENT_ID, id); - b = ContentProviderOperation - .newInsert(Attendees.CONTENT_URI) - .withValues(mExpectedValues); - } - ops.add(b.build()); - } - - @Smoke - @SmallTest - public void testSaveEventFailures() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - - // saveEvent should return false early if: - // -it was set to not ok - // -the model was null - // -the event doesn't represent the same event as the original event - // -there's a uri but an original event is not provided - mHelper.mEventOk = false; - assertFalse(mHelper.saveEvent(null, null, 0)); - mHelper.mEventOk = true; - assertFalse(mHelper.saveEvent(null, null, 0)); - mModel2.mId = 13; - assertFalse(mHelper.saveEvent(mModel1, mModel2, 0)); - mModel2.mId = mModel1.mId; - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - assertFalse(mHelper.saveEvent(mModel1, null, 0)); - } - - @Smoke - @SmallTest - public void testSaveEventNewEvent() { - // Creates a model of a new event for saving - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - mCurrentSaveTest = SAVE_EVENT_NEW_EVENT; - - assertTrue(mHelper.saveEvent(mModel1, null, 0)); - } - - private boolean verifySaveEventNewEvent(ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int br_id = 0; - mExpectedValues = buildTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - mExpectedValues.put(Events.HAS_ATTENDEE_DATA, 1); - ContentProviderOperation.Builder b = ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues); - expectedOps.add(b.build()); - - // This call has a separate unit test so we'll use it to simplify making the expected vals - mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders, - new ArrayList<ReminderEntry>(), true); - - addOwnerAttendeeToOps(expectedOps, br_id); - - addTestAttendees(expectedOps, true, br_id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifyRecurring() { - // Creates an original and an updated recurring event model - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - // Updating a recurring event with a new attendee list - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - mCurrentSaveTest = SAVE_EVENT_MOD_RECUR; - - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); - } - - private boolean verifySaveEventModifyRecurring(ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int br_id = 0; - mExpectedValues = buildTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - // This is tested elsewhere, used for convenience here - mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues, - EditEventHelper.MODIFY_ALL); - - expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( - mExpectedValues).build()); - - // This call has a separate unit test so we'll use it to simplify making the expected vals - mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, - mModel2.mReminders, false); - - addOwnerAttendeeToOps(expectedOps); - addAttendeeChangesOps(expectedOps); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventRecurringToNonRecurring() { - // Creates an original and an updated recurring event model - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - // Updating a recurring event with a new attendee list - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - // Replace an existing recurring event with a non-recurring event - mModel1.mRrule = null; - mModel1.mEnd = TEST_END; - mCurrentSaveTest = SAVE_EVENT_RECUR_TO_NORECUR; - - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); - } - - private boolean verifySaveEventRecurringToNonRecurring(ArrayList<ContentProviderOperation> ops) - { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int id = 0; - mExpectedValues = buildNonRecurringTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - // This is tested elsewhere, used for convenience here - mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, - EditEventHelper.MODIFY_ALL); - - expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build()); - id = expectedOps.size(); - expectedOps.add(ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues) - .build()); - - mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps, id); - - addTestAttendees(expectedOps, true, id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventNonRecurringToRecurring() { - // Creates an original non-recurring and an updated recurring event model - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - // Updating a recurring event with a new attendee list - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - mModel2.mRrule = null; - mModel2.mEnd = TEST_END; - mCurrentSaveTest = SAVE_EVENT_NORECUR_TO_RECUR; - - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); - } - - private boolean verifySaveEventNonRecurringToRecurring(ArrayList<ContentProviderOperation> ops) - { - // Changing a non-recurring event to a recurring event should generate the same operations - // as just modifying a recurring event. - return verifySaveEventModifyRecurring(ops); - } - - @Smoke - @SmallTest - public void testSaveEventUpdateNonRecurring() { - // Creates an original non-recurring and an updated recurring event model - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - // Updating a recurring event with a new attendee list - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - mModel2.mRrule = null; - mModel2.mEnd = TEST_END2; - mModel1.mRrule = null; - mModel1.mEnd = TEST_END2; - mCurrentSaveTest = SAVE_EVENT_MOD_NORECUR; - - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); - } - - private boolean verifySaveEventUpdateNonRecurring(ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int id = TEST_EVENT_ID; - mExpectedValues = buildNonRecurringTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - // This is tested elsewhere, used for convenience here - mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, - EditEventHelper.MODIFY_ALL); - expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( - mExpectedValues).build()); - // This call has a separate unit test so we'll use it to simplify making the expected vals - mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, - mModel2.mReminders, false); - addOwnerAttendeeToOps(expectedOps); - addAttendeeChangesOps(expectedOps); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifySingleInstance() { - // Creates an original non-recurring and an updated recurring event model - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - // Modify the second instance of the event - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mModel1.mRrule = null; - mModel1.mEnd = TEST_END + dayInMs; - mModel1.mStart += dayInMs; - mModel1.mOriginalStart = mModel1.mStart; - - mCurrentSaveTest = SAVE_EVENT_MOD_INSTANCE; - // Only modify this instance - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_SELECTED)); - } - - private boolean verifySaveEventModifySingleInstance(ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int id = 0; - mExpectedValues = buildNonRecurringTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - // This is tested elsewhere, used for convenience here - mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, - EditEventHelper.MODIFY_ALL); - - moveExpectedTimeValuesForwardOneDay(); - mExpectedValues.put(Events.ORIGINAL_SYNC_ID, mModel2.mSyncId); - mExpectedValues.put(Events.ORIGINAL_INSTANCE_TIME, mModel1.mOriginalStart); - mExpectedValues.put(Events.ORIGINAL_ALL_DAY, 1); - - ContentProviderOperation.Builder b = ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues); - expectedOps.add(b.build()); - - mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps, id); - - addTestAttendees(expectedOps, true, id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifyAllFollowingWithNonRecurring() { - // Creates an original and an updated recurring event model. The update starts on the 2nd - // instance of the original. - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - // Modify the second instance of the event - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mModel1.mRrule = null; - mModel1.mEnd = TEST_END + dayInMs; - mModel1.mStart += dayInMs; - mModel1.mOriginalStart = mModel1.mStart; - - mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_NORECUR; - // Only modify this instance - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); - } - - private boolean verifySaveEventModifyAllFollowingWithNonRecurring( - ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int id = 0; - mExpectedValues = buildNonRecurringTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - moveExpectedTimeValuesForwardOneDay(); - // This has a separate test - mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart); - id = expectedOps.size(); - expectedOps.add(ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues) - .build()); - - mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps, id); - - addTestAttendees(expectedOps, true, id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifyAllFollowingFirstWithNonRecurring() { - // Creates an original recurring and an updated non-recurring event model for the first - // instance. This should replace the original event with a non-recurring event. - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mModel2.mUri = mModel1.mUri; - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES3, null); - - // Move the event one day but keep original start set to the first instance - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mModel1.mRrule = null; - mModel1.mEnd = TEST_END + dayInMs; - mModel1.mStart += dayInMs; - - mCurrentSaveTest = SAVE_EVENT_FIRST_TO_NORECUR; - // Only modify this instance - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); - } - - private boolean verifySaveEventModifyAllFollowingFirstWithNonRecurring( - ArrayList<ContentProviderOperation> ops) { - - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int id = 0; - mExpectedValues = buildNonRecurringTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - moveExpectedTimeValuesForwardOneDay(); - - expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build()); - id = expectedOps.size(); - expectedOps.add(ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues) - .build()); - - mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps, id); - - addTestAttendees(expectedOps, true, id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifyAllFollowingFirstWithRecurring() { - // Creates an original recurring and an updated recurring event model for the first instance - // This should replace the original event with a new recurrence - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mModel2.mUri = mModel1.mUri; - // And a new start time to ensure the time fields aren't removed - mModel1.mOriginalStart = TEST_START; - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - // Move the event one day but keep original start set to the first instance - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mModel1.mStart += dayInMs; - - mCurrentSaveTest = SAVE_EVENT_FIRST_TO_RECUR; - // Only modify this instance - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); - } - - private boolean verifySaveEventModifyAllFollowingFirstWithRecurring( - ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int br_id = 0; - mExpectedValues = buildTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - moveExpectedTimeValuesForwardOneDay(); - mExpectedValues.put(Events.DTEND, (Long)null); - // This is tested elsewhere, used for convenience here - mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues, - EditEventHelper.MODIFY_ALL_FOLLOWING); - - expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( - mExpectedValues).build()); - - // This call has a separate unit test so we'll use it to simplify making the expected vals - mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps); - addAttendeeChangesOps(expectedOps); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testSaveEventModifyAllFollowingWithRecurring() { - // Creates an original recurring and an updated recurring event model - // for the second instance. This should end the original recurrence and add a new - // recurrence. - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); - - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - - // The original model is assumed correct so drop the no good bit - mModel2.addAttendees(TEST_ADDRESSES4, null); - - // Move the event one day and the original start so it references the second instance - long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; - mModel1.mStart += dayInMs; - mModel1.mOriginalStart = mModel1.mStart; - - mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_RECUR; - // Only modify this instance - assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); - } - - private boolean verifySaveEventModifyAllFollowingWithRecurring( - ArrayList<ContentProviderOperation> ops) { - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - int br_id = 0; - mExpectedValues = buildTestValues(); - mExpectedValues.put(Events.HAS_ALARM, 0); - moveExpectedTimeValuesForwardOneDay(); - mExpectedValues.put(Events.DTEND, (Long)null); - // This is tested elsewhere, used for convenience here - mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart); - - br_id = expectedOps.size(); - expectedOps.add(ContentProviderOperation - .newInsert(Events.CONTENT_URI) - .withValues(mExpectedValues) - .build()); - - // This call has a separate unit test so we'll use it to simplify making the expected vals - mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders, - mModel2.mReminders, true); - - addOwnerAttendeeToOps(expectedOps, br_id); - - addTestAttendees(expectedOps, true, br_id); - - assertEquals(expectedOps, ops); - return true; - } - - @Smoke - @SmallTest - public void testGetAddressesFromList() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - LinkedHashSet<Rfc822Token> expected = new LinkedHashSet<Rfc822Token>(); - expected.add(new Rfc822Token(null, "ad1@email.com", "")); - expected.add(new Rfc822Token("First Last", "first@email.com", "comment")); - expected.add(new Rfc822Token(null, "one.two.three@email.grue", "")); - - LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES, - new Rfc822Validator(null)); - assertEquals(expected, actual); - } - - @Smoke - @SmallTest - public void testConstructDefaultStartTime() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - long now = 0; - long expected = now + 30 * DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - // 2:00 -> 2:30 - now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST) - expected = now + 30 * DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - // 2:01 -> 2:30 - now += DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - // 2:02 -> 2:30 - now += DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - // 2:32 -> 3:00 - now += 30 * DateUtils.MINUTE_IN_MILLIS; - expected += 30 * DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - // 2:33 -> 3:00 - now += DateUtils.MINUTE_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultStartTime(now)); - - } - - @Smoke - @SmallTest - public void testConstructDefaultEndTime() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - long start = 1262340000000L; - long expected = start + DateUtils.HOUR_IN_MILLIS; - assertEquals(expected, mHelper.constructDefaultEndTime(start)); - } - - @Smoke - @SmallTest - public void testCheckTimeDependentFieldsNoChanges() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel2.mRrule = null; - - mValues = buildTestValues(); - mExpectedValues = buildTestValues(); - - // if any time/recurrence vals are different but there's no new rrule it - // shouldn't change - mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, EditEventHelper.MODIFY_ALL); - assertEquals(mExpectedValues, mValues); - - // also, if vals are different and it's not modifying all it shouldn't - // change. - mModel2.mRrule = "something else"; - mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, - EditEventHelper.MODIFY_SELECTED); - assertEquals(mExpectedValues, mValues); - - // if vals changed and modify all is selected dtstart should be updated - // by the difference - // between originalStart and start - mModel2.mOriginalStart = mModel2.mStart + 60000; // set the old time to - // one minute later - mModel2.mStart += 120000; // move the event another 1 minute. - - // shouldn't change for an allday event - // expectedVals.put(Events.DTSTART, mModel1.mStart + 60000); // should - // now be 1 minute later - // dtstart2 shouldn't change since it gets rezeroed in the local - // timezone for allDay events - - mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, - EditEventHelper.MODIFY_SELECTED); - assertEquals(mExpectedValues, mValues); - } - - @Smoke - @SmallTest - public void testCheckTimeDependentFieldsChanges() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mModel1 = buildTestModel(); - mModel2 = buildTestModel(); - mModel2.mRrule = null; - - mValues = buildTestValues(); - mExpectedValues = buildTestValues(); - - // if all the time values are the same it should remove them from vals - mModel2.mRrule = mModel1.mRrule; - mModel2.mStart = mModel1.mStart; - mModel2.mOriginalStart = mModel2.mStart; - - mExpectedValues.remove(Events.DTSTART); - mExpectedValues.remove(Events.DTEND); - mExpectedValues.remove(Events.DURATION); - mExpectedValues.remove(Events.ALL_DAY); - mExpectedValues.remove(Events.RRULE); - mExpectedValues.remove(Events.EVENT_TIMEZONE); - - mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, - EditEventHelper.MODIFY_SELECTED); - assertEquals(mExpectedValues, mValues); - - } - - @Smoke - @SmallTest - public void testUpdatePastEvents() { - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - long initialBeginTime = 1472864400000L; // Sep 3, 2016, 12AM UTC time - mValues = new ContentValues(); - - mModel1 = buildTestModel(); - mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - - mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903;WKST=SU"); // yyyymmddThhmmssZ - mValues.put(Events.DTSTART, TEST_START); - - ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate( - Uri.parse(mModel1.mUri)).withValues(mValues); - expectedOps.add(b.build()); - - mHelper.updatePastEvents(ops, mModel1, initialBeginTime); - assertEquals(expectedOps, ops); - - mModel1.mAllDay = false; - - mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903T005959Z;WKST=SU"); // yyyymmddThhmmssZ - - expectedOps.clear(); - b = ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(mValues); - expectedOps.add(b.build()); - - ops.clear(); - mHelper.updatePastEvents(ops, mModel1, initialBeginTime); - assertEquals(expectedOps, ops); - } - - @Smoke - @SmallTest - public void testConstructReminderLabel() { - mActivity = buildTestContext(); - - String label = EventViewUtils.constructReminderLabel(mActivity, 35, true); - assertEquals("35 mins", label); - - label = EventViewUtils.constructReminderLabel(mActivity, 72, false); - assertEquals("72 minutes", label); - - label = EventViewUtils.constructReminderLabel(mActivity, 60, true); - assertEquals("1 hours", label); - - label = EventViewUtils.constructReminderLabel(mActivity, 60 * 48, true); - assertEquals("2 days", label); - } - - @Smoke - @SmallTest - public void testIsSameEvent() { - mModel1 = new CalendarEventModel(); - mModel2 = new CalendarEventModel(); - - mModel1.mId = 1; - mModel1.mCalendarId = 1; - mModel2.mId = 1; - mModel2.mCalendarId = 1; - - // considered the same if the event and calendar ids both match - assertTrue(EditEventHelper.isSameEvent(mModel1, mModel2)); - - mModel2.mId = 2; - assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2)); - - mModel2.mId = 1; - mModel2.mCalendarId = 2; - assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2)); - } - - @Smoke - @SmallTest - public void testSaveReminders() { - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - long eventId = TEST_EVENT_ID; - ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>(); - ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>(); - boolean forceSave = true; - boolean result; - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - assertNotNull(mHelper); - - // First test forcing a delete with no reminders. - String where = Reminders.EVENT_ID + "=?"; - String[] args = new String[] {Long.toString(eventId)}; - ContentProviderOperation.Builder b = - ContentProviderOperation.newDelete(Reminders.CONTENT_URI); - b.withSelection(where, args); - expectedOps.add(b.build()); - - result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); - assertTrue(result); - assertEquals(expectedOps, ops); - - // Now test calling save with identical reminders and no forcing - reminders.add(ReminderEntry.valueOf(5)); - reminders.add(ReminderEntry.valueOf(10)); - reminders.add(ReminderEntry.valueOf(15)); - - originalReminders.add(ReminderEntry.valueOf(5)); - originalReminders.add(ReminderEntry.valueOf(10)); - originalReminders.add(ReminderEntry.valueOf(15)); - - forceSave = false; - - ops.clear(); - - // Should fail to create any ops since nothing changed - result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); - assertFalse(result); - assertEquals(0, ops.size()); - - //Now test adding a single reminder - originalReminders.remove(2); - - addExpectedMinutes(expectedOps); - - result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); - assertTrue(result); - assertEquals(expectedOps, ops); - } - - @Smoke - @SmallTest - public void testSaveRemindersWithBackRef() { - ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); - ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); - long eventId = TEST_EVENT_ID; - ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>(); - ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>(); - boolean forceSave = true; - boolean result; - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - assertNotNull(mHelper); - - // First test forcing a delete with no reminders. - ContentProviderOperation.Builder b = - ContentProviderOperation.newDelete(Reminders.CONTENT_URI); - b.withSelection(Reminders.EVENT_ID + "=?", new String[1]); - b.withSelectionBackReference(0, TEST_EVENT_INDEX_ID); - expectedOps.add(b.build()); - - result = - mHelper.saveRemindersWithBackRef(ops, TEST_EVENT_INDEX_ID, reminders, - originalReminders, forceSave); - assertTrue(result); - assertEquals(expectedOps, ops); - - // Now test calling save with identical reminders and no forcing - reminders.add(ReminderEntry.valueOf(5)); - reminders.add(ReminderEntry.valueOf(10)); - reminders.add(ReminderEntry.valueOf(15)); - - originalReminders.add(ReminderEntry.valueOf(5)); - originalReminders.add(ReminderEntry.valueOf(10)); - originalReminders.add(ReminderEntry.valueOf(15)); - - forceSave = false; - - ops.clear(); - - result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders, - forceSave); - assertFalse(result); - assertEquals(0, ops.size()); - - //Now test adding a single reminder - originalReminders.remove(2); - - addExpectedMinutesWithBackRef(expectedOps); - - result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders, - forceSave); - assertTrue(result); - assertEquals(expectedOps, ops); - } - - @Smoke - @SmallTest - public void testIsFirstEventInSeries() { - mModel1 = new CalendarEventModel(); - mModel2 = new CalendarEventModel(); - - // It's considered the first event if the original start of the new model matches the - // start of the old model - mModel1.mOriginalStart = 100; - mModel1.mStart = 200; - mModel2.mOriginalStart = 100; - mModel2.mStart = 100; - - assertTrue(EditEventHelper.isFirstEventInSeries(mModel1, mModel2)); - - mModel1.mOriginalStart = 80; - assertFalse(EditEventHelper.isFirstEventInSeries(mModel1, mModel2)); - } - - @Smoke - @SmallTest - public void testAddRecurrenceRule() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - mValues = new ContentValues(); - mExpectedValues = new ContentValues(); - mModel1 = new CalendarEventModel(); - - mExpectedValues.put(Events.RRULE, "Weekly, Monday"); - mExpectedValues.put(Events.DURATION, "P60S"); - mExpectedValues.put(Events.DTEND, (Long) null); - - mModel1.mRrule = "Weekly, Monday"; - mModel1.mStart = 1; - mModel1.mEnd = 60001; - mModel1.mAllDay = false; - - mHelper.addRecurrenceRule(mValues, mModel1); - assertEquals(mExpectedValues, mValues); - - mExpectedValues.put(Events.DURATION, "P1D"); - - mModel1.mAllDay = true; - mValues.clear(); - - mHelper.addRecurrenceRule(mValues, mModel1); - assertEquals(mExpectedValues, mValues); - - } - - @Smoke - @SmallTest - public void testUpdateRecurrenceRule() { - int selection = EditEventHelper.DOES_NOT_REPEAT; - int weekStart = Calendar.SUNDAY; - mModel1 = new CalendarEventModel(); - mModel1.mTimezone = Time.TIMEZONE_UTC; - mModel1.mStart = 1272665741000L; // Fri, April 30th ~ 3:17PM - - mModel1.mRrule = "This should go away"; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertNull(mModel1.mRrule); - - mModel1.mRrule = "This shouldn't change"; - selection = EditEventHelper.REPEATS_CUSTOM; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("This shouldn't change", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_DAILY; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=DAILY;WKST=SU", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_EVERY_WEEKDAY; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_WEEKLY_ON_DAY; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=WEEKLY;WKST=SU;BYDAY=FR", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=MONTHLY;WKST=SU;BYMONTHDAY=30", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=MONTHLY;WKST=SU;BYDAY=-1FR", mModel1.mRrule); - - selection = EditEventHelper.REPEATS_YEARLY; - - EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); - assertEquals("FREQ=YEARLY;WKST=SU", mModel1.mRrule); - } - - @Smoke - @SmallTest - public void testSetModelFromCursor() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - MatrixCursor c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); - c.addRow(TEST_CURSOR_DATA); - - mModel1 = new CalendarEventModel(); - mModel2 = buildTestModel(); - - EditEventHelper.setModelFromCursor(mModel1, c); - assertEquals(mModel1, mModel2); - - TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "0"; - c.close(); - c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); - c.addRow(TEST_CURSOR_DATA); - - mModel2.mAllDay = false; - mModel2.mStart = TEST_START; // UTC time - - EditEventHelper.setModelFromCursor(mModel1, c); - assertEquals(mModel1, mModel2); - - TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_RRULE] = null; - c.close(); - c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); - c.addRow(TEST_CURSOR_DATA); - - mModel2.mRrule = null; - mModel2.mEnd = TEST_END; - mModel2.mDuration = null; - - EditEventHelper.setModelFromCursor(mModel1, c); - assertEquals(mModel1, mModel2); - - TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "1"; - c.close(); - c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); - c.addRow(TEST_CURSOR_DATA); - - mModel2.mAllDay = true; - mModel2.mStart = TEST_START; // Monday, May 3rd, midnight - mModel2.mEnd = TEST_END; // Tuesday, May 4th, midnight - - EditEventHelper.setModelFromCursor(mModel1, c); - assertEquals(mModel1, mModel2); - } - - @Smoke - @SmallTest - public void testGetContentValuesFromModel() { - mActivity = buildTestContext(); - mHelper = new EditEventHelper(mActivity, null); - mExpectedValues = buildTestValues(); - mModel1 = buildTestModel(); - - ContentValues values = mHelper.getContentValuesFromModel(mModel1); - assertEquals(mExpectedValues, values); - - mModel1.mRrule = null; - mModel1.mEnd = TEST_END; - - mExpectedValues.put(Events.RRULE, (String) null); - mExpectedValues.put(Events.DURATION, (String) null); - mExpectedValues.put(Events.DTEND, TEST_END); // UTC time - - values = mHelper.getContentValuesFromModel(mModel1); - assertEquals(mExpectedValues, values); - - mModel1.mAllDay = false; - - mExpectedValues.put(Events.ALL_DAY, 0); - mExpectedValues.put(Events.DTSTART, TEST_START); - mExpectedValues.put(Events.DTEND, TEST_END); - // not an allday event so timezone isn't modified - mExpectedValues.put(Events.EVENT_TIMEZONE, "UTC"); - - values = mHelper.getContentValuesFromModel(mModel1); - assertEquals(mExpectedValues, values); - } - - @Smoke - @SmallTest - public void testExtractDomain() { - String domain = EditEventHelper.extractDomain("test.email@gmail.com"); - assertEquals("gmail.com", domain); - - domain = EditEventHelper.extractDomain("bademail.no#$%at symbol"); - assertNull(domain); - } - - private void addExpectedMinutes(ArrayList<ContentProviderOperation> expectedOps) { - ContentProviderOperation.Builder b; - mValues = new ContentValues(); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 5); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - expectedOps.add(b.build()); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 10); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - expectedOps.add(b.build()); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 15); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - expectedOps.add(b.build()); - } - - private void addExpectedMinutesWithBackRef(ArrayList<ContentProviderOperation> expectedOps) { - ContentProviderOperation.Builder b; - mValues = new ContentValues(); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 5); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); - expectedOps.add(b.build()); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 10); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); - expectedOps.add(b.build()); - - mValues.clear(); - mValues.put(Reminders.MINUTES, 15); - mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); - b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); - b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); - expectedOps.add(b.build()); - } - - private static void assertEquals(ArrayList<ContentProviderOperation> expected, - ArrayList<ContentProviderOperation> actual) { - if (expected == null) { - assertNull(actual); - } - int size = expected.size(); - - assertEquals(size, actual.size()); - - for (int i = 0; i < size; i++) { - assertTrue("At index " + i + ", expected:\n" + String.valueOf(expected.get(i)) + - "\nActual:\n" + String.valueOf(actual.get(i)), - cpoEquals(expected.get(i), actual.get(i))); - } - - } - - private static boolean cpoEquals(ContentProviderOperation cpo1, ContentProviderOperation cpo2) { - if (cpo1 == null && cpo2 != null) { - return false; - } - if (cpo1 == cpo2) { - return true; - } - if (cpo2 == null) { - return false; - } - - // It turns out we can't trust the toString() of the ContentProviderOperations to be - // consistent, so we have to do the comparison manually. - // - // Start by splitting by commas, so that we can compare each key-value pair individually. - String[] operations1 = cpo1.toString().split(","); - String[] operations2 = cpo2.toString().split(","); - // The two numbers of operations must be equal. - if (operations1.length != operations2.length) { - return false; - } - // Iterate through the key-value pairs and separate out the key and value. - // The value may be either a single string, or a series of further key-value pairs - // that are separated by " ", with a "=" between the key and value. - for (int i = 0; i < operations1.length; i++) { - String operation1 = operations1[i]; - String operation2 = operations2[i]; - // Limit the array to length 2 in case a ":" appears in the value. - String[] keyValue1 = operation1.split(":", 2); - String[] keyValue2 = operation2.split(":", 2); - // If the key doesn't match, return false. - if (!keyValue1[0].equals(keyValue2[0])) { - return false; - } - // First just check if the value matches up. If so, we're good to go. - if (keyValue1[1].equals(keyValue2[1])) { - continue; - } - // If not, we need to try splitting the value by " " and sorting those keyvalue pairs. - // Note that these are trimmed first to ensure we're not thrown off by extra whitespace. - String[] valueKeyValuePairs1 = keyValue1[1].trim().split(" "); - String[] valueKeyValuePairs2 = keyValue2[1].trim().split(" "); - // Sort the value's keyvalue pairs alphabetically, and now compare them to each other. - Arrays.sort(valueKeyValuePairs1); - Arrays.sort(valueKeyValuePairs2); - for (int j = 0; j < valueKeyValuePairs1.length; j++) { - if (!valueKeyValuePairs1[j].equals(valueKeyValuePairs2[j])) { - return false; - } - } - } - - // If we make it all the way through without finding anything different, return true. - return true; - } - - // Generates a default model for testing. Should match up with - // generateTestValues - private CalendarEventModel buildTestModel() { - CalendarEventModel model = new CalendarEventModel(); - model.mId = TEST_EVENT_ID; - model.mTitle = "The_Question"; - model.mDescription = "Evaluating_Life_the_Universe_and_Everything"; - model.mLocation = "Earth_Mk2"; - model.mAllDay = true; - model.mHasAlarm = false; - model.mCalendarId = 2; - model.mStart = TEST_START; // Monday, May 3rd, local Time - model.mDuration = "P3652421990D"; - // The model uses the local timezone for allday - model.mTimezone = "UTC"; - model.mRrule = "FREQ=DAILY;WKST=SU"; - model.mSyncId = "unique_per_calendar_stuff"; - model.mAvailability = 0; - model.mAccessLevel = 2; // This is one less than the values written if >0 - model.mOwnerAccount = "steve@gmail.com"; - model.mHasAttendeeData = true; - model.mOrganizer = "organizer@gmail.com"; - model.mIsOrganizer = false; - model.mGuestsCanModify = false; - model.mEventStatus = Events.STATUS_CONFIRMED; - int displayColor = Utils.getDisplayColorFromColor(-2350809); - model.setEventColor(displayColor); - model.mCalendarAccountName = "steve.owner@gmail.com"; - model.mCalendarAccountType = "gmail.com"; - EventColorCache cache = new EventColorCache(); - cache.insertColor("steve.owner@gmail.com", "gmail.com", displayColor, 12); - model.mEventColorCache = cache; - model.mModelUpdatedWithEventCursor = true; - return model; - } - - // Generates a default set of values for testing. Should match up with - // generateTestModel - private ContentValues buildTestValues() { - ContentValues values = new ContentValues(); - - values.put(Events.CALENDAR_ID, 2L); - values.put(Events.EVENT_TIMEZONE, "UTC"); // Allday events are converted - // to UTC for the db - values.put(Events.TITLE, "The_Question"); - values.put(Events.ALL_DAY, 1); - values.put(Events.DTSTART, TEST_START); // Monday, May 3rd, midnight UTC time - values.put(Events.HAS_ATTENDEE_DATA, 1); - - values.put(Events.RRULE, "FREQ=DAILY;WKST=SU"); - values.put(Events.DURATION, "P3652421990D"); - values.put(Events.DTEND, (Long) null); - values.put(Events.DESCRIPTION, "Evaluating_Life_the_Universe_and_Everything"); - values.put(Events.EVENT_LOCATION, "Earth_Mk2"); - values.put(Events.AVAILABILITY, 0); - values.put(Events.STATUS, Events.STATUS_CONFIRMED); - values.put(Events.ACCESS_LEVEL, 3); // This is one more than the model if - // >0 - values.put(Events.EVENT_COLOR_KEY, 12); - - return values; - } - - private ContentValues buildNonRecurringTestValues() { - ContentValues values = buildTestValues(); - values.put(Events.DURATION, (String)null); - values.put(Events.DTEND, TEST_END); - values.put(Events.RRULE, (String)null); - return values; - } - - // This gets called by EditEventHelper to read or write the data - class TestProvider extends ContentProvider { - int index = 0; - - public TestProvider() { - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String orderBy) { - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; - } - - @Override - public String getType(Uri uri) { - return null; - } - - @Override - public boolean onCreate() { - return false; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - // TODO Auto-generated method stub - return 0; - } - } - -} diff --git a/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java b/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java index 7a569f0f..4b0a0918 100644 --- a/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java +++ b/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java @@ -28,6 +28,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; import android.text.format.DateUtils; import android.text.format.Time; +import android.util.Log; import android.view.View; import java.util.TimeZone; @@ -163,13 +164,14 @@ public class CalendarAppWidgetServiceTest extends AndroidTestCase { DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL); eventInfo.where = location + i; eventInfo.title = title + i; - expected.mEventInfos.add(eventInfo); cursor.addRow(getRow(1, start, end, title + i, location + i, 0)); // Test CalendarAppWidgetModel actual = CalendarAppWidgetService.CalendarFactory.buildAppWidgetModel( context, cursor, Time.getCurrentTimezone()); + Log.e("Test", " expected: " + expected.toString() + + " actual: " + actual.toString()); assertEquals(expected.toString(), actual.toString()); } |