summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 20:38:16 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 20:38:16 +0000
commit3335569e8c6520ace43ce936318adb3751e5cee8 (patch)
treeb4a1af0e9e002b5b5c88aa0283beea9eac549e21
parent26da6391e5ab5a97834d0b3522d107a158bd5b1e (diff)
parentc95dcf939df9e7a3b87656279f638ecf13787fb9 (diff)
downloadCalendar-android13-mainline-media-release.tar.gz
Change-Id: I0d058de7ba2a5bb36326efd6f5d588b18b14b52c
-rw-r--r--Android.bp56
-rw-r--r--Android.mk53
-rw-r--r--AndroidManifest.xml3
-rw-r--r--src/com/android/calendar/AllInOneActivity.java1062
-rw-r--r--src/com/android/calendar/AllInOneActivity.kt1065
-rw-r--r--src/com/android/calendar/AsyncQueryServiceHelper.java70
-rw-r--r--src/com/android/calendar/AsyncQueryServiceHelper.kt61
-rw-r--r--src/com/android/calendar/CalendarApplication.kt (renamed from src/com/android/calendar/CalendarApplication.java)18
-rw-r--r--src/com/android/calendar/CalendarBackupAgent.java41
-rw-r--r--src/com/android/calendar/CalendarBackupAgent.kt40
-rw-r--r--src/com/android/calendar/CalendarController.java713
-rw-r--r--src/com/android/calendar/CalendarController.kt743
-rw-r--r--src/com/android/calendar/CalendarData.kt (renamed from src/com/android/calendar/CalendarData.java)25
-rw-r--r--src/com/android/calendar/CalendarUtils.java356
-rw-r--r--src/com/android/calendar/CalendarUtils.kt354
-rw-r--r--src/com/android/calendar/CalendarViewAdapter.java409
-rw-r--r--src/com/android/calendar/CalendarViewAdapter.kt370
-rw-r--r--src/com/android/calendar/DayFragment.java256
-rw-r--r--src/com/android/calendar/DayFragment.kt233
-rw-r--r--src/com/android/calendar/DayOfMonthDrawable.java77
-rw-r--r--src/com/android/calendar/DayOfMonthDrawable.kt74
-rw-r--r--src/com/android/calendar/DayView.java4008
-rw-r--r--src/com/android/calendar/DayView.kt3990
-rw-r--r--src/com/android/calendar/Event.java642
-rw-r--r--src/com/android/calendar/Event.kt640
-rw-r--r--src/com/android/calendar/EventGeometry.java170
-rw-r--r--src/com/android/calendar/EventGeometry.kt154
-rw-r--r--src/com/android/calendar/EventInfoActivity.java190
-rw-r--r--src/com/android/calendar/EventInfoActivity.kt196
-rw-r--r--src/com/android/calendar/EventInfoFragment.java877
-rw-r--r--src/com/android/calendar/EventInfoFragment.kt787
-rw-r--r--src/com/android/calendar/EventLoader.java286
-rw-r--r--src/com/android/calendar/EventLoader.kt283
-rw-r--r--src/com/android/calendar/GeneralPreferences.java400
-rw-r--r--src/com/android/calendar/GeneralPreferences.kt378
-rw-r--r--src/com/android/calendar/GoogleCalendarUriIntentFilter.kt (renamed from src/com/android/calendar/GoogleCalendarUriIntentFilter.java)32
-rw-r--r--src/com/android/calendar/MultiStateButton.java192
-rw-r--r--src/com/android/calendar/MultiStateButton.kt166
-rw-r--r--src/com/android/calendar/OtherPreferences.java210
-rw-r--r--src/com/android/calendar/OtherPreferences.kt184
-rw-r--r--src/com/android/calendar/StickyHeaderListView.java395
-rw-r--r--src/com/android/calendar/StickyHeaderListView.kt386
-rw-r--r--src/com/android/calendar/UpgradeReceiver.kt (renamed from src/com/android/calendar/UpgradeReceiver.java)19
-rw-r--r--src/com/android/calendar/Utils.java1499
-rw-r--r--src/com/android/calendar/Utils.kt1577
-rw-r--r--src/com/android/calendar/alerts/AlarmManagerInterface.kt (renamed from src/com/android/calendar/alerts/AlarmManagerInterface.java)13
-rw-r--r--src/com/android/calendar/alerts/AlarmScheduler.java322
-rw-r--r--src/com/android/calendar/alerts/AlarmScheduler.kt352
-rw-r--r--src/com/android/calendar/alerts/AlertReceiver.java123
-rw-r--r--src/com/android/calendar/alerts/AlertReceiver.kt118
-rw-r--r--src/com/android/calendar/alerts/AlertService.java202
-rw-r--r--src/com/android/calendar/alerts/AlertService.kt167
-rw-r--r--src/com/android/calendar/alerts/AlertUtils.java108
-rw-r--r--src/com/android/calendar/alerts/AlertUtils.kt92
-rw-r--r--src/com/android/calendar/alerts/DismissAlarmsService.java136
-rw-r--r--src/com/android/calendar/alerts/DismissAlarmsService.kt127
-rw-r--r--src/com/android/calendar/alerts/GlobalDismissManager.java84
-rw-r--r--src/com/android/calendar/alerts/GlobalDismissManager.kt58
-rw-r--r--src/com/android/calendar/alerts/InitAlarmsService.java62
-rw-r--r--src/com/android/calendar/alerts/InitAlarmsService.kt62
-rw-r--r--src/com/android/calendar/alerts/NotificationMgr.kt (renamed from src/com/android/calendar/alerts/NotificationMgr.java)25
-rw-r--r--src/com/android/calendar/alerts/QuickResponseActivity.java108
-rw-r--r--src/com/android/calendar/alerts/QuickResponseActivity.kt96
-rw-r--r--src/com/android/calendar/month/MonthByWeekAdapter.java406
-rw-r--r--src/com/android/calendar/month/MonthByWeekAdapter.kt406
-rw-r--r--src/com/android/calendar/month/MonthByWeekFragment.java494
-rw-r--r--src/com/android/calendar/month/MonthByWeekFragment.kt497
-rw-r--r--src/com/android/calendar/month/MonthListView.java51
-rw-r--r--src/com/android/calendar/month/MonthListView.kt43
-rw-r--r--src/com/android/calendar/month/MonthWeekEventsView.java1110
-rw-r--r--src/com/android/calendar/month/MonthWeekEventsView.kt1061
-rw-r--r--src/com/android/calendar/month/SimpleDayPickerFragment.java612
-rw-r--r--src/com/android/calendar/month/SimpleDayPickerFragment.kt616
-rw-r--r--src/com/android/calendar/month/SimpleWeekView.java551
-rw-r--r--src/com/android/calendar/month/SimpleWeekView.kt563
-rw-r--r--src/com/android/calendar/month/SimpleWeeksAdapter.java302
-rw-r--r--src/com/android/calendar/month/SimpleWeeksAdapter.kt314
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetModel.java430
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetModel.kt409
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetProvider.java230
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetProvider.kt251
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetService.java626
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetService.kt665
83 files changed, 17697 insertions, 17935 deletions
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 00000000..3c6477b3
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,56 @@
+package {
+
+ default_applicable_licenses: ["packages_apps_Calendar_license"],
+}
+
+license {
+
+ name: "packages_apps_Calendar_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
+// Include res dir from chips
+
+android_app {
+ name: "Calendar",
+
+ jacoco: {
+ include_filter: ["com.android.calendar.**"],
+ },
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ // bundled
+ //LOCAL_STATIC_JAVA_LIBRARIES +=
+ //# android-common
+ //# libchips
+ //# calendar-common
+
+ // unbundled
+ static_libs: [
+ "android-common",
+ "libchips",
+ "colorpicker",
+ "android-opt-timezonepicker",
+ "androidx.legacy_legacy-support-v4",
+ "calendar-common",
+ ],
+
+ sdk_version: "current",
+ target_sdk_version: "30",
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
+
+ product_specific: true,
+
+ aaptflags: ["--auto-add-overlay"],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index dce26a46..00000000
--- a/Android.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include res dir from chips
-chips_dir := ../../../frameworks/opt/chips/res
-color_picker_dir := ../../../frameworks/opt/colorpicker/res
-timezonepicker_dir := ../../../frameworks/opt/timezonepicker/res
-res_dirs := $(chips_dir) $(color_picker_dir) $(timezonepicker_dir) res
-src_dirs := src
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.calendar.*
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under,$(src_dirs))
-
-# bundled
-#LOCAL_STATIC_JAVA_LIBRARIES += \
-# android-common \
-# libchips \
-# calendar-common
-
-# unbundled
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-common \
- libchips \
- colorpicker \
- android-opt-timezonepicker \
- androidx.legacy_legacy-support-v4 \
- calendar-common
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := Calendar
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-LOCAL_PRODUCT_MODULE := true
-
-LOCAL_AAPT_FLAGS := --auto-add-overlay
-LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
-LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker
-LOCAL_AAPT_FLAGS += --extra-packages com.android.timezonepicker
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c9c5a04b..fed61b0c 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="19" android:targetSdkVersion="29"></uses-sdk>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"></uses-sdk>
<application android:name="CalendarApplication"
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
deleted file mode 100644
index cec6a40f..00000000
--- a/src/com/android/calendar/AllInOneActivity.java
+++ /dev/null
@@ -1,1062 +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.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.animation.Animator;
-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;
-import android.content.AsyncQueryHandler;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-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.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
-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.month.MonthByWeekFragment;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-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 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";
- private static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
- private static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
- private static final String BUNDLE_KEY_RESTORE_VIEW = "key_restore_view";
- private static final String BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts";
- private static final int HANDLER_KEY = 0;
-
- // Indices of buttons for the drop down menu (tabs replacement)
- // Must match the strings in the array buttons_list in arrays.xml and the
- // OnNavigationListener
- private static final int BUTTON_DAY_INDEX = 0;
- private static final int BUTTON_WEEK_INDEX = 1;
- private static final int BUTTON_MONTH_INDEX = 2;
- private static final int BUTTON_AGENDA_INDEX = 3;
-
- private CalendarController mController;
- private static boolean mIsMultipane;
- private static boolean mIsTabletConfig;
- private boolean mOnSaveInstanceStateCalled = false;
- private boolean mBackToPreviousView = false;
- private ContentResolver mContentResolver;
- private int mPreviousView;
- private int mCurrentView;
- private boolean mPaused = true;
- private boolean mUpdateOnResume = false;
- private boolean mHideControls = false;
- private boolean mShowSideViews = true;
- private boolean mShowWeekNum = false;
- private TextView mHomeTime;
- private TextView mDateRange;
- private TextView mWeekTextView;
- private View mMiniMonth;
- private View mCalendarsList;
- private View mMiniMonthContainer;
- private View mSecondaryPane;
- private String mTimeZone;
- private boolean mShowCalendarControls;
- private boolean mShowEventInfoFullScreen;
- private int mWeekNum;
- private int mCalendarControlsAnimationTime;
- private int mControlsAnimateWidth;
- private int mControlsAnimateHeight;
-
- private long mViewEventId = -1;
- private long mIntentEventStartMillis = -1;
- private long mIntentEventEndMillis = -1;
- private int mIntentAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE;
- private boolean mIntentAllDay = false;
-
- // Action bar and Navigation bar (left side of Action bar)
- private ActionBar mActionBar;
- private ActionBar.Tab mDayTab;
- private ActionBar.Tab mWeekTab;
- private ActionBar.Tab mMonthTab;
- private MenuItem mControlsMenu;
- private Menu mOptionsMenu;
- private CalendarViewAdapter mActionBarMenuSpinnerAdapter;
- private QueryHandler mHandler;
- private boolean mCheckForAccounts = true;
-
- private String mHideString;
- private String mShowString;
-
- DayOfMonthDrawable mDayOfMonthIcon;
-
- int mOrientation;
-
- // Params for animating the controls on the right
- private LayoutParams mControlsParams;
- private LinearLayout.LayoutParams mVerticalControlsParams;
-
- private final AnimatorListener mSlideAnimationDoneListener = new AnimatorListener() {
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(android.animation.Animator animation) {
- int visibility = mShowSideViews ? View.VISIBLE : View.GONE;
- mMiniMonth.setVisibility(visibility);
- mCalendarsList.setVisibility(visibility);
- mMiniMonthContainer.setVisibility(visibility);
- }
-
- @Override
- public void onAnimationRepeat(android.animation.Animator animation) {
- }
-
- @Override
- public void onAnimationStart(android.animation.Animator animation) {
- }
- };
-
- private class QueryHandler extends AsyncQueryHandler {
- public QueryHandler(ContentResolver cr) {
- super(cr);
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- mCheckForAccounts = false;
- try {
- // If the query didn't return a cursor for some reason return
- if (cursor == null || cursor.getCount() > 0 || isFinishing()) {
- return;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- Bundle options = new Bundle();
- options.putCharSequence("introMessage",
- getResources().getString(R.string.create_an_account_desc));
- options.putBoolean("allowSkip", true);
-
- AccountManager am = AccountManager.get(AllInOneActivity.this);
- am.addAccount("com.google", CalendarContract.AUTHORITY, null, options,
- AllInOneActivity.this,
- new AccountManagerCallback<Bundle>() {
- @Override
- public void run(AccountManagerFuture<Bundle> future) {
- }
- }, null);
- }
- }
-
- private final Runnable mHomeTimeUpdater = new Runnable() {
- @Override
- public void run() {
- mTimeZone = Utils.getTimeZone(AllInOneActivity.this, mHomeTimeUpdater);
- updateSecondaryTitleFields(-1);
- AllInOneActivity.this.invalidateOptionsMenu();
- Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
- }
- };
-
- // runs every midnight/time changes and refreshes the today icon
- private final Runnable mTimeChangesUpdater = new Runnable() {
- @Override
- public void run() {
- mTimeZone = Utils.getTimeZone(AllInOneActivity.this, mHomeTimeUpdater);
- AllInOneActivity.this.invalidateOptionsMenu();
- Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
- }
- };
-
-
- // 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) {
- eventsChanged();
- }
- };
-
- @Override
- protected void onNewIntent(Intent intent) {
- String action = intent.getAction();
- if (DEBUG)
- Log.d(TAG, "New intent received " + intent.toString());
- // Don't change the date if we're just returning to the app's home
- if (Intent.ACTION_VIEW.equals(action)
- && !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false)) {
- long millis = parseViewAction(intent);
- if (millis == -1) {
- millis = Utils.timeFromIntentInMillis(intent);
- }
- if (millis != -1 && mViewEventId == -1 && mController != null) {
- Time time = new Time(mTimeZone);
- time.set(millis);
- time.normalize(true);
- mController.sendEvent(this, EventType.GO_TO, time, time, -1, ViewType.CURRENT);
- }
- }
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) {
- mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS);
- }
- // Launch add google account if this is first time and there are no
- // accounts yet
- if (mCheckForAccounts) {
-
- mHandler = new QueryHandler(this.getContentResolver());
- mHandler.startQuery(0, null, Calendars.CONTENT_URI, new String[] {
- Calendars._ID
- }, null, null /* selection args */, null /* sort order */);
- }
-
- // This needs to be created before setContentView
- mController = CalendarController.getInstance(this);
-
-
- // Get time from intent or icicle
- long timeMillis = -1;
- int viewType = -1;
- final Intent intent = getIntent();
- if (icicle != null) {
- timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME);
- viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1);
- } else {
- String action = intent.getAction();
- if (Intent.ACTION_VIEW.equals(action)) {
- // Open EventInfo later
- timeMillis = parseViewAction(intent);
- }
-
- if (timeMillis == -1) {
- timeMillis = Utils.timeFromIntentInMillis(intent);
- }
- }
-
- if (viewType == -1 || viewType > ViewType.MAX_VALUE) {
- viewType = Utils.getViewTypeFromIntentAndSharedPref(this);
- }
- mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater);
- Time t = new Time(mTimeZone);
- t.set(timeMillis);
-
- if (DEBUG) {
- if (icicle != null && intent != null) {
- Log.d(TAG, "both, icicle:" + icicle.toString() + " intent:" + intent.toString());
- } else {
- Log.d(TAG, "not both, icicle:" + icicle + " intent:" + intent);
- }
- }
-
- Resources res = getResources();
- mHideString = res.getString(R.string.hide_controls);
- mShowString = res.getString(R.string.show_controls);
- mOrientation = res.getConfiguration().orientation;
- if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- mControlsAnimateWidth = (int)res.getDimension(R.dimen.calendar_controls_width);
- if (mControlsParams == null) {
- mControlsParams = new LayoutParams(mControlsAnimateWidth, 0);
- }
- mControlsParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- } else {
- // Make sure width is in between allowed min and max width values
- mControlsAnimateWidth = Math.max(res.getDisplayMetrics().widthPixels * 45 / 100,
- (int)res.getDimension(R.dimen.min_portrait_calendar_controls_width));
- mControlsAnimateWidth = Math.min(mControlsAnimateWidth,
- (int)res.getDimension(R.dimen.max_portrait_calendar_controls_width));
- }
-
- mControlsAnimateHeight = (int)res.getDimension(R.dimen.calendar_controls_height);
-
- mHideControls = true;
- mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config);
- mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config);
- mShowCalendarControls =
- Utils.getConfigBool(this, R.bool.show_calendar_controls);
- mShowEventInfoFullScreen =
- Utils.getConfigBool(this, R.bool.show_event_info_full_screen);
- mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time);
- Utils.setAllowWeekForDetailView(mIsMultipane);
-
- // setContentView must be called before configureActionBar
- setContentView(R.layout.all_in_one);
-
- if (mIsTabletConfig) {
- mDateRange = (TextView) findViewById(R.id.date_bar);
- mWeekTextView = (TextView) findViewById(R.id.week_num);
- } else {
- mDateRange = (TextView) getLayoutInflater().inflate(R.layout.date_range_title, null);
- }
-
- // configureActionBar auto-selects the first tab you add, so we need to
- // call it before we set up our own fragments to make sure it doesn't
- // overwrite us
- configureActionBar(viewType);
-
- mHomeTime = (TextView) findViewById(R.id.home_time);
- mMiniMonth = findViewById(R.id.mini_month);
- if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) {
- mMiniMonth.setLayoutParams(new RelativeLayout.LayoutParams(mControlsAnimateWidth,
- mControlsAnimateHeight));
- }
- mCalendarsList = findViewById(R.id.calendar_list);
- mMiniMonthContainer = findViewById(R.id.mini_month_container);
- mSecondaryPane = findViewById(R.id.secondary_pane);
-
- // Must register as the first activity 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.registerFirstEventHandler(HANDLER_KEY, this);
-
- initFragments(timeMillis, viewType, icicle);
-
- // Listen for changes that would require this to be refreshed
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
- prefs.registerOnSharedPreferenceChangeListener(this);
-
- mContentResolver = getContentResolver();
- }
-
- private long parseViewAction(final Intent intent) {
- long timeMillis = -1;
- Uri data = intent.getData();
- if (data != null && data.isHierarchical()) {
- List<String> path = data.getPathSegments();
- if (path.size() == 2 && path.get(0).equals("events")) {
- try {
- mViewEventId = Long.valueOf(data.getLastPathSegment());
- if (mViewEventId != -1) {
- mIntentEventStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0);
- mIntentEventEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0);
- mIntentAttendeeResponse = intent.getIntExtra(
- ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
- mIntentAllDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false);
- timeMillis = mIntentEventStartMillis;
- }
- } catch (NumberFormatException e) {
- // Ignore if mViewEventId can't be parsed
- }
- }
- }
- return timeMillis;
- }
-
- private void configureActionBar(int viewType) {
- createButtonsSpinner(viewType, mIsTabletConfig);
- if (mIsMultipane) {
- mActionBar.setDisplayOptions(
- ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
- } else {
- mActionBar.setDisplayOptions(0);
- }
- }
-
- private void createButtonsSpinner(int viewType, boolean tabletConfig) {
- // If tablet configuration , show spinner with no dates
- mActionBarMenuSpinnerAdapter = new CalendarViewAdapter (this, viewType, !tabletConfig);
- mActionBar = getActionBar();
- mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- mActionBar.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this);
- switch (viewType) {
- case ViewType.AGENDA:
- break;
- case ViewType.DAY:
- mActionBar.setSelectedNavigationItem(BUTTON_DAY_INDEX);
- break;
- case ViewType.WEEK:
- mActionBar.setSelectedNavigationItem(BUTTON_WEEK_INDEX);
- break;
- case ViewType.MONTH:
- mActionBar.setSelectedNavigationItem(BUTTON_MONTH_INDEX);
- break;
- default:
- mActionBar.setSelectedNavigationItem(BUTTON_DAY_INDEX);
- break;
- }
- }
- // Clear buttons used in the agenda view
- private void clearOptionsMenu() {
- if (mOptionsMenu == null) {
- return;
- }
- MenuItem cancelItem = mOptionsMenu.findItem(R.id.action_cancel);
- if (cancelItem != null) {
- cancelItem.setVisible(false);
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- // Check if the upgrade code has ever been run. If not, force a sync just this one time.
- Utils.trySyncAndDisableUpgradeReceiver(this);
-
- // Must register as the first activity 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.registerFirstEventHandler(HANDLER_KEY, this);
-
- mOnSaveInstanceStateCalled = false;
- mContentResolver.registerContentObserver(CalendarContract.Events.CONTENT_URI,
- true, mObserver);
- if (mUpdateOnResume) {
- initFragments(mController.getTime(), mController.getViewType(), null);
- mUpdateOnResume = false;
- }
- Time t = new Time(mTimeZone);
- t.set(mController.getTime());
- mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT,
- mController.getDateFlags(), null, null);
- // Make sure the drop-down menu will get its date updated at midnight
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBarMenuSpinnerAdapter.refresh(this);
- }
-
- if (mControlsMenu != null) {
- mControlsMenu.setTitle(mHideControls ? mShowString : mHideString);
- }
- mPaused = false;
-
- if (mViewEventId != -1 && mIntentEventStartMillis != -1 && mIntentEventEndMillis != -1) {
- long currentMillis = System.currentTimeMillis();
- long selectedTime = -1;
- if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) {
- selectedTime = currentMillis;
- }
- mController.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, mViewEventId,
- mIntentEventStartMillis, mIntentEventEndMillis, -1, -1,
- EventInfo.buildViewExtraLong(mIntentAttendeeResponse,mIntentAllDay),
- selectedTime);
- mViewEventId = -1;
- mIntentEventStartMillis = -1;
- mIntentEventEndMillis = -1;
- mIntentAllDay = false;
- }
- Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
- // Make sure the today icon is up to date
- invalidateOptionsMenu();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- mController.deregisterEventHandler(HANDLER_KEY);
- mPaused = true;
- mHomeTime.removeCallbacks(mHomeTimeUpdater);
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBarMenuSpinnerAdapter.onPause();
- }
- mContentResolver.unregisterContentObserver(mObserver);
- if (isFinishing()) {
- // Stop listening for changes that would require this to be refreshed
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
- prefs.unregisterOnSharedPreferenceChangeListener(this);
- }
- // FRAG_TODO save highlighted days of the week;
- if (mController.getViewType() != ViewType.EDIT) {
- Utils.setDefaultView(this, mController.getViewType());
- }
- Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater);
- }
-
- @Override
- protected void onUserLeaveHint() {
- mController.sendEvent(this, EventType.USER_HOME, null, null, -1, ViewType.CURRENT);
- super.onUserLeaveHint();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- mOnSaveInstanceStateCalled = true;
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
- prefs.unregisterOnSharedPreferenceChangeListener(this);
-
- mController.deregisterAllEventHandlers();
-
- CalendarController.removeInstance(this);
- }
-
- private void initFragments(long timeMillis, int viewType, Bundle icicle) {
- if (DEBUG) {
- Log.d(TAG, "Initializing to " + timeMillis + " for view " + viewType);
- }
- FragmentTransaction ft = getFragmentManager().beginTransaction();
-
- if (mShowCalendarControls) {
- Fragment miniMonthFrag = new MonthByWeekFragment(timeMillis, true);
- ft.replace(R.id.mini_month, miniMonthFrag);
- mController.registerEventHandler(R.id.mini_month, (EventHandler) miniMonthFrag);
- }
- if (!mShowCalendarControls || viewType == ViewType.EDIT) {
- mMiniMonth.setVisibility(View.GONE);
- mCalendarsList.setVisibility(View.GONE);
- }
-
- EventInfo info = null;
- if (viewType == ViewType.EDIT) {
- mPreviousView = GeneralPreferences.getSharedPreferences(this).getInt(
- GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
-
- long eventId = -1;
- Intent intent = getIntent();
- Uri data = intent.getData();
- if (data != null) {
- try {
- eventId = Long.parseLong(data.getLastPathSegment());
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Log.d(TAG, "Create new event");
- }
- }
- } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) {
- eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID);
- }
-
- long begin = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1);
- long end = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1);
- info = new EventInfo();
- if (end != -1) {
- info.endTime = new Time();
- info.endTime.set(end);
- }
- if (begin != -1) {
- info.startTime = new Time();
- info.startTime.set(begin);
- }
- info.id = eventId;
- // We set the viewtype so if the user presses back when they are
- // done editing the controller knows we were in the Edit Event
- // screen. Likewise for eventId
- mController.setViewType(viewType);
- mController.setEventId(eventId);
- } else {
- mPreviousView = viewType;
- }
-
- setMainPane(ft, R.id.main_pane, viewType, timeMillis, true);
- ft.commit(); // this needs to be after setMainPane()
-
- Time t = new Time(mTimeZone);
- t.set(timeMillis);
- if (viewType != ViewType.EDIT) {
- mController.sendEvent(this, EventType.GO_TO, t, null, -1, viewType);
- }
- }
-
- @Override
- public void onBackPressed() {
- if (mCurrentView == ViewType.EDIT || mBackToPreviousView) {
- mController.sendEvent(this, EventType.GO_TO, null, null, -1, mPreviousView);
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- mOptionsMenu = menu;
- getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu);
-
- // Hide the "show/hide controls" button if this is a phone
- // or the view type is "Month".
-
- mControlsMenu = menu.findItem(R.id.action_hide_controls);
- if (!mShowCalendarControls) {
- if (mControlsMenu != null) {
- mControlsMenu.setVisible(false);
- mControlsMenu.setEnabled(false);
- }
- } else if (mControlsMenu != null && mController != null
- && (mController.getViewType() == ViewType.MONTH)) {
- mControlsMenu.setVisible(false);
- mControlsMenu.setEnabled(false);
- } else if (mControlsMenu != null){
- mControlsMenu.setTitle(mHideControls ? mShowString : mHideString);
- }
-
- MenuItem menuItem = menu.findItem(R.id.action_today);
- if (Utils.isJellybeanOrLater()) {
- // replace the default top layer drawable of the today icon with a
- // custom drawable that shows the day of the month of today
- LayerDrawable icon = (LayerDrawable) menuItem.getIcon();
- Utils.setTodayIcon(icon, this, mTimeZone);
- } else {
- menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- Time t = null;
- int viewType = ViewType.CURRENT;
- long extras = CalendarController.EXTRA_GOTO_TIME;
- final int itemId = item.getItemId();
- 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_hide_controls) {
- mHideControls = !mHideControls;
- item.setTitle(mHideControls ? mShowString : mHideString);
- if (!mHideControls) {
- mMiniMonth.setVisibility(View.VISIBLE);
- mCalendarsList.setVisibility(View.VISIBLE);
- mMiniMonthContainer.setVisibility(View.VISIBLE);
- }
- final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this, "controlsOffset",
- mHideControls ? 0 : mControlsAnimateWidth,
- mHideControls ? mControlsAnimateWidth : 0);
- slideAnimation.setDuration(mCalendarControlsAnimationTime);
- ObjectAnimator.setFrameDelay(0);
- slideAnimation.start();
- return true;
- } else {
- Log.d(TAG, "Unsupported itemId: " + itemId);
- return true;
- }
- mController.sendEvent(this, EventType.GO_TO, t, null, t, -1, viewType, extras, null, null);
- return true;
- }
-
- /**
- * Sets the offset of the controls on the right for animating them off/on
- * screen. ProGuard strips this if it's not in proguard.flags
- *
- * @param controlsOffset The current offset in pixels
- */
- public void setControlsOffset(int controlsOffset) {
- if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- mMiniMonth.setTranslationX(controlsOffset);
- mCalendarsList.setTranslationX(controlsOffset);
- mControlsParams.width = Math.max(0, mControlsAnimateWidth - controlsOffset);
- mMiniMonthContainer.setLayoutParams(mControlsParams);
- } else {
- mMiniMonth.setTranslationY(controlsOffset);
- mCalendarsList.setTranslationY(controlsOffset);
- if (mVerticalControlsParams == null) {
- mVerticalControlsParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight);
- }
- mVerticalControlsParams.height = Math.max(0, mControlsAnimateHeight - controlsOffset);
- mMiniMonthContainer.setLayoutParams(mVerticalControlsParams);
- }
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) {
- if (mPaused) {
- mUpdateOnResume = true;
- } else {
- initFragments(mController.getTime(), mController.getViewType(), null);
- }
- }
- }
-
- private void setMainPane(
- FragmentTransaction ft, int viewId, int viewType, long timeMillis, boolean force) {
- if (mOnSaveInstanceStateCalled) {
- return;
- }
- if (!force && mCurrentView == viewType) {
- return;
- }
-
- // Remove this when transition to and from month view looks fine.
- boolean doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH;
- FragmentManager fragmentManager = getFragmentManager();
-
- if (viewType != mCurrentView) {
- // The rules for this previous view are different than the
- // controller's and are used for intercepting the back button.
- if (mCurrentView != ViewType.EDIT && mCurrentView > 0) {
- mPreviousView = mCurrentView;
- }
- mCurrentView = viewType;
- }
- // Create new fragment
- Fragment frag = null;
- Fragment secFrag = null;
- switch (viewType) {
- case ViewType.AGENDA:
- break;
- case ViewType.DAY:
- if (mActionBar != null && (mActionBar.getSelectedTab() != mDayTab)) {
- mActionBar.selectTab(mDayTab);
- }
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBar.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX);
- }
- frag = new DayFragment(timeMillis, 1);
- break;
- case ViewType.MONTH:
- if (mActionBar != null && (mActionBar.getSelectedTab() != mMonthTab)) {
- mActionBar.selectTab(mMonthTab);
- }
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBar.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX);
- }
- frag = new MonthByWeekFragment(timeMillis, false);
- break;
- case ViewType.WEEK:
- default:
- if (mActionBar != null && (mActionBar.getSelectedTab() != mWeekTab)) {
- mActionBar.selectTab(mWeekTab);
- }
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBar.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX);
- }
- frag = new DayFragment(timeMillis, 7);
- break;
- }
-
- // Update the current view so that the menu can update its look according to the
- // current view.
- if (mActionBarMenuSpinnerAdapter != null) {
- mActionBarMenuSpinnerAdapter.setMainView(viewType);
- if (!mIsTabletConfig) {
- mActionBarMenuSpinnerAdapter.setTime(timeMillis);
- }
- }
-
-
- // Show date only on tablet configurations in views different than Agenda
- if (!mIsTabletConfig) {
- mDateRange.setVisibility(View.GONE);
- } else {
- mDateRange.setVisibility(View.GONE);
- }
-
- // Clear unnecessary buttons from the option menu when switching from the agenda view
- if (viewType != ViewType.AGENDA) {
- clearOptionsMenu();
- }
-
- boolean doCommit = false;
- if (ft == null) {
- doCommit = true;
- ft = fragmentManager.beginTransaction();
- }
-
- if (doTransition) {
- ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- }
-
- ft.replace(viewId, frag);
- if (DEBUG) {
- Log.d(TAG, "Adding handler with viewId " + viewId + " and type " + viewType);
- }
- // If the key is already registered this will replace it
- mController.registerEventHandler(viewId, (EventHandler) frag);
-
- if (doCommit) {
- if (DEBUG) {
- Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing());
- }
- ft.commit();
- }
- }
-
- private void setTitleInActionBar(EventInfo event) {
- if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) {
- return;
- }
-
- final long start = event.startTime.toMillis(false /* use isDst */);
- final long end;
- if (event.endTime != null) {
- end = event.endTime.toMillis(false /* use isDst */);
- } else {
- end = start;
- }
-
- final String msg = Utils.formatDateRange(this, start, end, (int) event.extraLong);
- CharSequence oldDate = mDateRange.getText();
- mDateRange.setText(msg);
- updateSecondaryTitleFields(event.selectedTime != null ? event.selectedTime.toMillis(true)
- : start);
- if (!TextUtils.equals(oldDate, msg)) {
- mDateRange.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- if (mShowWeekNum && mWeekTextView != null) {
- mWeekTextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- }
- }
-
- private void updateSecondaryTitleFields(long visibleMillisSinceEpoch) {
- mShowWeekNum = Utils.getShowWeekNumber(this);
- mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater);
- if (visibleMillisSinceEpoch != -1) {
- int weekNum = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this);
- mWeekNum = weekNum;
- }
-
- if (mShowWeekNum && (mCurrentView == ViewType.WEEK) && mIsTabletConfig
- && mWeekTextView != null) {
- String weekString = getResources().getQuantityString(R.plurals.weekN, mWeekNum,
- mWeekNum);
- mWeekTextView.setText(weekString);
- mWeekTextView.setVisibility(View.VISIBLE);
- } else if (visibleMillisSinceEpoch != -1 && mWeekTextView != null
- && mCurrentView == ViewType.DAY && mIsTabletConfig) {
- Time time = new Time(mTimeZone);
- time.set(visibleMillisSinceEpoch);
- int julianDay = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff);
- time.setToNow();
- int todayJulianDay = Time.getJulianDay(time.toMillis(false), time.gmtoff);
- String dayString = Utils.getDayOfWeekString(julianDay, todayJulianDay,
- visibleMillisSinceEpoch, this);
- mWeekTextView.setText(dayString);
- mWeekTextView.setVisibility(View.VISIBLE);
- } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) {
- mWeekTextView.setVisibility(View.GONE);
- }
-
- if (mHomeTime != null
- && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK)
- && !TextUtils.equals(mTimeZone, Time.getCurrentTimezone())) {
- Time time = new Time(mTimeZone);
- time.setToNow();
- long millis = time.toMillis(true);
- boolean isDST = time.isDst != 0;
- int flags = DateUtils.FORMAT_SHOW_TIME;
- if (DateFormat.is24HourFormat(this)) {
- flags |= DateUtils.FORMAT_24HOUR;
- }
- // Formats the time as
- String timeString = (new StringBuilder(
- Utils.formatDateRange(this, millis, millis, flags))).append(" ").append(
- TimeZone.getTimeZone(mTimeZone).getDisplayName(
- isDST, TimeZone.SHORT, Locale.getDefault())).toString();
- mHomeTime.setText(timeString);
- mHomeTime.setVisibility(View.VISIBLE);
- // Update when the minute changes
- mHomeTime.removeCallbacks(mHomeTimeUpdater);
- mHomeTime.postDelayed(
- mHomeTimeUpdater,
- DateUtils.MINUTE_IN_MILLIS - (millis % DateUtils.MINUTE_IN_MILLIS));
- } else if (mHomeTime != null) {
- mHomeTime.setVisibility(View.GONE);
- }
- }
-
- @Override
- public long getSupportedEventTypes() {
- return EventType.GO_TO | EventType.UPDATE_TITLE;
- }
-
- @Override
- public void handleEvent(EventInfo event) {
- long displayTime = -1;
- if (event.eventType == EventType.GO_TO) {
- if ((event.extraLong & CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS) != 0) {
- mBackToPreviousView = true;
- } else if (event.viewType != mController.getPreviousViewType()
- && event.viewType != ViewType.EDIT) {
- // Clear the flag is change to a different view type
- mBackToPreviousView = false;
- }
-
- setMainPane(
- null, R.id.main_pane, event.viewType, event.startTime.toMillis(false), false);
- if (mShowCalendarControls) {
- int animationSize = (mOrientation == Configuration.ORIENTATION_LANDSCAPE) ?
- mControlsAnimateWidth : mControlsAnimateHeight;
- boolean noControlsView = event.viewType == ViewType.MONTH;
- if (mControlsMenu != null) {
- mControlsMenu.setVisible(!noControlsView);
- mControlsMenu.setEnabled(!noControlsView);
- }
- if (noControlsView || mHideControls) {
- // hide minimonth and calendar frag
- mShowSideViews = false;
- if (!mHideControls) {
- final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this,
- "controlsOffset", 0, animationSize);
- slideAnimation.addListener(mSlideAnimationDoneListener);
- slideAnimation.setDuration(mCalendarControlsAnimationTime);
- ObjectAnimator.setFrameDelay(0);
- slideAnimation.start();
- } else {
- mMiniMonth.setVisibility(View.GONE);
- mCalendarsList.setVisibility(View.GONE);
- mMiniMonthContainer.setVisibility(View.GONE);
- }
- } else {
- // show minimonth and calendar frag
- mShowSideViews = true;
- mMiniMonth.setVisibility(View.VISIBLE);
- mCalendarsList.setVisibility(View.VISIBLE);
- mMiniMonthContainer.setVisibility(View.VISIBLE);
- if (!mHideControls &&
- (mController.getPreviousViewType() == ViewType.MONTH)) {
- final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this,
- "controlsOffset", animationSize, 0);
- slideAnimation.setDuration(mCalendarControlsAnimationTime);
- ObjectAnimator.setFrameDelay(0);
- slideAnimation.start();
- }
- }
- }
- displayTime = event.selectedTime != null ? event.selectedTime.toMillis(true)
- : event.startTime.toMillis(true);
- if (!mIsTabletConfig) {
- mActionBarMenuSpinnerAdapter.setTime(displayTime);
- }
- } else if (event.eventType == EventType.UPDATE_TITLE) {
- setTitleInActionBar(event);
- if (!mIsTabletConfig) {
- mActionBarMenuSpinnerAdapter.setTime(mController.getTime());
- }
- }
- updateSecondaryTitleFields(displayTime);
- }
-
- @Override
- public void eventsChanged() {
- mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
- }
-
- @Override
- public void onTabSelected(Tab tab, FragmentTransaction ft) {
- Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing());
- if (tab == mDayTab && mCurrentView != ViewType.DAY) {
- mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.DAY);
- } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) {
- 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 {
- 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);
- }
- }
-
- @Override
- public void onTabReselected(Tab tab, FragmentTransaction ft) {
- }
-
- @Override
- public void onTabUnselected(Tab tab, FragmentTransaction ft) {
- }
-
-
- @Override
- public boolean onNavigationItemSelected(int itemPosition, long itemId) {
- switch (itemPosition) {
- case CalendarViewAdapter.DAY_BUTTON_INDEX:
- if (mCurrentView != ViewType.DAY) {
- mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.DAY);
- }
- break;
- case CalendarViewAdapter.WEEK_BUTTON_INDEX:
- if (mCurrentView != ViewType.WEEK) {
- mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.WEEK);
- }
- break;
- case CalendarViewAdapter.MONTH_BUTTON_INDEX:
- if (mCurrentView != ViewType.MONTH) {
- mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.MONTH);
- }
- break;
- case CalendarViewAdapter.AGENDA_BUTTON_INDEX:
- break;
- default:
- Log.w(TAG, "ItemSelected event from unknown button: " + itemPosition);
- Log.w(TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition +
- " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab);
- break;
- }
- return false;
- }
-}
diff --git a/src/com/android/calendar/AllInOneActivity.kt b/src/com/android/calendar/AllInOneActivity.kt
new file mode 100644
index 00000000..6c2e825f
--- /dev/null
+++ b/src/com/android/calendar/AllInOneActivity.kt
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.accounts.AccountManager
+import android.accounts.AccountManagerCallback
+import android.accounts.AccountManagerFuture
+import android.animation.Animator
+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
+import android.content.AsyncQueryHandler
+import android.content.ContentResolver
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.database.Cursor
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+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.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import android.widget.RelativeLayout.LayoutParams
+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.month.MonthByWeekFragment
+import java.util.Locale
+import java.util.TimeZone
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+
+class AllInOneActivity : Activity(), EventHandler, OnSharedPreferenceChangeListener,
+ ActionBar.TabListener, ActionBar.OnNavigationListener {
+ private var mController: CalendarController? = null
+ private var mOnSaveInstanceStateCalled = false
+ private var mBackToPreviousView = false
+ private var mContentResolver: ContentResolver? = null
+ private var mPreviousView = 0
+ private var mCurrentView = 0
+ private var mPaused = true
+ private var mUpdateOnResume = false
+ private var mHideControls = false
+ private var mShowSideViews = true
+ private var mShowWeekNum = false
+ private var mHomeTime: TextView? = null
+ private var mDateRange: TextView? = null
+ private var mWeekTextView: TextView? = null
+ private var mMiniMonth: View? = null
+ private var mCalendarsList: View? = null
+ private var mMiniMonthContainer: View? = null
+ private var mSecondaryPane: View? = null
+ private var mTimeZone: String? = null
+ private var mShowCalendarControls = false
+ private var mShowEventInfoFullScreen = false
+ private var mWeekNum = 0
+ private var mCalendarControlsAnimationTime = 0
+ private var mControlsAnimateWidth = 0
+ private var mControlsAnimateHeight = 0
+ private var mViewEventId: Long = -1
+ private var mIntentEventStartMillis: Long = -1
+ private var mIntentEventEndMillis: Long = -1
+ private var mIntentAttendeeResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+ private var mIntentAllDay = false
+
+ // Action bar and Navigation bar (left side of Action bar)
+ private var mActionBar: ActionBar? = null
+ private val mDayTab: Tab? = null
+ private val mWeekTab: Tab? = null
+ private val mMonthTab: Tab? = null
+ private var mControlsMenu: MenuItem? = null
+ private var mOptionsMenu: Menu? = null
+ private var mActionBarMenuSpinnerAdapter: CalendarViewAdapter? = null
+ private var mHandler: QueryHandler? = null
+ private var mCheckForAccounts = true
+ private var mHideString: String? = null
+ private var mShowString: String? = null
+ var mDayOfMonthIcon: DayOfMonthDrawable? = null
+ var mOrientation = 0
+
+ // Params for animating the controls on the right
+ private var mControlsParams: LayoutParams? = null
+ private var mVerticalControlsParams: LinearLayout.LayoutParams? = null
+ private val mSlideAnimationDoneListener: AnimatorListener = object : AnimatorListener {
+ @Override
+ override fun onAnimationCancel(animation: Animator) {
+ }
+
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ val visibility: Int = if (mShowSideViews) View.VISIBLE else View.GONE
+ mMiniMonth?.setVisibility(visibility)
+ mCalendarsList?.setVisibility(visibility)
+ mMiniMonthContainer?.setVisibility(visibility)
+ }
+
+ @Override
+ override fun onAnimationRepeat(animation: Animator) {
+ }
+
+ @Override
+ override fun onAnimationStart(animation: Animator) {
+ }
+ }
+
+ private inner class QueryHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) {
+ @Override
+ protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) {
+ mCheckForAccounts = false
+ try {
+ // If the query didn't return a cursor for some reason return
+ if (cursor == null || cursor.getCount() > 0 || isFinishing()) {
+ return
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close()
+ }
+ }
+ val options = Bundle()
+ options.putCharSequence(
+ "introMessage",
+ getResources().getString(R.string.create_an_account_desc)
+ )
+ options.putBoolean("allowSkip", true)
+ val am: AccountManager = AccountManager.get(this@AllInOneActivity)
+ am.addAccount("com.google", CalendarContract.AUTHORITY, null, options,
+ this@AllInOneActivity,
+ object : AccountManagerCallback<Bundle?> {
+ @Override
+ override fun run(future: AccountManagerFuture<Bundle?>?) {
+ }
+ }, null
+ )
+ }
+ }
+
+ private val mHomeTimeUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ mTimeZone = Utils.getTimeZone(this@AllInOneActivity, this)
+ updateSecondaryTitleFields(-1)
+ this@AllInOneActivity.invalidateOptionsMenu()
+ Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone)
+ }
+ }
+
+ // runs every midnight/time changes and refreshes the today icon
+ private val mTimeChangesUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ mTimeZone = Utils.getTimeZone(this@AllInOneActivity, mHomeTimeUpdater)
+ this@AllInOneActivity.invalidateOptionsMenu()
+ Utils.setMidnightUpdater(mHandler, this, mTimeZone)
+ }
+ }
+
+ // Create an observer so that we can update the views whenever a
+ // Calendar event changes.
+ private val mObserver: ContentObserver = object : ContentObserver(Handler()) {
+ @Override
+ override fun deliverSelfNotifications(): Boolean {
+ return true
+ }
+
+ @Override
+ override fun onChange(selfChange: Boolean) {
+ eventsChanged()
+ }
+ }
+
+ @Override
+ protected override fun onNewIntent(intent: Intent) {
+ val action: String? = intent.getAction()
+ if (DEBUG) Log.d(TAG, "New intent received " + intent.toString())
+ // Don't change the date if we're just returning to the app's home
+ if (Intent.ACTION_VIEW.equals(action) &&
+ !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false)
+ ) {
+ var millis = parseViewAction(intent)
+ if (millis == -1L) {
+ millis = Utils.timeFromIntentInMillis(intent) as Long
+ }
+ if (millis != -1L && mViewEventId == -1L && mController != null) {
+ val time = Time(mTimeZone)
+ time.set(millis)
+ time.normalize(true)
+ mController?.sendEvent(this as Object?, EventType.GO_TO, time, time, -1,
+ ViewType.CURRENT)
+ }
+ }
+ }
+
+ @Override
+ protected override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) {
+ mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS)
+ }
+ // Launch add google account if this is first time and there are no
+ // accounts yet
+ if (mCheckForAccounts) {
+ mHandler = QueryHandler(this.getContentResolver())
+ mHandler?.startQuery(
+ 0, null, Calendars.CONTENT_URI, arrayOf<String>(
+ Calendars._ID
+ ), null, null /* selection args */, null /* sort order */
+ )
+ }
+
+ // This needs to be created before setContentView
+ mController = CalendarController.getInstance(this)
+
+ // Get time from intent or icicle
+ var timeMillis: Long = -1
+ var viewType = -1
+ val intent: Intent = getIntent()
+ if (icicle != null) {
+ timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME)
+ viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1)
+ } else {
+ val action: String? = intent.getAction()
+ if (Intent.ACTION_VIEW.equals(action)) {
+ // Open EventInfo later
+ timeMillis = parseViewAction(intent)
+ }
+ if (timeMillis == -1L) {
+ timeMillis = Utils.timeFromIntentInMillis(intent) as Long
+ }
+ }
+ if (viewType == -1 || viewType > ViewType.MAX_VALUE) {
+ viewType = Utils.getViewTypeFromIntentAndSharedPref(this)
+ }
+ mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater)
+ val t = Time(mTimeZone)
+ t.set(timeMillis)
+ if (DEBUG) {
+ if (icicle != null && intent != null) {
+ Log.d(
+ TAG,
+ "both, icicle:" + icicle.toString().toString() + " intent:" + intent.toString()
+ )
+ } else {
+ Log.d(TAG, "not both, icicle:$icicle intent:$intent")
+ }
+ }
+ val res: Resources = getResources()
+ mHideString = res.getString(R.string.hide_controls)
+ mShowString = res.getString(R.string.show_controls)
+ mOrientation = res.getConfiguration().orientation
+ if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ mControlsAnimateWidth = res.getDimension(R.dimen.calendar_controls_width).toInt()
+ if (mControlsParams == null) {
+ mControlsParams = LayoutParams(mControlsAnimateWidth, 0)
+ }
+ mControlsParams?.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+ as RelativeLayout.LayoutParams
+ } else {
+ // Make sure width is in between allowed min and max width values
+ mControlsAnimateWidth = Math.max(
+ res.getDisplayMetrics().widthPixels * 45 / 100,
+ res.getDimension(R.dimen.min_portrait_calendar_controls_width).toInt()
+ )
+ mControlsAnimateWidth = Math.min(
+ mControlsAnimateWidth,
+ res.getDimension(R.dimen.max_portrait_calendar_controls_width).toInt()
+ )
+ }
+ mControlsAnimateHeight = res?.getDimension(R.dimen.calendar_controls_height).toInt()
+ mHideControls = true
+ mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config)
+ mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config)
+ mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls)
+ mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen)
+ mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time)
+ Utils.setAllowWeekForDetailView(mIsMultipane)
+
+ // setContentView must be called before configureActionBar
+ setContentView(R.layout.all_in_one)
+ if (mIsTabletConfig) {
+ mDateRange = findViewById(R.id.date_bar) as TextView?
+ mWeekTextView = findViewById(R.id.week_num) as TextView?
+ } else {
+ mDateRange = getLayoutInflater().inflate(R.layout.date_range_title, null) as TextView
+ }
+
+ // configureActionBar auto-selects the first tab you add, so we need to
+ // call it before we set up our own fragments to make sure it doesn't
+ // overwrite us
+ configureActionBar(viewType)
+ mHomeTime = findViewById(R.id.home_time) as TextView?
+ mMiniMonth = findViewById(R.id.mini_month)
+ if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ mMiniMonth?.setLayoutParams(
+ LayoutParams(
+ mControlsAnimateWidth,
+ mControlsAnimateHeight
+ )
+ )
+ }
+ mCalendarsList = findViewById(R.id.calendar_list)
+ mMiniMonthContainer = findViewById(R.id.mini_month_container)
+ mSecondaryPane = findViewById(R.id.secondary_pane)
+
+ // Must register as the first activity 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?.registerFirstEventHandler(HANDLER_KEY, this)
+ initFragments(timeMillis, viewType, icicle)
+
+ // Listen for changes that would require this to be refreshed
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+ prefs?.registerOnSharedPreferenceChangeListener(this)
+ mContentResolver = getContentResolver()
+ }
+
+ private fun parseViewAction(intent: Intent?): Long {
+ var timeMillis: Long = -1
+ val data: Uri? = intent?.getData()
+ if (data != null && data?.isHierarchical()) {
+ val path = data.getPathSegments()
+ if (path?.size == 2 && path!![0].equals("events")) {
+ try {
+ mViewEventId = data.getLastPathSegment()?.toLong() as Long
+ if (mViewEventId != -1L) {
+ mIntentEventStartMillis = intent?.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0)
+ mIntentEventEndMillis = intent?.getLongExtra(EXTRA_EVENT_END_TIME, 0)
+ mIntentAttendeeResponse = intent?.getIntExtra(
+ ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE
+ )
+ mIntentAllDay = intent?.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false)
+ as Boolean
+ timeMillis = mIntentEventStartMillis
+ }
+ } catch (e: NumberFormatException) {
+ // Ignore if mViewEventId can't be parsed
+ }
+ }
+ }
+ return timeMillis
+ }
+
+ private fun configureActionBar(viewType: Int) {
+ createButtonsSpinner(viewType, mIsTabletConfig)
+ if (mIsMultipane) {
+ mActionBar?.setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_SHOW_HOME
+ )
+ } else {
+ mActionBar?.setDisplayOptions(0)
+ }
+ }
+
+ private fun createButtonsSpinner(viewType: Int, tabletConfig: Boolean) {
+ // If tablet configuration , show spinner with no dates
+ mActionBarMenuSpinnerAdapter = CalendarViewAdapter(this, viewType, !tabletConfig)
+ mActionBar = getActionBar()
+ mActionBar?.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST)
+ mActionBar?.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this)
+ when (viewType) {
+ ViewType.AGENDA -> {
+ }
+ ViewType.DAY -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX)
+ ViewType.WEEK -> mActionBar?.setSelectedNavigationItem(BUTTON_WEEK_INDEX)
+ ViewType.MONTH -> mActionBar?.setSelectedNavigationItem(BUTTON_MONTH_INDEX)
+ else -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX)
+ }
+ }
+
+ // Clear buttons used in the agenda view
+ private fun clearOptionsMenu() {
+ if (mOptionsMenu == null) {
+ return
+ }
+ val cancelItem: MenuItem? = mOptionsMenu?.findItem(R.id.action_cancel)
+ if (cancelItem != null) {
+ cancelItem?.setVisible(false)
+ }
+ }
+
+ @Override
+ protected override fun onResume() {
+ super.onResume()
+
+ // Check if the upgrade code has ever been run. If not, force a sync just this one time.
+ Utils.trySyncAndDisableUpgradeReceiver(this)
+
+ // Must register as the first activity 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?.registerFirstEventHandler(HANDLER_KEY, this)
+ mOnSaveInstanceStateCalled = false
+ mContentResolver?.registerContentObserver(
+ CalendarContract.Events.CONTENT_URI,
+ true, mObserver
+ )
+ if (mUpdateOnResume) {
+ initFragments(mController?.time as Long, mController?.viewType as Int, null)
+ mUpdateOnResume = false
+ }
+ val t = Time(mTimeZone)
+ t.set(mController?.time as Long)
+ mController?.sendEvent(
+ this as Object?, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT,
+ mController?.dateFlags as Long, null, null
+ )
+ // Make sure the drop-down menu will get its date updated at midnight
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBarMenuSpinnerAdapter?.refresh(this)
+ }
+ if (mControlsMenu != null) {
+ mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString)
+ }
+ mPaused = false
+ if (mViewEventId != -1L && mIntentEventStartMillis != -1L && mIntentEventEndMillis != -1L) {
+ val currentMillis: Long = System.currentTimeMillis()
+ var selectedTime: Long = -1
+ if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) {
+ selectedTime = currentMillis
+ }
+ mController?.sendEventRelatedEventWithExtra(
+ this as Object?, EventType.VIEW_EVENT, mViewEventId,
+ mIntentEventStartMillis, mIntentEventEndMillis, -1, -1,
+ EventInfo.buildViewExtraLong(mIntentAttendeeResponse, mIntentAllDay),
+ selectedTime
+ )
+ mViewEventId = -1
+ mIntentEventStartMillis = -1
+ mIntentEventEndMillis = -1
+ mIntentAllDay = false
+ }
+ Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone)
+ // Make sure the today icon is up to date
+ invalidateOptionsMenu()
+ }
+
+ @Override
+ protected override fun onPause() {
+ super.onPause()
+ mController?.deregisterEventHandler(HANDLER_KEY)
+ mPaused = true
+ mHomeTime?.removeCallbacks(mHomeTimeUpdater)
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBarMenuSpinnerAdapter?.onPause()
+ }
+ mContentResolver?.unregisterContentObserver(mObserver)
+ if (isFinishing()) {
+ // Stop listening for changes that would require this to be refreshed
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+ prefs?.unregisterOnSharedPreferenceChangeListener(this)
+ }
+ // FRAG_TODO save highlighted days of the week;
+ if (mController?.viewType != ViewType.EDIT) {
+ Utils.setDefaultView(this, mController?.viewType as Int)
+ }
+ Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater)
+ }
+
+ @Override
+ protected override fun onUserLeaveHint() {
+ mController?.sendEvent(this as Object?, EventType.USER_HOME, null, null, -1,
+ ViewType.CURRENT)
+ super.onUserLeaveHint()
+ }
+
+ @Override
+ override fun onSaveInstanceState(outState: Bundle) {
+ mOnSaveInstanceStateCalled = true
+ super.onSaveInstanceState(outState)
+ }
+
+ @Override
+ protected override fun onDestroy() {
+ super.onDestroy()
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+ prefs?.unregisterOnSharedPreferenceChangeListener(this)
+ mController?.deregisterAllEventHandlers()
+ CalendarController.removeInstance(this)
+ }
+
+ private fun initFragments(timeMillis: Long, viewType: Int, icicle: Bundle?) {
+ if (DEBUG) {
+ Log.d(TAG, "Initializing to $timeMillis for view $viewType")
+ }
+ val ft: FragmentTransaction = getFragmentManager().beginTransaction()
+ if (mShowCalendarControls) {
+ val miniMonthFrag: Fragment = MonthByWeekFragment(timeMillis, true)
+ ft.replace(R.id.mini_month, miniMonthFrag)
+ mController?.registerEventHandler(R.id.mini_month, miniMonthFrag as EventHandler)
+ }
+ if (!mShowCalendarControls || viewType == ViewType.EDIT) {
+ mMiniMonth?.setVisibility(View.GONE)
+ mCalendarsList?.setVisibility(View.GONE)
+ }
+ var info: EventInfo? = null
+ if (viewType == ViewType.EDIT) {
+ mPreviousView = GeneralPreferences.getSharedPreferences(this)?.getInt(
+ GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW
+ ) as Int
+ var eventId: Long = -1
+ val intent: Intent = getIntent()
+ val data: Uri? = intent.getData()
+ if (data != null) {
+ try {
+ eventId = data?.getLastPathSegment()?.toLong() as Long
+ } catch (e: NumberFormatException) {
+ 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)
+ }
+ val begin: Long = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1)
+ val end: Long = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1)
+ info = EventInfo()
+ if (end != -1L) {
+ info?.endTime = Time()
+ info?.endTime?.set(end)
+ }
+ if (begin != -1L) {
+ info?.startTime = Time()
+ info?.startTime?.set(begin)
+ }
+ info.id = eventId
+ // We set the viewtype so if the user presses back when they are
+ // done editing the controller knows we were in the Edit Event
+ // screen. Likewise for eventId
+ mController?.viewType = viewType
+ mController?.eventId = eventId
+ } else {
+ mPreviousView = viewType
+ }
+ setMainPane(ft, R.id.main_pane, viewType, timeMillis, true)
+ ft.commit() // this needs to be after setMainPane()
+ val t = Time(mTimeZone)
+ t.set(timeMillis)
+ if (viewType != ViewType.EDIT) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, -1, viewType)
+ }
+ }
+
+ @Override
+ override fun onBackPressed() {
+ if (mCurrentView == ViewType.EDIT || mBackToPreviousView) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, mPreviousView)
+ } else {
+ super.onBackPressed()
+ }
+ }
+
+ @Override
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ super.onCreateOptionsMenu(menu)
+ mOptionsMenu = menu
+ getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu)
+
+ // Hide the "show/hide controls" button if this is a phone
+ // or the view type is "Month".
+ mControlsMenu = menu.findItem(R.id.action_hide_controls)
+ if (!mShowCalendarControls) {
+ if (mControlsMenu != null) {
+ mControlsMenu?.setVisible(false)
+ mControlsMenu?.setEnabled(false)
+ }
+ } else if (mControlsMenu != null && mController != null &&
+ mController?.viewType == ViewType.MONTH) {
+ mControlsMenu?.setVisible(false)
+ mControlsMenu?.setEnabled(false)
+ } else if (mControlsMenu != null) {
+ mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString)
+ }
+ val menuItem: MenuItem = menu.findItem(R.id.action_today)
+ if (Utils.isJellybeanOrLater()) {
+ // replace the default top layer drawable of the today icon with a
+ // custom drawable that shows the day of the month of today
+ val icon: LayerDrawable = menuItem.getIcon() as LayerDrawable
+ Utils.setTodayIcon(icon, this, mTimeZone)
+ } else {
+ menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light)
+ }
+ return true
+ }
+
+ @Override
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ var t: Time? = null
+ var viewType: Int = ViewType.CURRENT
+ var extras: Long = CalendarController.EXTRA_GOTO_TIME
+ val itemId: Int = item.getItemId()
+ if (itemId == R.id.action_today) {
+ viewType = ViewType.CURRENT
+ t = Time(mTimeZone)
+ t.setToNow()
+ extras = extras or CalendarController.EXTRA_GOTO_TODAY
+ } else if (itemId == R.id.action_hide_controls) {
+ mHideControls = !mHideControls
+ item.setTitle(if (mHideControls) mShowString else mHideString)
+ if (!mHideControls) {
+ mMiniMonth?.setVisibility(View.VISIBLE)
+ mCalendarsList?.setVisibility(View.VISIBLE)
+ mMiniMonthContainer?.setVisibility(View.VISIBLE)
+ }
+ val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+ this, "controlsOffset",
+ if (mHideControls) 0 else mControlsAnimateWidth,
+ if (mHideControls) mControlsAnimateWidth else 0
+ )
+ slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+ ObjectAnimator.setFrameDelay(0)
+ slideAnimation.start()
+ return true
+ } else {
+ Log.d(TAG, "Unsupported itemId: $itemId")
+ return true
+ }
+ mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, t, -1,
+ viewType, extras, null, null)
+ return true
+ }
+
+ /**
+ * Sets the offset of the controls on the right for animating them off/on
+ * screen. ProGuard strips this if it's not in proguard.flags
+ *
+ * @param controlsOffset The current offset in pixels
+ */
+ fun setControlsOffset(controlsOffset: Int) {
+ if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ mMiniMonth?.setTranslationX(controlsOffset.toFloat())
+ mCalendarsList?.setTranslationX(controlsOffset.toFloat())
+ mControlsParams?.width = Math.max(0, mControlsAnimateWidth - controlsOffset)
+ mMiniMonthContainer?.setLayoutParams(mControlsParams)
+ } else {
+ mMiniMonth?.setTranslationY(controlsOffset.toFloat())
+ mCalendarsList?.setTranslationY(controlsOffset.toFloat())
+ if (mVerticalControlsParams == null) {
+ mVerticalControlsParams = LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight
+ ) as LinearLayout.LayoutParams?
+ }
+ mVerticalControlsParams?.height = Math.max(0, mControlsAnimateHeight - controlsOffset)
+ mMiniMonthContainer?.setLayoutParams(mVerticalControlsParams)
+ }
+ }
+
+ @Override
+ override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String) {
+ if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) {
+ if (mPaused) {
+ mUpdateOnResume = true
+ } else {
+ initFragments(mController?.time as Long, mController?.viewType as Int, null)
+ }
+ }
+ }
+
+ private fun setMainPane(
+ ft: FragmentTransaction?,
+ viewId: Int,
+ viewType: Int,
+ timeMillis: Long,
+ force: Boolean
+ ) {
+ var ft: FragmentTransaction? = ft
+ if (mOnSaveInstanceStateCalled) {
+ return
+ }
+ if (!force && mCurrentView == viewType) {
+ return
+ }
+
+ // Remove this when transition to and from month view looks fine.
+ val doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH
+ val fragmentManager: FragmentManager = getFragmentManager()
+ if (viewType != mCurrentView) {
+ // The rules for this previous view are different than the
+ // controller's and are used for intercepting the back button.
+ if (mCurrentView != ViewType.EDIT && mCurrentView > 0) {
+ mPreviousView = mCurrentView
+ }
+ mCurrentView = viewType
+ }
+ // Create new fragment
+ var frag: Fragment? = null
+ val secFrag: Fragment? = null
+ when (viewType) {
+ ViewType.AGENDA -> {
+ }
+ ViewType.DAY -> {
+ if (mActionBar != null && mActionBar?.getSelectedTab() != mDayTab) {
+ mActionBar?.selectTab(mDayTab)
+ }
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX)
+ }
+ frag = DayFragment(timeMillis, 1)
+ }
+ ViewType.MONTH -> {
+ if (mActionBar != null && mActionBar?.getSelectedTab() != mMonthTab) {
+ mActionBar?.selectTab(mMonthTab)
+ }
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX)
+ }
+ frag = MonthByWeekFragment(timeMillis, false)
+ }
+ ViewType.WEEK -> {
+ if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) {
+ mActionBar?.selectTab(mWeekTab)
+ }
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX)
+ }
+ frag = DayFragment(timeMillis, 7)
+ }
+ else -> {
+ if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) {
+ mActionBar?.selectTab(mWeekTab)
+ }
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX)
+ }
+ frag = DayFragment(timeMillis, 7)
+ }
+ }
+
+ // Update the current view so that the menu can update its look according to the
+ // current view.
+ if (mActionBarMenuSpinnerAdapter != null) {
+ mActionBarMenuSpinnerAdapter?.setMainView(viewType)
+ if (!mIsTabletConfig) {
+ mActionBarMenuSpinnerAdapter?.setTime(timeMillis)
+ }
+ }
+
+ // Show date only on tablet configurations in views different than Agenda
+ if (!mIsTabletConfig) {
+ mDateRange?.setVisibility(View.GONE)
+ } else {
+ mDateRange?.setVisibility(View.GONE)
+ }
+
+ // Clear unnecessary buttons from the option menu when switching from the agenda view
+ if (viewType != ViewType.AGENDA) {
+ clearOptionsMenu()
+ }
+ var doCommit = false
+ if (ft == null) {
+ doCommit = true
+ ft = fragmentManager.beginTransaction()
+ }
+ if (doTransition) {
+ ft?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+ }
+ ft?.replace(viewId, frag)
+ if (DEBUG) {
+ Log.d(TAG, "Adding handler with viewId $viewId and type $viewType")
+ }
+ // If the key is already registered this will replace it
+ mController?.registerEventHandler(viewId, frag as EventHandler?)
+ if (doCommit) {
+ if (DEBUG) {
+ Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing())
+ }
+ ft?.commit()
+ }
+ }
+
+ private fun setTitleInActionBar(event: EventInfo) {
+ if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) {
+ return
+ }
+ val start: Long? = event?.startTime?.toMillis(false /* use isDst */)
+ val end: Long?
+ end = if (event.endTime != null) {
+ event?.endTime?.toMillis(false /* use isDst */)
+ } else {
+ start
+ }
+ val msg: String? = Utils.formatDateRange(this,
+ start as Long,
+ end as Long,
+ event.extraLong.toInt()
+ )
+ val oldDate: CharSequence? = mDateRange?.getText()
+ mDateRange?.setText(msg)
+ updateSecondaryTitleFields(if (event?.selectedTime != null)
+ event?.selectedTime?.toMillis(true) as Long else start)
+ if (!TextUtils.equals(oldDate, msg)) {
+ mDateRange?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ if (mShowWeekNum && mWeekTextView != null) {
+ mWeekTextView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
+ }
+
+ private fun updateSecondaryTitleFields(visibleMillisSinceEpoch: Long) {
+ mShowWeekNum = Utils.getShowWeekNumber(this)
+ mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater)
+ if (visibleMillisSinceEpoch != -1L) {
+ val weekNum: Int = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this)
+ mWeekNum = weekNum
+ }
+ if (mShowWeekNum && mCurrentView == ViewType.WEEK && mIsTabletConfig &&
+ mWeekTextView != null
+ ) {
+ val weekString: String = getResources().getQuantityString(
+ R.plurals.weekN, mWeekNum,
+ mWeekNum
+ )
+ mWeekTextView?.setText(weekString)
+ mWeekTextView?.setVisibility(View.VISIBLE)
+ } else if (visibleMillisSinceEpoch != -1L && mWeekTextView != null &&
+ mCurrentView == ViewType.DAY && mIsTabletConfig) {
+ val time = Time(mTimeZone)
+ time.set(visibleMillisSinceEpoch)
+ val julianDay: Int = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff)
+ time.setToNow()
+ val todayJulianDay: Int = Time.getJulianDay(time.toMillis(false), time.gmtoff)
+ val dayString: String = Utils.getDayOfWeekString(
+ julianDay,
+ todayJulianDay,
+ visibleMillisSinceEpoch,
+ this
+ )
+ mWeekTextView?.setText(dayString)
+ mWeekTextView?.setVisibility(View.VISIBLE)
+ } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) {
+ mWeekTextView?.setVisibility(View.GONE)
+ }
+ if (mHomeTime != null && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) &&
+ !TextUtils.equals(mTimeZone, Time.getCurrentTimezone())
+ ) {
+ val time = Time(mTimeZone)
+ time.setToNow()
+ val millis: Long = time.toMillis(true)
+ val isDST = time.isDst !== 0
+ var flags: Int = DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(this)) {
+ flags = flags or DateUtils.FORMAT_24HOUR
+ }
+ // Formats the time as
+ val timeString: String = StringBuilder(
+ Utils.formatDateRange(this, millis, millis, flags)
+ ).append(" ").append(
+ TimeZone.getTimeZone(mTimeZone).getDisplayName(
+ isDST, TimeZone.SHORT, Locale.getDefault()
+ )
+ ).toString()
+ mHomeTime?.setText(timeString)
+ mHomeTime?.setVisibility(View.VISIBLE)
+ // Update when the minute changes
+ mHomeTime?.removeCallbacks(mHomeTimeUpdater)
+ mHomeTime?.postDelayed(
+ mHomeTimeUpdater,
+ DateUtils.MINUTE_IN_MILLIS - millis % DateUtils.MINUTE_IN_MILLIS
+ )
+ } else if (mHomeTime != null) {
+ mHomeTime?.setVisibility(View.GONE)
+ }
+ }
+
+ @get:Override override val supportedEventTypes: Long
+ get() = EventType.GO_TO or EventType.UPDATE_TITLE
+
+ @Override
+ override fun handleEvent(event: EventInfo?) {
+ var displayTime: Long = -1
+ if (event?.eventType == EventType.GO_TO) {
+ if (event?.extraLong and CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS != 0L) {
+ mBackToPreviousView = true
+ } else if (event?.viewType != mController?.previousViewType &&
+ event?.viewType != ViewType.EDIT
+ ) {
+ // Clear the flag is change to a different view type
+ mBackToPreviousView = false
+ }
+ setMainPane(
+ null, R.id.main_pane, event?.viewType, event?.startTime?.toMillis(false)
+ as Long, false
+ )
+ if (mShowCalendarControls) {
+ val animationSize =
+ if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) mControlsAnimateWidth
+ else mControlsAnimateHeight
+ val noControlsView = event?.viewType == ViewType.MONTH
+ if (mControlsMenu != null) {
+ mControlsMenu?.setVisible(!noControlsView)
+ mControlsMenu?.setEnabled(!noControlsView)
+ }
+ if (noControlsView || mHideControls) {
+ // hide minimonth and calendar frag
+ mShowSideViews = false
+ if (!mHideControls) {
+ val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+ this,
+ "controlsOffset", 0, animationSize
+ )
+ slideAnimation.addListener(mSlideAnimationDoneListener)
+ slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+ ObjectAnimator.setFrameDelay(0)
+ slideAnimation.start()
+ } else {
+ mMiniMonth?.setVisibility(View.GONE)
+ mCalendarsList?.setVisibility(View.GONE)
+ mMiniMonthContainer?.setVisibility(View.GONE)
+ }
+ } else {
+ // show minimonth and calendar frag
+ mShowSideViews = true
+ mMiniMonth?.setVisibility(View.VISIBLE)
+ mCalendarsList?.setVisibility(View.VISIBLE)
+ mMiniMonthContainer?.setVisibility(View.VISIBLE)
+ if (!mHideControls &&
+ mController?.previousViewType == ViewType.MONTH
+ ) {
+ val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+ this,
+ "controlsOffset", animationSize, 0
+ )
+ slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+ ObjectAnimator.setFrameDelay(0)
+ slideAnimation.start()
+ }
+ }
+ }
+ displayTime =
+ if (event?.selectedTime != null) event?.selectedTime?.toMillis(true) as Long
+ else event?.startTime?.toMillis(true) as Long
+ if (!mIsTabletConfig) {
+ mActionBarMenuSpinnerAdapter?.setTime(displayTime)
+ }
+ } else if (event?.eventType == EventType.UPDATE_TITLE) {
+ setTitleInActionBar(event as CalendarController.EventInfo)
+ if (!mIsTabletConfig) {
+ mActionBarMenuSpinnerAdapter?.setTime(mController?.time as Long)
+ }
+ }
+ updateSecondaryTitleFields(displayTime)
+ }
+
+ @Override
+ override fun eventsChanged() {
+ mController?.sendEvent(this as Object?, EventType.EVENTS_CHANGED, null, null, -1,
+ ViewType.CURRENT)
+ }
+
+ @Override
+ override fun onTabSelected(tab: Tab?, ft: FragmentTransaction?) {
+ Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing())
+ if (tab == mDayTab && mCurrentView != ViewType.DAY) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY)
+ } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK)
+ } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH)
+ } else {
+ Log.w(
+ TAG, "TabSelected event from unknown tab: " +
+ if (tab == null) "null" else tab.getText()
+ )
+ Log.w(
+ TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab +
+ " Week:" + mWeekTab + " Month:" + mMonthTab
+ )
+ }
+ }
+
+ @Override
+ override fun onTabReselected(tab: Tab?, ft: FragmentTransaction?) {
+ }
+
+ @Override
+ override fun onTabUnselected(tab: Tab?, ft: FragmentTransaction?) {
+ }
+
+ @Override
+ override fun onNavigationItemSelected(itemPosition: Int, itemId: Long): Boolean {
+ when (itemPosition) {
+ CalendarViewAdapter.DAY_BUTTON_INDEX -> if (mCurrentView != ViewType.DAY) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+ ViewType.DAY)
+ }
+ CalendarViewAdapter.WEEK_BUTTON_INDEX -> if (mCurrentView != ViewType.WEEK) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+ ViewType.WEEK)
+ }
+ CalendarViewAdapter.MONTH_BUTTON_INDEX -> if (mCurrentView != ViewType.MONTH) {
+ mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+ ViewType.MONTH)
+ }
+ CalendarViewAdapter.AGENDA_BUTTON_INDEX -> {
+ }
+ else -> {
+ Log.w(TAG, "ItemSelected event from unknown button: $itemPosition")
+ Log.w(
+ TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition +
+ " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab
+ )
+ }
+ }
+ return false
+ }
+
+ companion object {
+ private const val TAG = "AllInOneActivity"
+ private const val DEBUG = false
+ private const val EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment"
+ private const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time"
+ private const val BUNDLE_KEY_EVENT_ID = "key_event_id"
+ private const val BUNDLE_KEY_RESTORE_VIEW = "key_restore_view"
+ private const val BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts"
+ private const val HANDLER_KEY = 0
+
+ // Indices of buttons for the drop down menu (tabs replacement)
+ // Must match the strings in the array buttons_list in arrays.xml and the
+ // OnNavigationListener
+ private const val BUTTON_DAY_INDEX = 0
+ private const val BUTTON_WEEK_INDEX = 1
+ private const val BUTTON_MONTH_INDEX = 2
+ private const val BUTTON_AGENDA_INDEX = 3
+ private var mIsMultipane = false
+ private var mIsTabletConfig = false
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.java b/src/com/android/calendar/AsyncQueryServiceHelper.java
deleted file mode 100644
index c6e0a2bc..00000000
--- a/src/com/android/calendar/AsyncQueryServiceHelper.java
+++ /dev/null
@@ -1,70 +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.IntentService;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.PriorityQueue;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.TimeUnit;
-
-public class AsyncQueryServiceHelper extends IntentService {
- private static final String TAG = "AsyncQuery";
-
- public AsyncQueryServiceHelper(String name) {
- super(name);
- }
-
- public AsyncQueryServiceHelper() {
- super("AsyncQueryServiceHelper");
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- }
-}
diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.kt b/src/com/android/calendar/AsyncQueryServiceHelper.kt
new file mode 100644
index 00000000..47973304
--- /dev/null
+++ b/src/com/android/calendar/AsyncQueryServiceHelper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.IntentService
+import android.content.ContentProviderOperation
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.OperationApplicationException
+import android.database.Cursor
+import android.net.Uri
+import android.os.Handler
+import android.os.Message
+import android.os.RemoteException
+import android.os.SystemClock
+import android.util.Log
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Iterator
+import java.util.PriorityQueue
+import java.util.concurrent.Delayed
+import java.util.concurrent.TimeUnit
+
+class AsyncQueryServiceHelper : IntentService {
+ constructor(name: String?) : super(name) {}
+ constructor() : super("AsyncQueryServiceHelper") {}
+
+ protected override fun onHandleIntent(intent: Intent?) {
+ }
+
+ override fun onStart(intent: Intent?, startId: Int) {
+ super.onStart(intent, startId)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ companion object {
+ private const val TAG = "AsyncQuery"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarApplication.java b/src/com/android/calendar/CalendarApplication.kt
index d0ca4698..445d7257 100644
--- a/src/com/android/calendar/CalendarApplication.java
+++ b/src/com/android/calendar/CalendarApplication.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.calendar
-package com.android.calendar;
+import android.app.Application
-import android.app.Application;
-
-public class CalendarApplication extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
+class CalendarApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
/*
* Ensure the default values are set for any receiver, activity,
* service, etc. of Calendar
*/
- GeneralPreferences.setDefaultValues(this);
+ GeneralPreferences.setDefaultValues(this)
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarBackupAgent.java b/src/com/android/calendar/CalendarBackupAgent.java
deleted file mode 100644
index 02456fdc..00000000
--- a/src/com/android/calendar/CalendarBackupAgent.java
+++ /dev/null
@@ -1,41 +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.backup.BackupAgentHelper;
-import android.app.backup.BackupDataInput;
-import android.app.backup.SharedPreferencesBackupHelper;
-import android.content.Context;
-import android.content.SharedPreferences.Editor;
-import android.os.ParcelFileDescriptor;
-
-import java.io.IOException;
-
-public class CalendarBackupAgent extends BackupAgentHelper
-{
- static final String SHARED_KEY = "shared_pref";
-
- @Override
- public void onCreate() {
- }
-
- @Override
- public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
- throws IOException {
- super.onRestore(data, appVersionCode, newState);
- }
-}
diff --git a/src/com/android/calendar/CalendarBackupAgent.kt b/src/com/android/calendar/CalendarBackupAgent.kt
new file mode 100644
index 00000000..f3e230ac
--- /dev/null
+++ b/src/com/android/calendar/CalendarBackupAgent.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup.BackupAgentHelper
+import android.app.backup.BackupDataInput
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import android.content.SharedPreferences.Editor
+import android.os.ParcelFileDescriptor
+
+import java.io.IOException
+
+class CalendarBackupAgent : BackupAgentHelper() {
+ override fun onCreate() {
+ }
+
+ @Throws(IOException::class)
+ override fun onRestore(data: BackupDataInput?, appVersionCode: Int,
+ newState: ParcelFileDescriptor?) {
+ super.onRestore(data, appVersionCode, newState)
+ }
+
+ companion object {
+ const val SHARED_KEY = "shared_pref"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarController.java b/src/com/android/calendar/CalendarController.java
deleted file mode 100644
index 37286f2e..00000000
--- a/src/com/android/calendar/CalendarController.java
+++ /dev/null
@@ -1,713 +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_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.text.format.Time;
-import android.util.Log;
-import android.util.Pair;
-
-import java.lang.ref.WeakReference;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.Map.Entry;
-import java.util.WeakHashMap;
-
-public class CalendarController {
- private static final boolean DEBUG = false;
- private static final String TAG = "CalendarController";
-
- public static final String EVENT_EDIT_ON_LAUNCH = "editMode";
-
- public static final int MIN_CALENDAR_YEAR = 1970;
- public static final int MAX_CALENDAR_YEAR = 2036;
- public static final int MIN_CALENDAR_WEEK = 0;
- public static final int MAX_CALENDAR_WEEK = 3497; // weeks between 1/1/1970 and 1/1/2037
-
- private final Context mContext;
-
- // This uses a LinkedHashMap so that we can replace fragments based on the
- // view id they are being expanded into since we can't guarantee a reference
- // to the handler will be findable
- private final LinkedHashMap<Integer,EventHandler> eventHandlers =
- new LinkedHashMap<Integer,EventHandler>(5);
- private final LinkedList<Integer> mToBeRemovedEventHandlers = new LinkedList<Integer>();
- private final LinkedHashMap<Integer, EventHandler> mToBeAddedEventHandlers = new LinkedHashMap<
- Integer, EventHandler>();
- private Pair<Integer, EventHandler> mFirstEventHandler;
- private Pair<Integer, EventHandler> mToBeAddedFirstEventHandler;
- private volatile int mDispatchInProgressCounter = 0;
-
- private static WeakHashMap<Context, WeakReference<CalendarController>> instances =
- new WeakHashMap<Context, WeakReference<CalendarController>>();
-
- private final WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1);
-
- private int mViewType = -1;
- private int mDetailViewType = -1;
- private int mPreviousViewType = -1;
- private long mEventId = -1;
- private final Time mTime = new Time();
- private long mDateFlags = 0;
-
- private final Runnable mUpdateTimezone = new Runnable() {
- @Override
- public void run() {
- mTime.switchTimezone(Utils.getTimeZone(mContext, this));
- }
- };
-
- /**
- * One of the event types that are sent to or from the controller
- */
- public interface EventType {
- // Simple view of an event
- final long VIEW_EVENT = 1L << 1;
-
- // Full detail view in read only mode
- final long VIEW_EVENT_DETAILS = 1L << 2;
-
- // full detail view in edit mode
- final long EDIT_EVENT = 1L << 3;
-
- final long GO_TO = 1L << 5;
-
- final long EVENTS_CHANGED = 1L << 7;
-
- final long USER_HOME = 1L << 9;
-
- // date range has changed, update the title
- final long UPDATE_TITLE = 1L << 10;
- }
-
- /**
- * One of the Agenda/Day/Week/Month view types
- */
- public interface ViewType {
- final int DETAIL = -1;
- final int CURRENT = 0;
- final int AGENDA = 1;
- final int DAY = 2;
- final int WEEK = 3;
- final int MONTH = 4;
- final int EDIT = 5;
- final int MAX_VALUE = 5;
- }
-
- public static class EventInfo {
-
- private static final long ATTENTEE_STATUS_MASK = 0xFF;
- private static final long ALL_DAY_MASK = 0x100;
- private static final int ATTENDEE_STATUS_NONE_MASK = 0x01;
- private static final int ATTENDEE_STATUS_ACCEPTED_MASK = 0x02;
- private static final int ATTENDEE_STATUS_DECLINED_MASK = 0x04;
- private static final int ATTENDEE_STATUS_TENTATIVE_MASK = 0x08;
-
- public long eventType; // one of the EventType
- public int viewType; // one of the ViewType
- public long id; // event id
- public Time selectedTime; // the selected time in focus
-
- // Event start and end times. All-day events are represented in:
- // - local time for GO_TO commands
- // - UTC time for VIEW_EVENT and other event-related commands
- public Time startTime;
- public Time endTime;
-
- public int x; // x coordinate in the activity space
- public int y; // y coordinate in the activity space
- public String query; // query for a user search
- public ComponentName componentName; // used in combination with query
- public String eventTitle;
- public long calendarId;
-
- /**
- * For EventType.VIEW_EVENT:
- * It is the default attendee response and an all day event indicator.
- * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
- * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE.
- * 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.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.
- * Set to {@link #EXTRA_GOTO_BACK_TO_PREVIOUS} if back should bring back previous view.
- * Set to {@link #EXTRA_GOTO_TODAY} if this is a user request to go to the current time.
- * <p>
- * For EventType.UPDATE_TITLE:
- * Set formatting flags for Utils.formatDateRange
- */
- public long extraLong;
-
- public boolean isAllDay() {
- if (eventType != EventType.VIEW_EVENT) {
- Log.wtf(TAG, "illegal call to isAllDay , wrong event type " + eventType);
- return false;
- }
- return ((extraLong & ALL_DAY_MASK) != 0) ? true : false;
- }
-
- public int getResponse() {
- if (eventType != EventType.VIEW_EVENT) {
- Log.wtf(TAG, "illegal call to getResponse , wrong event type " + eventType);
- return Attendees.ATTENDEE_STATUS_NONE;
- }
-
- int response = (int)(extraLong & ATTENTEE_STATUS_MASK);
- switch (response) {
- case ATTENDEE_STATUS_NONE_MASK:
- return Attendees.ATTENDEE_STATUS_NONE;
- case ATTENDEE_STATUS_ACCEPTED_MASK:
- return Attendees.ATTENDEE_STATUS_ACCEPTED;
- case ATTENDEE_STATUS_DECLINED_MASK:
- return Attendees.ATTENDEE_STATUS_DECLINED;
- case ATTENDEE_STATUS_TENTATIVE_MASK:
- return Attendees.ATTENDEE_STATUS_TENTATIVE;
- default:
- Log.wtf(TAG,"Unknown attendee response " + response);
- }
- return ATTENDEE_STATUS_NONE_MASK;
- }
-
- // Used to build the extra long for a VIEW event.
- public static long buildViewExtraLong(int response, boolean allDay) {
- long extra = allDay ? ALL_DAY_MASK : 0;
-
- switch (response) {
- case Attendees.ATTENDEE_STATUS_NONE:
- extra |= ATTENDEE_STATUS_NONE_MASK;
- break;
- case Attendees.ATTENDEE_STATUS_ACCEPTED:
- extra |= ATTENDEE_STATUS_ACCEPTED_MASK;
- break;
- case Attendees.ATTENDEE_STATUS_DECLINED:
- extra |= ATTENDEE_STATUS_DECLINED_MASK;
- break;
- case Attendees.ATTENDEE_STATUS_TENTATIVE:
- extra |= ATTENDEE_STATUS_TENTATIVE_MASK;
- break;
- default:
- Log.wtf(TAG,"Unknown attendee response " + response);
- extra |= ATTENDEE_STATUS_NONE_MASK;
- break;
- }
- return extra;
- }
- }
-
- /**
- * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time
- * can be ignored
- */
- public static final long EXTRA_GOTO_DATE = 1;
- public static final long EXTRA_GOTO_TIME = 2;
- public static final long EXTRA_GOTO_BACK_TO_PREVIOUS = 4;
- public static final long EXTRA_GOTO_TODAY = 8;
-
- public interface EventHandler {
- long getSupportedEventTypes();
- void handleEvent(EventInfo event);
-
- /**
- * This notifies the handler that the database has changed and it should
- * update its view.
- */
- void eventsChanged();
- }
-
- /**
- * Creates and/or returns an instance of CalendarController associated with
- * the supplied context. It is best to pass in the current Activity.
- *
- * @param context The activity if at all possible.
- */
- public static CalendarController getInstance(Context context) {
- synchronized (instances) {
- CalendarController controller = null;
- WeakReference<CalendarController> weakController = instances.get(context);
- if (weakController != null) {
- controller = weakController.get();
- }
-
- if (controller == null) {
- controller = new CalendarController(context);
- instances.put(context, new WeakReference(controller));
- }
- return controller;
- }
- }
-
- /**
- * Removes an instance when it is no longer needed. This should be called in
- * an activity's onDestroy method.
- *
- * @param context The activity used to create the controller
- */
- public static void removeInstance(Context context) {
- instances.remove(context);
- }
-
- private CalendarController(Context context) {
- mContext = context;
- mUpdateTimezone.run();
- mTime.setToNow();
- mDetailViewType = Utils.getSharedPreference(mContext,
- GeneralPreferences.KEY_DETAILED_VIEW,
- GeneralPreferences.DEFAULT_DETAILED_VIEW);
- }
-
- public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis,
- long endMillis, int x, int y, long selectedMillis) {
- // TODO: pass the real allDay status or at least a status that says we don't know the
- // status and have the receiver query the data.
- // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo
- // so currently the missing allDay status has no effect.
- sendEventRelatedEventWithExtra(sender, eventType, eventId, startMillis, endMillis, x, y,
- EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false),
- selectedMillis);
- }
-
- /**
- * Helper for sending New/View/Edit/Delete events
- *
- * @param sender object of the caller
- * @param eventType one of {@link EventType}
- * @param eventId event id
- * @param startMillis start time
- * @param endMillis end time
- * @param x x coordinate in the activity space
- * @param y y coordinate in the activity space
- * @param extraLong default response value for the "simple event view" and all day indication.
- * Use Attendees.ATTENDEE_STATUS_NONE for no response.
- * @param selectedMillis The time to specify as selected
- */
- public void sendEventRelatedEventWithExtra(Object sender, long eventType, long eventId,
- long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis) {
- sendEventRelatedEventWithExtraWithTitleWithCalendarId(sender, eventType, eventId,
- startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1);
- }
-
- /**
- * Helper for sending New/View/Edit/Delete events
- *
- * @param sender object of the caller
- * @param eventType one of {@link EventType}
- * @param eventId event id
- * @param startMillis start time
- * @param endMillis end time
- * @param x x coordinate in the activity space
- * @param y y coordinate in the activity space
- * @param extraLong default response value for the "simple event view" and all day indication.
- * Use Attendees.ATTENDEE_STATUS_NONE for no response.
- * @param selectedMillis The time to specify as selected
- * @param title The title of the event
- * @param calendarId The id of the calendar which the event belongs to
- */
- public void sendEventRelatedEventWithExtraWithTitleWithCalendarId(Object sender, long eventType,
- long eventId, long startMillis, long endMillis, int x, int y, long extraLong,
- long selectedMillis, String title, long calendarId) {
- EventInfo info = new EventInfo();
- info.eventType = eventType;
- if (eventType == EventType.VIEW_EVENT_DETAILS) {
- info.viewType = ViewType.CURRENT;
- }
-
- info.id = eventId;
- info.startTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
- info.startTime.set(startMillis);
- if (selectedMillis != -1) {
- info.selectedTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
- info.selectedTime.set(selectedMillis);
- } else {
- info.selectedTime = info.startTime;
- }
- info.endTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
- info.endTime.set(endMillis);
- info.x = x;
- info.y = y;
- info.extraLong = extraLong;
- info.eventTitle = title;
- info.calendarId = calendarId;
- this.sendEvent(sender, info);
- }
- /**
- * Helper for sending non-calendar-event events
- *
- * @param sender object of the caller
- * @param eventType one of {@link EventType}
- * @param start start time
- * @param end end time
- * @param eventId event id
- * @param viewType {@link ViewType}
- */
- public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
- int viewType) {
- sendEvent(sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null,
- null);
- }
-
- /**
- * sendEvent() variant with extraLong, search query, and search component name.
- */
- public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
- int viewType, long extraLong, String query, ComponentName componentName) {
- sendEvent(sender, eventType, start, end, start, eventId, viewType, extraLong, query,
- componentName);
- }
-
- public void sendEvent(Object sender, long eventType, Time start, Time end, Time selected,
- long eventId, int viewType, long extraLong, String query, ComponentName componentName) {
- EventInfo info = new EventInfo();
- info.eventType = eventType;
- info.startTime = start;
- info.selectedTime = selected;
- info.endTime = end;
- info.id = eventId;
- info.viewType = viewType;
- info.query = query;
- info.componentName = componentName;
- info.extraLong = extraLong;
- this.sendEvent(sender, info);
- }
-
- public void sendEvent(Object sender, final EventInfo event) {
- // TODO Throw exception on invalid events
-
- if (DEBUG) {
- Log.d(TAG, eventInfoToString(event));
- }
-
- Long filteredTypes = filters.get(sender);
- if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) {
- // Suppress event per filter
- if (DEBUG) {
- Log.d(TAG, "Event suppressed");
- }
- return;
- }
-
- mPreviousViewType = mViewType;
-
- // Fix up view if not specified
- if (event.viewType == ViewType.DETAIL) {
- event.viewType = mDetailViewType;
- mViewType = mDetailViewType;
- } else if (event.viewType == ViewType.CURRENT) {
- event.viewType = mViewType;
- } else if (event.viewType != ViewType.EDIT) {
- mViewType = event.viewType;
-
- if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY
- || (Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK)) {
- mDetailViewType = mViewType;
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "vvvvvvvvvvvvvvv");
- Log.d(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString()));
- Log.d(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString()));
- Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
- Log.d(TAG, "mTime " + (mTime == null ? "null" : mTime.toString()));
- }
-
- long startMillis = 0;
- if (event.startTime != null) {
- startMillis = event.startTime.toMillis(false);
- }
-
- // Set mTime if selectedTime is set
- if (event.selectedTime != null && event.selectedTime.toMillis(false) != 0) {
- mTime.set(event.selectedTime);
- } else {
- if (startMillis != 0) {
- // selectedTime is not set so set mTime to startTime iff it is not
- // within start and end times
- long mtimeMillis = mTime.toMillis(false);
- if (mtimeMillis < startMillis
- || (event.endTime != null && mtimeMillis > event.endTime.toMillis(false))) {
- mTime.set(event.startTime);
- }
- }
- event.selectedTime = mTime;
- }
- // Store the formatting flags if this is an update to the title
- if (event.eventType == EventType.UPDATE_TITLE) {
- mDateFlags = event.extraLong;
- }
-
- // Fix up start time if not specified
- if (startMillis == 0) {
- event.startTime = mTime;
- }
- if (DEBUG) {
- Log.d(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString()));
- Log.d(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString()));
- Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
- Log.d(TAG, "mTime " + (mTime == null ? "null" : mTime.toString()));
- Log.d(TAG, "^^^^^^^^^^^^^^^");
- }
-
- // Store the eventId if we're entering edit event
- if ((event.eventType
- & (EventType.VIEW_EVENT_DETAILS))
- != 0) {
- if (event.id > 0) {
- mEventId = event.id;
- } else {
- mEventId = -1;
- }
- }
-
- boolean handled = false;
- synchronized (this) {
- mDispatchInProgressCounter ++;
-
- if (DEBUG) {
- Log.d(TAG, "sendEvent: Dispatching to " + eventHandlers.size() + " handlers");
- }
- // Dispatch to event handler(s)
- if (mFirstEventHandler != null) {
- // Handle the 'first' one before handling the others
- EventHandler handler = mFirstEventHandler.second;
- if (handler != null && (handler.getSupportedEventTypes() & event.eventType) != 0
- && !mToBeRemovedEventHandlers.contains(mFirstEventHandler.first)) {
- handler.handleEvent(event);
- handled = true;
- }
- }
- for (Iterator<Entry<Integer, EventHandler>> handlers =
- eventHandlers.entrySet().iterator(); handlers.hasNext();) {
- Entry<Integer, EventHandler> entry = handlers.next();
- int key = entry.getKey();
- if (mFirstEventHandler != null && key == mFirstEventHandler.first) {
- // If this was the 'first' handler it was already handled
- continue;
- }
- EventHandler eventHandler = entry.getValue();
- if (eventHandler != null
- && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) {
- if (mToBeRemovedEventHandlers.contains(key)) {
- continue;
- }
- eventHandler.handleEvent(event);
- handled = true;
- }
- }
-
- mDispatchInProgressCounter --;
-
- if (mDispatchInProgressCounter == 0) {
-
- // Deregister removed handlers
- if (mToBeRemovedEventHandlers.size() > 0) {
- for (Integer zombie : mToBeRemovedEventHandlers) {
- eventHandlers.remove(zombie);
- if (mFirstEventHandler != null && zombie.equals(mFirstEventHandler.first)) {
- mFirstEventHandler = null;
- }
- }
- mToBeRemovedEventHandlers.clear();
- }
- // Add new handlers
- if (mToBeAddedFirstEventHandler != null) {
- mFirstEventHandler = mToBeAddedFirstEventHandler;
- mToBeAddedFirstEventHandler = null;
- }
- if (mToBeAddedEventHandlers.size() > 0) {
- for (Entry<Integer, EventHandler> food : mToBeAddedEventHandlers.entrySet()) {
- eventHandlers.put(food.getKey(), food.getValue());
- }
- }
- }
- }
- }
-
- /**
- * Adds or updates an event handler. This uses a LinkedHashMap so that we can
- * replace fragments based on the view id they are being expanded into.
- *
- * @param key The view id or placeholder for this handler
- * @param eventHandler Typically a fragment or activity in the calendar app
- */
- public void registerEventHandler(int key, EventHandler eventHandler) {
- synchronized (this) {
- if (mDispatchInProgressCounter > 0) {
- mToBeAddedEventHandlers.put(key, eventHandler);
- } else {
- eventHandlers.put(key, eventHandler);
- }
- }
- }
-
- public void registerFirstEventHandler(int key, EventHandler eventHandler) {
- synchronized (this) {
- registerEventHandler(key, eventHandler);
- if (mDispatchInProgressCounter > 0) {
- mToBeAddedFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
- } else {
- mFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
- }
- }
- }
-
- public void deregisterEventHandler(Integer key) {
- synchronized (this) {
- if (mDispatchInProgressCounter > 0) {
- // To avoid ConcurrencyException, stash away the event handler for now.
- mToBeRemovedEventHandlers.add(key);
- } else {
- eventHandlers.remove(key);
- if (mFirstEventHandler != null && mFirstEventHandler.first == key) {
- mFirstEventHandler = null;
- }
- }
- }
- }
-
- public void deregisterAllEventHandlers() {
- synchronized (this) {
- if (mDispatchInProgressCounter > 0) {
- // To avoid ConcurrencyException, stash away the event handler for now.
- mToBeRemovedEventHandlers.addAll(eventHandlers.keySet());
- } else {
- eventHandlers.clear();
- mFirstEventHandler = null;
- }
- }
- }
-
- // FRAG_TODO doesn't work yet
- public void filterBroadcasts(Object sender, long eventTypes) {
- filters.put(sender, eventTypes);
- }
-
- /**
- * @return the time that this controller is currently pointed at
- */
- public long getTime() {
- return mTime.toMillis(false);
- }
-
- /**
- * @return the last set of date flags sent with
- * {@link EventType#UPDATE_TITLE}
- */
- public long getDateFlags() {
- return mDateFlags;
- }
-
- /**
- * Set the time this controller is currently pointed at
- *
- * @param millisTime Time since epoch in millis
- */
- public void setTime(long millisTime) {
- mTime.set(millisTime);
- }
-
- /**
- * @return the last event ID the edit view was launched with
- */
- public long getEventId() {
- return mEventId;
- }
-
- public int getViewType() {
- return mViewType;
- }
-
- public int getPreviousViewType() {
- return mPreviousViewType;
- }
-
- 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);
- intent.setData(eventUri);
- intent.setClass(mContext, AllInOneActivity.class);
- intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
- intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
- intent.putExtra(ATTENDEE_STATUS, response);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- // Forces the viewType. Should only be used for initialization.
- public void setViewType(int viewType) {
- mViewType = viewType;
- }
-
- // Sets the eventId. Should only be used for initialization.
- public void setEventId(long eventId) {
- mEventId = eventId;
- }
-
- private String eventInfoToString(EventInfo eventInfo) {
- String tmp = "Unknown";
-
- StringBuilder builder = new StringBuilder();
- if ((eventInfo.eventType & EventType.GO_TO) != 0) {
- tmp = "Go to time/event";
- } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) {
- tmp = "View event";
- } else if ((eventInfo.eventType & EventType.VIEW_EVENT_DETAILS) != 0) {
- tmp = "View details";
- } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) {
- tmp = "Refresh events";
- } else if ((eventInfo.eventType & EventType.USER_HOME) != 0) {
- tmp = "Gone home";
- } else if ((eventInfo.eventType & EventType.UPDATE_TITLE) != 0) {
- tmp = "Update title";
- }
- builder.append(tmp);
- builder.append(": id=");
- builder.append(eventInfo.id);
- builder.append(", selected=");
- builder.append(eventInfo.selectedTime);
- builder.append(", start=");
- builder.append(eventInfo.startTime);
- builder.append(", end=");
- builder.append(eventInfo.endTime);
- builder.append(", viewType=");
- builder.append(eventInfo.viewType);
- builder.append(", x=");
- builder.append(eventInfo.x);
- builder.append(", y=");
- builder.append(eventInfo.y);
- return builder.toString();
- }
-}
diff --git a/src/com/android/calendar/CalendarController.kt b/src/com/android/calendar/CalendarController.kt
new file mode 100644
index 00000000..16ee8fdd
--- /dev/null
+++ b/src/com/android/calendar/CalendarController.kt
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.content.ComponentName
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Events
+import android.text.format.Time
+import android.util.Log
+import android.util.Pair
+import java.lang.ref.WeakReference
+import java.util.LinkedHashMap
+import java.util.LinkedList
+import java.util.WeakHashMap
+
+class CalendarController private constructor(context: Context?) {
+ private var mContext: Context? = null
+
+ // This uses a LinkedHashMap so that we can replace fragments based on the
+ // view id they are being expanded into since we can't guarantee a reference
+ // to the handler will be findable
+ private val eventHandlers: LinkedHashMap<Int, EventHandler> =
+ LinkedHashMap<Int, EventHandler>(5)
+ private val mToBeRemovedEventHandlers: LinkedList<Int> = LinkedList<Int>()
+ private val mToBeAddedEventHandlers: LinkedHashMap<Int, EventHandler> =
+ LinkedHashMap<Int, EventHandler>()
+ private var mFirstEventHandler: Pair<Int, EventHandler>? = null
+ private var mToBeAddedFirstEventHandler: Pair<Int, EventHandler>? = null
+
+ @Volatile
+ private var mDispatchInProgressCounter = 0
+ private val filters: WeakHashMap<Object, Long> = WeakHashMap<Object, Long>(1)
+
+ // Forces the viewType. Should only be used for initialization.
+ var viewType = -1
+ private var mDetailViewType = -1
+ var previousViewType = -1
+ private set
+
+ // The last event ID the edit view was launched with
+ var eventId: Long = -1
+ private val mTime: Time? = Time()
+
+ // The last set of date flags sent with
+ var dateFlags: Long = 0
+ private set
+ private val mUpdateTimezone: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ mTime?.switchTimezone(Utils.getTimeZone(mContext, this))
+ }
+ }
+
+ /**
+ * One of the event types that are sent to or from the controller
+ */
+ interface EventType {
+ companion object {
+ // Simple view of an event
+ const val VIEW_EVENT = 1L shl 1
+
+ // Full detail view in read only mode
+ const val VIEW_EVENT_DETAILS = 1L shl 2
+
+ // full detail view in edit mode
+ const val EDIT_EVENT = 1L shl 3
+ const val GO_TO = 1L shl 5
+ const val EVENTS_CHANGED = 1L shl 7
+ const val USER_HOME = 1L shl 9
+
+ // date range has changed, update the title
+ const val UPDATE_TITLE = 1L shl 10
+ }
+ }
+
+ /**
+ * One of the Agenda/Day/Week/Month view types
+ */
+ interface ViewType {
+ companion object {
+ const val DETAIL = -1
+ const val CURRENT = 0
+ const val AGENDA = 1
+ const val DAY = 2
+ const val WEEK = 3
+ const val MONTH = 4
+ const val EDIT = 5
+ const val MAX_VALUE = 5
+ }
+ }
+
+ class EventInfo {
+ @JvmField var eventType: Long = 0 // one of the EventType
+ @JvmField var viewType = 0 // one of the ViewType
+ @JvmField var id: Long = 0 // event id
+ @JvmField var selectedTime: Time? = null // the selected time in focus
+
+ // Event start and end times. All-day events are represented in:
+ // - local time for GO_TO commands
+ // - UTC time for VIEW_EVENT and other event-related commands
+ @JvmField var startTime: Time? = null
+ @JvmField var endTime: Time? = null
+ @JvmField var x = 0 // x coordinate in the activity space
+ @JvmField var y = 0 // y coordinate in the activity space
+ @JvmField var query: String? = null // query for a user search
+ @JvmField var componentName: ComponentName? = null // used in combination with query
+ @JvmField var eventTitle: String? = null
+ @JvmField var calendarId: Long = 0
+
+ /**
+ * For EventType.VIEW_EVENT:
+ * It is the default attendee response and an all day event indicator.
+ * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+ * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE.
+ * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response.
+ * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay().
+ *
+ *
+ * For EventType.GO_TO:
+ * Set to [.EXTRA_GOTO_TIME] to go to the specified date/time.
+ * Set to [.EXTRA_GOTO_DATE] to consider the date but ignore the time.
+ * Set to [.EXTRA_GOTO_BACK_TO_PREVIOUS] if back should bring back previous view.
+ * Set to [.EXTRA_GOTO_TODAY] if this is a user request to go to the current time.
+ *
+ *
+ * For EventType.UPDATE_TITLE:
+ * Set formatting flags for Utils.formatDateRange
+ */
+ @JvmField var extraLong: Long = 0
+ val isAllDay: Boolean
+ get() {
+ if (eventType != EventType.VIEW_EVENT) {
+ Log.wtf(TAG, "illegal call to isAllDay , wrong event type $eventType")
+ return false
+ }
+ return if (extraLong and ALL_DAY_MASK != 0L) true else false
+ }
+ val response: Int
+ get() {
+ if (eventType != EventType.VIEW_EVENT) {
+ Log.wtf(TAG, "illegal call to getResponse , wrong event type $eventType")
+ return Attendees.ATTENDEE_STATUS_NONE
+ }
+ val response = (extraLong and ATTENTEE_STATUS_MASK).toInt()
+ when (response) {
+ ATTENDEE_STATUS_NONE_MASK -> return Attendees.ATTENDEE_STATUS_NONE
+ ATTENDEE_STATUS_ACCEPTED_MASK -> return Attendees.ATTENDEE_STATUS_ACCEPTED
+ ATTENDEE_STATUS_DECLINED_MASK -> return Attendees.ATTENDEE_STATUS_DECLINED
+ ATTENDEE_STATUS_TENTATIVE_MASK -> return Attendees.ATTENDEE_STATUS_TENTATIVE
+ else -> Log.wtf(TAG, "Unknown attendee response $response")
+ }
+ return ATTENDEE_STATUS_NONE_MASK
+ }
+
+ companion object {
+ private const val ATTENTEE_STATUS_MASK: Long = 0xFF
+ private const val ALL_DAY_MASK: Long = 0x100
+ private const val ATTENDEE_STATUS_NONE_MASK = 0x01
+ private const val ATTENDEE_STATUS_ACCEPTED_MASK = 0x02
+ private const val ATTENDEE_STATUS_DECLINED_MASK = 0x04
+ private const val ATTENDEE_STATUS_TENTATIVE_MASK = 0x08
+
+ // Used to build the extra long for a VIEW event.
+ @JvmStatic fun buildViewExtraLong(response: Int, allDay: Boolean): Long {
+ var extra = if (allDay) ALL_DAY_MASK else 0
+ extra = when (response) {
+ Attendees.ATTENDEE_STATUS_NONE -> extra or
+ ATTENDEE_STATUS_NONE_MASK.toLong()
+ Attendees.ATTENDEE_STATUS_ACCEPTED -> extra or
+ ATTENDEE_STATUS_ACCEPTED_MASK.toLong()
+ Attendees.ATTENDEE_STATUS_DECLINED -> extra or
+ ATTENDEE_STATUS_DECLINED_MASK.toLong()
+ Attendees.ATTENDEE_STATUS_TENTATIVE -> extra or
+ ATTENDEE_STATUS_TENTATIVE_MASK.toLong()
+ else -> {
+ Log.wtf(
+ TAG,
+ "Unknown attendee response $response"
+ )
+ extra or ATTENDEE_STATUS_NONE_MASK.toLong()
+ }
+ }
+ return extra
+ }
+ }
+ }
+
+ interface EventHandler {
+ val supportedEventTypes: Long
+ fun handleEvent(event: EventInfo?)
+
+ /**
+ * This notifies the handler that the database has changed and it should
+ * update its view.
+ */
+ fun eventsChanged()
+ }
+
+ fun sendEventRelatedEvent(
+ sender: Object?,
+ eventType: Long,
+ eventId: Long,
+ startMillis: Long,
+ endMillis: Long,
+ x: Int,
+ y: Int,
+ selectedMillis: Long
+ ) {
+ // TODO: pass the real allDay status or at least a status that says we don't know the
+ // status and have the receiver query the data.
+ // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo
+ // so currently the missing allDay status has no effect.
+ sendEventRelatedEventWithExtra(
+ sender, eventType, eventId, startMillis, endMillis, x, y,
+ EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false),
+ selectedMillis
+ )
+ }
+
+ /**
+ * Helper for sending New/View/Edit/Delete events
+ *
+ * @param sender object of the caller
+ * @param eventType one of [EventType]
+ * @param eventId event id
+ * @param startMillis start time
+ * @param endMillis end time
+ * @param x x coordinate in the activity space
+ * @param y y coordinate in the activity space
+ * @param extraLong default response value for the "simple event view" and all day indication.
+ * Use Attendees.ATTENDEE_STATUS_NONE for no response.
+ * @param selectedMillis The time to specify as selected
+ */
+ fun sendEventRelatedEventWithExtra(
+ sender: Object?,
+ eventType: Long,
+ eventId: Long,
+ startMillis: Long,
+ endMillis: Long,
+ x: Int,
+ y: Int,
+ extraLong: Long,
+ selectedMillis: Long
+ ) {
+ sendEventRelatedEventWithExtraWithTitleWithCalendarId(
+ sender, eventType, eventId,
+ startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1
+ )
+ }
+
+ /**
+ * Helper for sending New/View/Edit/Delete events
+ *
+ * @param sender object of the caller
+ * @param eventType one of [EventType]
+ * @param eventId event id
+ * @param startMillis start time
+ * @param endMillis end time
+ * @param x x coordinate in the activity space
+ * @param y y coordinate in the activity space
+ * @param extraLong default response value for the "simple event view" and all day indication.
+ * Use Attendees.ATTENDEE_STATUS_NONE for no response.
+ * @param selectedMillis The time to specify as selected
+ * @param title The title of the event
+ * @param calendarId The id of the calendar which the event belongs to
+ */
+ fun sendEventRelatedEventWithExtraWithTitleWithCalendarId(
+ sender: Object?,
+ eventType: Long,
+ eventId: Long,
+ startMillis: Long,
+ endMillis: Long,
+ x: Int,
+ y: Int,
+ extraLong: Long,
+ selectedMillis: Long,
+ title: String?,
+ calendarId: Long
+ ) {
+ val info = EventInfo()
+ info.eventType = eventType
+ if (eventType == EventType.VIEW_EVENT_DETAILS) {
+ info.viewType = ViewType.CURRENT
+ }
+ info.id = eventId
+ info.startTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+ (info.startTime as Time).set(startMillis)
+ if (selectedMillis != -1L) {
+ info.selectedTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+ (info.selectedTime as Time).set(selectedMillis)
+ } else {
+ info.selectedTime = info.startTime
+ }
+ info.endTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+ (info.endTime as Time).set(endMillis)
+ info.x = x
+ info.y = y
+ info.extraLong = extraLong
+ info.eventTitle = title
+ info.calendarId = calendarId
+ this.sendEvent(sender, info)
+ }
+
+ /**
+ * Helper for sending non-calendar-event events
+ *
+ * @param sender object of the caller
+ * @param eventType one of [EventType]
+ * @param start start time
+ * @param end end time
+ * @param eventId event id
+ * @param viewType [ViewType]
+ */
+ fun sendEvent(
+ sender: Object?,
+ eventType: Long,
+ start: Time?,
+ end: Time?,
+ eventId: Long,
+ viewType: Int
+ ) {
+ sendEvent(
+ sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null,
+ null
+ )
+ }
+
+ /**
+ * sendEvent() variant with extraLong, search query, and search component name.
+ */
+ fun sendEvent(
+ sender: Object?,
+ eventType: Long,
+ start: Time?,
+ end: Time?,
+ eventId: Long,
+ viewType: Int,
+ extraLong: Long,
+ query: String?,
+ componentName: ComponentName?
+ ) {
+ sendEvent(
+ sender, eventType, start, end, start, eventId, viewType, extraLong, query,
+ componentName
+ )
+ }
+
+ fun sendEvent(
+ sender: Object?,
+ eventType: Long,
+ start: Time?,
+ end: Time?,
+ selected: Time?,
+ eventId: Long,
+ viewType: Int,
+ extraLong: Long,
+ query: String?,
+ componentName: ComponentName?
+ ) {
+ val info = EventInfo()
+ info.eventType = eventType
+ info.startTime = start
+ info.selectedTime = selected
+ info.endTime = end
+ info.id = eventId
+ info.viewType = viewType
+ info.query = query
+ info.componentName = componentName
+ info.extraLong = extraLong
+ this.sendEvent(sender, info)
+ }
+
+ fun sendEvent(sender: Object?, event: EventInfo) {
+ // TODO Throw exception on invalid events
+ if (DEBUG) {
+ Log.d(TAG, eventInfoToString(event))
+ }
+ val filteredTypes: Long? = filters.get(sender)
+ if (filteredTypes != null && filteredTypes.toLong() and event.eventType != 0L) {
+ // Suppress event per filter
+ if (DEBUG) {
+ Log.d(TAG, "Event suppressed")
+ }
+ return
+ }
+ previousViewType = viewType
+
+ // Fix up view if not specified
+ if (event.viewType == ViewType.DETAIL) {
+ event.viewType = mDetailViewType
+ viewType = mDetailViewType
+ } else if (event.viewType == ViewType.CURRENT) {
+ event.viewType = viewType
+ } else if (event.viewType != ViewType.EDIT) {
+ viewType = event.viewType
+ if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY ||
+ Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK) {
+ mDetailViewType = viewType
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "vvvvvvvvvvvvvvv")
+ Log.d(
+ TAG,
+ "Start " + if (event.startTime == null) "null" else event.startTime.toString()
+ )
+ Log.d(TAG, "End " + if (event.endTime == null) "null" else event.endTime.toString())
+ Log.d(
+ TAG,
+ "Select " + if (event.selectedTime == null) "null"
+ else event.selectedTime.toString()
+ )
+ Log.d(TAG, "mTime " + if (mTime == null) "null" else mTime.toString())
+ }
+ var startMillis: Long = 0
+ val temp = event.startTime
+ if (temp != null) {
+ startMillis = (event.startTime as Time).toMillis(false)
+ }
+
+ // Set mTime if selectedTime is set
+ val temp1 = event.selectedTime
+ if (temp1 != null && temp1?.toMillis(false) != 0L) {
+ mTime?.set(event.selectedTime)
+ } else {
+ if (startMillis != 0L) {
+ // selectedTime is not set so set mTime to startTime iff it is not
+ // within start and end times
+ val mtimeMillis: Long = mTime?.toMillis(false) as Long
+ val temp2 = event.endTime
+ if (mtimeMillis < startMillis ||
+ temp2 != null && mtimeMillis > temp2.toMillis(false)) {
+ mTime?.set(event.startTime)
+ }
+ }
+ event.selectedTime = mTime
+ }
+ // Store the formatting flags if this is an update to the title
+ if (event.eventType == EventType.UPDATE_TITLE) {
+ dateFlags = event.extraLong
+ }
+
+ // Fix up start time if not specified
+ if (startMillis == 0L) {
+ event.startTime = mTime
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Start " + if (event.startTime == null) "null" else
+ event.startTime.toString()
+ )
+ Log.d(TAG, "End " + if (event.endTime == null) "null" else
+ event.endTime.toString())
+ Log.d(
+ TAG,
+ "Select " + if (event.selectedTime == null) "null" else
+ event.selectedTime.toString()
+ )
+ Log.d(TAG, "mTime " + if (mTime == null) "null" else mTime.toString())
+ Log.d(TAG, "^^^^^^^^^^^^^^^")
+ }
+
+ // Store the eventId if we're entering edit event
+ if ((event.eventType and EventType.VIEW_EVENT_DETAILS) != 0L) {
+ if (event.id > 0) {
+ eventId = event.id
+ } else {
+ eventId = -1
+ }
+ }
+ var handled = false
+ synchronized(this) {
+ mDispatchInProgressCounter++
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "sendEvent: Dispatching to " + eventHandlers.size.toString() + " handlers"
+ )
+ }
+ // Dispatch to event handler(s)
+ val temp3 = mFirstEventHandler
+ if (temp3 != null) {
+ // Handle the 'first' one before handling the others
+ val handler: EventHandler? = mFirstEventHandler?.second
+ if (handler != null && handler.supportedEventTypes and event.eventType != 0L &&
+ !mToBeRemovedEventHandlers.contains(mFirstEventHandler?.first)) {
+ handler.handleEvent(event)
+ handled = true
+ }
+ }
+ val handlers: MutableIterator<MutableMap.MutableEntry<Int,
+ CalendarController.EventHandler>> = eventHandlers.entries.iterator()
+ while (handlers.hasNext()) {
+ val entry: MutableMap.MutableEntry<Int,
+ CalendarController.EventHandler> = handlers.next()
+ val key: Int = entry.key.toInt()
+ val temp4 = mFirstEventHandler
+ if (temp4 != null && key.toInt() == temp4.first.toInt()) {
+ // If this was the 'first' handler it was already handled
+ continue
+ }
+ val eventHandler: EventHandler = entry.value
+ if (eventHandler != null &&
+ eventHandler.supportedEventTypes and event.eventType != 0L) {
+ if (mToBeRemovedEventHandlers.contains(key)) {
+ continue
+ }
+ eventHandler.handleEvent(event)
+ handled = true
+ }
+ }
+ mDispatchInProgressCounter--
+ if (mDispatchInProgressCounter == 0) {
+
+ // Deregister removed handlers
+ if (mToBeRemovedEventHandlers.size > 0) {
+ for (zombie in mToBeRemovedEventHandlers) {
+ eventHandlers.remove(zombie)
+ val temp5 = mFirstEventHandler
+ if (temp5 != null && zombie.equals(temp5.first)) {
+ mFirstEventHandler = null
+ }
+ }
+ mToBeRemovedEventHandlers.clear()
+ }
+ // Add new handlers
+ if (mToBeAddedFirstEventHandler != null) {
+ mFirstEventHandler = mToBeAddedFirstEventHandler
+ mToBeAddedFirstEventHandler = null
+ }
+ if (mToBeAddedEventHandlers.size > 0) {
+ for (food in mToBeAddedEventHandlers.entries) {
+ eventHandlers.put(food.key, food.value)
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds or updates an event handler. This uses a LinkedHashMap so that we can
+ * replace fragments based on the view id they are being expanded into.
+ *
+ * @param key The view id or placeholder for this handler
+ * @param eventHandler Typically a fragment or activity in the calendar app
+ */
+ fun registerEventHandler(key: Int, eventHandler: EventHandler?) {
+ synchronized(this) {
+ if (mDispatchInProgressCounter > 0) {
+ mToBeAddedEventHandlers.put(key,
+ eventHandler as CalendarController.EventHandler)
+ } else {
+ eventHandlers.put(key, eventHandler as CalendarController.EventHandler)
+ }
+ }
+ }
+
+ fun registerFirstEventHandler(key: Int, eventHandler: EventHandler?) {
+ synchronized(this) {
+ registerEventHandler(key, eventHandler)
+ if (mDispatchInProgressCounter > 0) {
+ mToBeAddedFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler)
+ } else {
+ mFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler)
+ }
+ }
+ }
+
+ fun deregisterEventHandler(key: Int) {
+ synchronized(this) {
+ if (mDispatchInProgressCounter > 0) {
+ // To avoid ConcurrencyException, stash away the event handler for now.
+ mToBeRemovedEventHandlers.add(key)
+ } else {
+ eventHandlers.remove(key)
+ val temp6 = mFirstEventHandler
+ if (temp6 != null && temp6.first == key) {
+ mFirstEventHandler = null
+ } else {}
+ }
+ }
+ }
+
+ fun deregisterAllEventHandlers() {
+ synchronized(this) {
+ if (mDispatchInProgressCounter > 0) {
+ // To avoid ConcurrencyException, stash away the event handler for now.
+ mToBeRemovedEventHandlers.addAll(eventHandlers.keys)
+ } else {
+ eventHandlers.clear()
+ mFirstEventHandler = null
+ }
+ }
+ }
+
+ // FRAG_TODO doesn't work yet
+ fun filterBroadcasts(sender: Object?, eventTypes: Long) {
+ filters.put(sender, eventTypes)
+ }
+ /**
+ * @return the time that this controller is currently pointed at
+ */
+ /**
+ * Set the time this controller is currently pointed at
+ *
+ * @param millisTime Time since epoch in millis
+ */
+ var time: Long?
+ get() = mTime?.toMillis(false)
+ set(millisTime) {
+ mTime?.set(millisTime as Long)
+ }
+
+ fun launchViewEvent(eventId: Long, startMillis: Long, endMillis: Long, response: Int) {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val eventUri: Uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId)
+ intent.setData(eventUri)
+ intent.setClass(mContext as Context, AllInOneActivity::class.java)
+ intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis)
+ intent.putExtra(EXTRA_EVENT_END_TIME, endMillis)
+ intent.putExtra(ATTENDEE_STATUS, response)
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ mContext?.startActivity(intent)
+ }
+
+ private fun eventInfoToString(eventInfo: EventInfo): String {
+ var tmp = "Unknown"
+ val builder = StringBuilder()
+ if (eventInfo.eventType and EventType.GO_TO != 0L) {
+ tmp = "Go to time/event"
+ } else if (eventInfo.eventType and EventType.VIEW_EVENT != 0L) {
+ tmp = "View event"
+ } else if (eventInfo.eventType and EventType.VIEW_EVENT_DETAILS != 0L) {
+ tmp = "View details"
+ } else if (eventInfo.eventType and EventType.EVENTS_CHANGED != 0L) {
+ tmp = "Refresh events"
+ } else if (eventInfo.eventType and EventType.USER_HOME != 0L) {
+ tmp = "Gone home"
+ } else if (eventInfo.eventType and EventType.UPDATE_TITLE != 0L) {
+ tmp = "Update title"
+ }
+ builder.append(tmp)
+ builder.append(": id=")
+ builder.append(eventInfo.id)
+ builder.append(", selected=")
+ builder.append(eventInfo.selectedTime)
+ builder.append(", start=")
+ builder.append(eventInfo.startTime)
+ builder.append(", end=")
+ builder.append(eventInfo.endTime)
+ builder.append(", viewType=")
+ builder.append(eventInfo.viewType)
+ builder.append(", x=")
+ builder.append(eventInfo.x)
+ builder.append(", y=")
+ builder.append(eventInfo.y)
+ return builder.toString()
+ }
+
+ companion object {
+ private const val DEBUG = false
+ private const val TAG = "CalendarController"
+ const val EVENT_EDIT_ON_LAUNCH = "editMode"
+ const val MIN_CALENDAR_YEAR = 1970
+ const val MAX_CALENDAR_YEAR = 2036
+ const val MIN_CALENDAR_WEEK = 0
+ const val MAX_CALENDAR_WEEK = 3497 // weeks between 1/1/1970 and 1/1/2037
+ private val instances: WeakHashMap<Context, WeakReference<CalendarController>> =
+ WeakHashMap<Context, WeakReference<CalendarController>>()
+
+ /**
+ * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time
+ * can be ignored
+ */
+ const val EXTRA_GOTO_DATE: Long = 1
+ const val EXTRA_GOTO_TIME: Long = 2
+ const val EXTRA_GOTO_BACK_TO_PREVIOUS: Long = 4
+ const val EXTRA_GOTO_TODAY: Long = 8
+
+ /**
+ * Creates and/or returns an instance of CalendarController associated with
+ * the supplied context. It is best to pass in the current Activity.
+ *
+ * @param context The activity if at all possible.
+ */
+ @JvmStatic fun getInstance(context: Context?): CalendarController? {
+ synchronized(instances) {
+ var controller: CalendarController? = null
+ val weakController: WeakReference<CalendarController>? = instances.get(context)
+ if (weakController != null) {
+ controller = weakController.get()
+ }
+ if (controller == null) {
+ controller = CalendarController(context)
+ instances.put(context, WeakReference(controller))
+ }
+ return controller
+ }
+ }
+
+ /**
+ * Removes an instance when it is no longer needed. This should be called in
+ * an activity's onDestroy method.
+ *
+ * @param context The activity used to create the controller
+ */
+ @JvmStatic fun removeInstance(context: Context?) {
+ instances.remove(context)
+ }
+ }
+
+ init {
+ mContext = context
+ mUpdateTimezone.run()
+ mTime?.setToNow()
+ mDetailViewType = Utils.getSharedPreference(
+ mContext,
+ GeneralPreferences.KEY_DETAILED_VIEW,
+ GeneralPreferences.DEFAULT_DETAILED_VIEW
+ )
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarData.java b/src/com/android/calendar/CalendarData.kt
index 5c8456fa..7370f2e2 100644
--- a/src/com/android/calendar/CalendarData.java
+++ b/src/com/android/calendar/CalendarData.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.calendar
-package com.android.calendar;
+object CalendarData {
+ @JvmField
+ val s12HoursNoAmPm = arrayOf("12", "1", "2", "3", "4",
+ "5", "6", "7", "8", "9", "10", "11", "12",
+ "1", "2", "3", "4", "5", "6", "7", "8",
+ "9", "10", "11", "12")
-public final class CalendarData {
- static final String[] s12HoursNoAmPm = { "12", "1", "2", "3", "4",
- "5", "6", "7", "8", "9", "10", "11", "12",
- "1", "2", "3", "4", "5", "6", "7", "8",
- "9", "10", "11", "12" };
-
- static final String[] s24Hours = { "00", "01", "02", "03", "04", "05",
- "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16",
- "17", "18", "19", "20", "21", "22", "23", "00" };
-}
+ @JvmField
+ val s24Hours = arrayOf("00", "01", "02", "03", "04", "05",
+ "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16",
+ "17", "18", "19", "20", "21", "22", "23", "00")
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarUtils.java b/src/com/android/calendar/CalendarUtils.java
deleted file mode 100644
index 0238c321..00000000
--- a/src/com/android/calendar/CalendarUtils.java
+++ /dev/null
@@ -1,356 +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.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.Looper;
-import android.provider.CalendarContract.CalendarCache;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import java.util.Formatter;
-import java.util.HashSet;
-import java.util.Locale;
-
-/**
- * A class containing utility methods related to Calendar apps.
- *
- * This class is expected to move into the app framework eventually.
- */
-public class CalendarUtils {
- private static final boolean DEBUG = false;
- private static final String TAG = "CalendarUtils";
-
- /**
- * This class contains methods specific to reading and writing time zone
- * values.
- */
- public static class TimeZoneUtils {
- private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.KEY_TIMEZONE_TYPE };
- private static final String[] TIMEZONE_INSTANCES_ARGS =
- { CalendarCache.KEY_TIMEZONE_INSTANCES };
- public static final String[] CALENDAR_CACHE_POJECTION = {
- CalendarCache.KEY, CalendarCache.VALUE
- };
-
- private static StringBuilder mSB = new StringBuilder(50);
- private static Formatter mF = new Formatter(mSB, Locale.getDefault());
- private volatile static boolean mFirstTZRequest = true;
- private volatile static boolean mTZQueryInProgress = false;
-
- private volatile static boolean mUseHomeTZ = false;
- private volatile static String mHomeTZ = Time.getCurrentTimezone();
-
- private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
- private static int mToken = 1;
- private static AsyncTZHandler mHandler;
-
- // The name of the shared preferences file. This name must be maintained for historical
- // reasons, as it's what PreferenceManager assigned the first time the file was created.
- private final String mPrefsName;
-
- /**
- * This is the key used for writing whether or not a home time zone should
- * be used in the Calendar app to the Calendar Preferences.
- */
- public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
- /**
- * This is the key used for writing the time zone that should be used if
- * home time zones are enabled for the Calendar app.
- */
- public static final String KEY_HOME_TZ = "preferences_home_tz";
-
- /**
- * This is a helper class for handling the async queries and updates for the
- * time zone settings in Calendar.
- */
- private class AsyncTZHandler extends AsyncQueryHandler {
- public AsyncTZHandler(ContentResolver cr) {
- super(cr);
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- synchronized (mTZCallbacks) {
- if (cursor == null) {
- mTZQueryInProgress = false;
- mFirstTZRequest = true;
- return;
- }
-
- boolean writePrefs = false;
- // Check the values in the db
- int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
- int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
- while(cursor.moveToNext()) {
- String key = cursor.getString(keyColumn);
- String value = cursor.getString(valueColumn);
- if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
- boolean useHomeTZ = !TextUtils.equals(
- value, CalendarCache.TIMEZONE_TYPE_AUTO);
- if (useHomeTZ != mUseHomeTZ) {
- writePrefs = true;
- mUseHomeTZ = useHomeTZ;
- }
- } else if (TextUtils.equals(
- key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
- if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
- writePrefs = true;
- mHomeTZ = value;
- }
- }
- }
- cursor.close();
- if (writePrefs) {
- SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
- // Write the prefs
- setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
- setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
- }
-
- mTZQueryInProgress = false;
- for (Runnable callback : mTZCallbacks) {
- if (callback != null) {
- callback.run();
- }
- }
- mTZCallbacks.clear();
- }
- }
- }
-
- /**
- * The name of the file where the shared prefs for Calendar are stored
- * must be provided. All activities within an app should provide the
- * same preferences name or behavior may become erratic.
- *
- * @param prefsName
- */
- public TimeZoneUtils(String prefsName) {
- mPrefsName = prefsName;
- }
-
- /**
- * Formats a date or a time range according to the local conventions.
- *
- * This formats a date/time range using Calendar's time zone and the
- * local conventions for the region of the device.
- *
- * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
- * the UTC time zone instead.
- *
- * @param context the context is required only if the time is shown
- * @param startMillis the start time in UTC milliseconds
- * @param endMillis the end time in UTC milliseconds
- * @param flags a bit mask of options See
- * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
- * @return a string containing the formatted date/time range.
- */
- public String formatDateRange(Context context, long startMillis,
- long endMillis, int flags) {
- String date;
- String tz;
- if ((flags & DateUtils.FORMAT_UTC) != 0) {
- tz = Time.TIMEZONE_UTC;
- } else {
- tz = getTimeZone(context, null);
- }
- synchronized (mSB) {
- mSB.setLength(0);
- date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
- tz).toString();
- }
- return date;
- }
-
- /**
- * 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
- * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
- * to the device's time zone. null or empty tz will be ignored.
- *
- * @param context The calling activity
- * @param timeZone The time zone to set Calendar to, or
- * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
- */
- public void setTimeZone(Context context, String timeZone) {
- if (TextUtils.isEmpty(timeZone)) {
- if (DEBUG) {
- Log.d(TAG, "Empty time zone, nothing to be done.");
- }
- return;
- }
- boolean updatePrefs = false;
- synchronized (mTZCallbacks) {
- if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
- if (mUseHomeTZ) {
- updatePrefs = true;
- }
- mUseHomeTZ = false;
- } else {
- if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
- updatePrefs = true;
- }
- mUseHomeTZ = true;
- mHomeTZ = timeZone;
- }
- }
- if (updatePrefs) {
- // Write the prefs
- SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
- setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
- setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
-
- // Update the db
- ContentValues values = new ContentValues();
- if (mHandler != null) {
- mHandler.cancelOperation(mToken);
- }
-
- mHandler = new AsyncTZHandler(context.getContentResolver());
-
- // skip 0 so query can use it
- if (++mToken == 0) {
- mToken = 1;
- }
-
- // Write the use home tz setting
- values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
- : CalendarCache.TIMEZONE_TYPE_AUTO);
- mHandler.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
- TIMEZONE_TYPE_ARGS);
-
- // If using a home tz write it to the db
- if (mUseHomeTZ) {
- ContentValues values2 = new ContentValues();
- values2.put(CalendarCache.VALUE, mHomeTZ);
- mHandler.startUpdate(mToken, null, CalendarCache.URI, values2,
- "key=?", TIMEZONE_INSTANCES_ARGS);
- }
- }
- }
-
- /**
- * Gets the time zone that Calendar should be displayed in
- *
- * This is a helper method to get the appropriate time zone for Calendar. If this
- * is the first time this method has been called it will initiate an asynchronous
- * query to verify that the data in preferences is correct. The callback supplied
- * will only be called if this query returns a value other than what is stored in
- * preferences and should cause the calling activity to refresh anything that
- * depends on calling this method.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns new values
- * @return The string value representing the time zone Calendar should display
- */
- public String getTimeZone(Context context, Runnable callback) {
- synchronized (mTZCallbacks){
- if (mFirstTZRequest) {
- SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
- mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
- mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
-
- // Only check content resolver if we have a looper to attach to use
- if (Looper.myLooper() != null) {
- mTZQueryInProgress = true;
- mFirstTZRequest = false;
-
- // When the async query returns it should synchronize on
- // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
- // preferences, set mTZQueryInProgress to false, and call all
- // the runnables in mTZCallbacks.
- if (mHandler == null) {
- mHandler = new AsyncTZHandler(context.getContentResolver());
- }
- mHandler.startQuery(0, context, CalendarCache.URI, CALENDAR_CACHE_POJECTION,
- null, null, null);
- }
- }
- if (mTZQueryInProgress) {
- mTZCallbacks.add(callback);
- }
- }
- return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
- }
-
- /**
- * Forces a query of the database to check for changes to the time zone.
- * This should be called if another app may have modified the db. If a
- * query is already in progress the callback will be added to the list
- * of callbacks to be called when it returns.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns
- * new values
- */
- public void forceDBRequery(Context context, Runnable callback) {
- synchronized (mTZCallbacks){
- if (mTZQueryInProgress) {
- mTZCallbacks.add(callback);
- return;
- }
- mFirstTZRequest = true;
- getTimeZone(context, callback);
- }
- }
- }
-
- /**
- * A helper method for writing a String value to the preferences
- * asynchronously.
- *
- * @param context A context with access to the correct preferences
- * @param key The preference to write to
- * @param value The value to write
- */
- public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
-// SharedPreferences prefs = getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(key, value);
- editor.apply();
- }
-
- /**
- * A helper method for writing a boolean value to the preferences
- * asynchronously.
- *
- * @param context A context with access to the correct preferences
- * @param key The preference to write to
- * @param value The value to write
- */
- public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
-// SharedPreferences prefs = getSharedPreferences(context, prefsName);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(key, value);
- editor.apply();
- }
-
- /** Return a properly configured SharedPreferences instance */
- public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
- return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
- }
-}
diff --git a/src/com/android/calendar/CalendarUtils.kt b/src/com/android/calendar/CalendarUtils.kt
new file mode 100644
index 00000000..94ca7234
--- /dev/null
+++ b/src/com/android/calendar/CalendarUtils.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.AsyncQueryHandler
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.SharedPreferences
+import android.database.Cursor
+import android.os.Looper
+import android.provider.CalendarContract.CalendarCache
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+
+import java.util.Formatter
+import java.util.HashSet
+import java.util.Locale
+
+/**
+ * A class containing utility methods related to Calendar apps.
+ *
+ * This class is expected to move into the app framework eventually.
+ */
+class CalendarUtils {
+
+ companion object {
+ private const val DEBUG = false
+ private const val TAG = "CalendarUtils"
+
+ /**
+ * A helper method for writing a boolean value to the preferences
+ * asynchronously.
+ *
+ * @param context A context with access to the correct preferences
+ * @param key The preference to write to
+ * @param value The value to write
+ */
+ @JvmStatic
+ fun setSharedPreference(prefs: SharedPreferences, key: String?, value: Boolean) {
+ val editor: SharedPreferences.Editor = prefs.edit()
+ editor.putBoolean(key, value)
+ editor.apply()
+ }
+
+ /** Return a properly configured SharedPreferences instance */
+ @JvmStatic
+ fun getSharedPreferences(context: Context, prefsName: String?): SharedPreferences {
+ return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
+ }
+
+ /**
+ * A helper method for writing a String value to the preferences
+ * asynchronously.
+ *
+ * @param context A context with access to the correct preferences
+ * @param key The preference to write to
+ * @param value The value to write
+ */
+ @JvmStatic
+ fun setSharedPreference(prefs: SharedPreferences, key: String?, value: String?) {
+ val editor: SharedPreferences.Editor = prefs.edit()
+ editor.putString(key, value)
+ editor.apply()
+ }
+ }
+
+ /**
+ * This class contains methods specific to reading and writing time zone
+ * values.
+ */
+ class TimeZoneUtils
+ /**
+ * The name of the file where the shared prefs for Calendar are stored
+ * must be provided. All activities within an app should provide the
+ * same preferences name or behavior may become erratic.
+ *
+ * @param prefsName
+ */( // The name of the shared preferences file. This name must be maintained for historical
+ // reasons, as it's what PreferenceManager assigned the first time the file was created.
+ private val mPrefsName: String) {
+ /**
+ * This is a helper class for handling the async queries and updates for the
+ * time zone settings in Calendar.
+ */
+ private inner class AsyncTZHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) {
+ protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) {
+ synchronized(mTZCallbacks) {
+ if (cursor == null) {
+ mTZQueryInProgress = false
+ mFirstTZRequest = true
+ return
+ }
+ var writePrefs = false
+ // Check the values in the db
+ val keyColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.KEY)
+ val valueColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.VALUE)
+ while (cursor.moveToNext()) {
+ val key: String = cursor.getString(keyColumn)
+ val value: String = cursor.getString(valueColumn)
+ if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
+ val useHomeTZ: Boolean = !TextUtils.equals(
+ value, CalendarCache.TIMEZONE_TYPE_AUTO)
+ if (useHomeTZ != mUseHomeTZ) {
+ writePrefs = true
+ mUseHomeTZ = useHomeTZ
+ }
+ } else if (TextUtils.equals(
+ key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
+ if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
+ writePrefs = true
+ mHomeTZ = value
+ }
+ }
+ }
+ cursor.close()
+ if (writePrefs) {
+ val prefs: SharedPreferences =
+ getSharedPreferences(cookie as Context, mPrefsName)
+ // Write the prefs
+ setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ)
+ setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ)
+ }
+ mTZQueryInProgress = false
+ for (callback in mTZCallbacks) {
+ if (callback != null) {
+ callback.run()
+ }
+ }
+ mTZCallbacks.clear()
+ }
+ }
+ }
+
+ /**
+ * Formats a date or a time range according to the local conventions.
+ *
+ * This formats a date/time range using Calendar's time zone and the
+ * local conventions for the region of the device.
+ *
+ * If the [DateUtils.FORMAT_UTC] flag is used it will pass in
+ * the UTC time zone instead.
+ *
+ * @param context the context is required only if the time is shown
+ * @param startMillis the start time in UTC milliseconds
+ * @param endMillis the end time in UTC milliseconds
+ * @param flags a bit mask of options See
+ * [formatDateRange][DateUtils.formatDateRange]
+ * @return a string containing the formatted date/time range.
+ */
+ fun formatDateRange(context: Context, startMillis: Long,
+ endMillis: Long, flags: Int): String {
+ var date: String
+ val tz: String
+ tz = if (flags and DateUtils.FORMAT_UTC !== 0) {
+ Time.TIMEZONE_UTC
+ } else {
+ getTimeZone(context, null)
+ }
+ synchronized(mSB) {
+ mSB.setLength(0)
+ date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
+ tz).toString()
+ }
+ return date
+ }
+
+ /**
+ * 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
+ * [CalendarCache.TIMEZONE_TYPE_AUTO] will cause it to be set
+ * to the device's time zone. null or empty tz will be ignored.
+ *
+ * @param context The calling activity
+ * @param timeZone The time zone to set Calendar to, or
+ * [CalendarCache.TIMEZONE_TYPE_AUTO]
+ */
+ fun setTimeZone(context: Context, timeZone: String) {
+ if (TextUtils.isEmpty(timeZone)) {
+ if (DEBUG) {
+ Log.d(TAG, "Empty time zone, nothing to be done.")
+ }
+ return
+ }
+ var updatePrefs = false
+ synchronized(mTZCallbacks) {
+ if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
+ if (mUseHomeTZ) {
+ updatePrefs = true
+ }
+ mUseHomeTZ = false
+ } else {
+ if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
+ updatePrefs = true
+ }
+ mUseHomeTZ = true
+ mHomeTZ = timeZone
+ }
+ }
+ if (updatePrefs) {
+ // Write the prefs
+ val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName)
+ setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ)
+ setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ)
+
+ // Update the db
+ val values = ContentValues()
+ if (mHandler != null) {
+ mHandler?.cancelOperation(mToken)
+ }
+ mHandler = AsyncTZHandler(context.getContentResolver())
+
+ // skip 0 so query can use it
+ if (++mToken == 0) {
+ mToken = 1
+ }
+
+ // Write the use home tz setting
+ values.put(CalendarCache.VALUE, if (mUseHomeTZ) CalendarCache.TIMEZONE_TYPE_HOME
+ else CalendarCache.TIMEZONE_TYPE_AUTO)
+ mHandler?.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
+ TIMEZONE_TYPE_ARGS)
+
+ // If using a home tz write it to the db
+ if (mUseHomeTZ) {
+ val values2 = ContentValues()
+ values2.put(CalendarCache.VALUE, mHomeTZ)
+ mHandler?.startUpdate(mToken, null, CalendarCache.URI, values2,
+ "key=?", TIMEZONE_INSTANCES_ARGS)
+ }
+ }
+ }
+
+ /**
+ * Gets the time zone that Calendar should be displayed in
+ *
+ * This is a helper method to get the appropriate time zone for Calendar. If this
+ * is the first time this method has been called it will initiate an asynchronous
+ * query to verify that the data in preferences is correct. The callback supplied
+ * will only be called if this query returns a value other than what is stored in
+ * preferences and should cause the calling activity to refresh anything that
+ * depends on calling this method.
+ *
+ * @param context The calling activity
+ * @param callback The runnable that should execute if a query returns new values
+ * @return The string value representing the time zone Calendar should display
+ */
+ fun getTimeZone(context: Context, callback: Runnable?): String {
+ synchronized(mTZCallbacks) {
+ if (mFirstTZRequest) {
+ val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName)
+ mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)
+ mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone()) ?: String()
+
+ // Only check content resolver if we have a looper to attach to use
+ if (Looper.myLooper() != null) {
+ mTZQueryInProgress = true
+ mFirstTZRequest = false
+
+ // When the async query returns it should synchronize on
+ // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
+ // preferences, set mTZQueryInProgress to false, and call all
+ // the runnables in mTZCallbacks.
+ if (mHandler == null) {
+ mHandler = AsyncTZHandler(context.getContentResolver())
+ }
+ mHandler?.startQuery(0, context, CalendarCache.URI,
+ CALENDAR_CACHE_POJECTION, null, null, null)
+ }
+ }
+ if (mTZQueryInProgress && callback != null) {
+ mTZCallbacks.add(callback)
+ }
+ }
+ return if (mUseHomeTZ) mHomeTZ else Time.getCurrentTimezone()
+ }
+
+ /**
+ * Forces a query of the database to check for changes to the time zone.
+ * This should be called if another app may have modified the db. If a
+ * query is already in progress the callback will be added to the list
+ * of callbacks to be called when it returns.
+ *
+ * @param context The calling activity
+ * @param callback The runnable that should execute if a query returns
+ * new values
+ */
+ fun forceDBRequery(context: Context, callback: Runnable) {
+ synchronized(mTZCallbacks) {
+ if (mTZQueryInProgress) {
+ mTZCallbacks.add(callback)
+ return
+ }
+ mFirstTZRequest = true
+ getTimeZone(context, callback)
+ }
+ }
+
+ companion object {
+ private val TIMEZONE_TYPE_ARGS = arrayOf<String>(CalendarCache.KEY_TIMEZONE_TYPE)
+ private val TIMEZONE_INSTANCES_ARGS =
+ arrayOf<String>(CalendarCache.KEY_TIMEZONE_INSTANCES)
+ val CALENDAR_CACHE_POJECTION = arrayOf<String>(
+ CalendarCache.KEY, CalendarCache.VALUE
+ )
+ private val mSB: StringBuilder = StringBuilder(50)
+ private val mF: Formatter = Formatter(mSB, Locale.getDefault())
+
+ @Volatile
+ private var mFirstTZRequest = true
+
+ @Volatile
+ private var mTZQueryInProgress = false
+
+ @Volatile
+ private var mUseHomeTZ = false
+
+ @Volatile
+ private var mHomeTZ: String = Time.getCurrentTimezone()
+ private val mTZCallbacks: HashSet<Runnable> = HashSet<Runnable>()
+ private var mToken = 1
+ private var mHandler: AsyncTZHandler? = null
+
+ /**
+ * This is the key used for writing whether or not a home time zone should
+ * be used in the Calendar app to the Calendar Preferences.
+ */
+ const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled"
+
+ /**
+ * This is the key used for writing the time zone that should be used if
+ * home time zones are enabled for the Calendar app.
+ */
+ const val KEY_HOME_TZ = "preferences_home_tz"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/CalendarViewAdapter.java b/src/com/android/calendar/CalendarViewAdapter.java
deleted file mode 100644
index 524268fc..00000000
--- a/src/com/android/calendar/CalendarViewAdapter.java
+++ /dev/null
@@ -1,409 +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.calendar.CalendarController.ViewType;
-
-import android.content.Context;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import java.util.Formatter;
-import java.util.Locale;
-
-
-/*
- * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu
- * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts
- *
- * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu.
- */
-
-public class CalendarViewAdapter extends BaseAdapter {
-
- private static final String TAG = "MenuSpinnerAdapter";
-
- private final String mButtonNames []; // Text on buttons
-
- // Used to define the look of the menu button according to the current view:
- // Day view: show day of the week + full date underneath
- // Week view: show the month + year
- // Month view: show the month + year
- // Agenda view: show day of the week + full date underneath
- private int mCurrentMainView;
-
- private final LayoutInflater mInflater;
-
- // Defines the types of view returned by this spinner
- private static final int BUTTON_VIEW_TYPE = 0;
- static final int VIEW_TYPE_NUM = 1; // Increase this if you add more view types
-
- public static final int DAY_BUTTON_INDEX = 0;
- public static final int WEEK_BUTTON_INDEX = 1;
- public static final int MONTH_BUTTON_INDEX = 2;
- public static final int AGENDA_BUTTON_INDEX = 3;
-
- // The current selected event's time, used to calculate the date and day of the week
- // for the buttons.
- private long mMilliTime;
- private String mTimeZone;
- private long mTodayJulianDay;
-
- private final Context mContext;
- private final Formatter mFormatter;
- private final StringBuilder mStringBuilder;
- private Handler mMidnightHandler = null; // Used to run a time update every midnight
- private final boolean mShowDate; // Spinner mode indicator (view name or view name with date)
-
- // Updates time specific variables (time-zone, today's Julian day).
- private final Runnable mTimeUpdater = new Runnable() {
- @Override
- public void run() {
- refresh(mContext);
- }
- };
-
- public CalendarViewAdapter(Context context, int viewType, boolean showDate) {
- super();
-
- mMidnightHandler = new Handler();
- mCurrentMainView = viewType;
- mContext = context;
- mShowDate = showDate;
-
- // Initialize
- mButtonNames = context.getResources().getStringArray(R.array.buttons_list);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mStringBuilder = new StringBuilder(50);
- mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
- // Sets time specific variables and starts a thread for midnight updates
- if (showDate) {
- refresh(context);
- }
- }
-
-
- // Sets the time zone and today's Julian day to be used by the adapter.
- // Also, notify listener on the change and resets the midnight update thread.
- public void refresh(Context context) {
- mTimeZone = Utils.getTimeZone(context, mTimeUpdater);
- Time time = new Time(mTimeZone);
- long now = System.currentTimeMillis();
- time.set(now);
- mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
- notifyDataSetChanged();
- setMidnightHandler();
- }
-
- // Sets a thread to run 1 second after midnight and update the current date
- // This is used to display correctly the date of yesterday/today/tomorrow
- private void setMidnightHandler() {
- mMidnightHandler.removeCallbacks(mTimeUpdater);
- // Set the time updater to run at 1 second after midnight
- long now = System.currentTimeMillis();
- Time time = new Time(mTimeZone);
- time.set(now);
- long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 -
- time.second + 1) * 1000;
- mMidnightHandler.postDelayed(mTimeUpdater, runInMillis);
- }
-
- // Stops the midnight update thread, called by the activity when it is paused.
- public void onPause() {
- mMidnightHandler.removeCallbacks(mTimeUpdater);
- }
-
- // Returns the amount of buttons in the menu
- @Override
- public int getCount() {
- return mButtonNames.length;
- }
-
-
- @Override
- public Object getItem(int position) {
- if (position < mButtonNames.length) {
- return mButtonNames[position];
- }
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- // Item ID is its location in the list
- return position;
- }
-
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
-
- View v;
-
- if (mShowDate) {
- // Check if can recycle the view
- if (convertView == null || ((Integer) convertView.getTag()).intValue()
- != R.layout.actionbar_pulldown_menu_top_button) {
- v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false);
- // Set the tag to make sure you can recycle it when you get it
- // as a convert view
- v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button));
- } else {
- v = convertView;
- }
- TextView weekDay = (TextView) v.findViewById(R.id.top_button_weekday);
- TextView date = (TextView) v.findViewById(R.id.top_button_date);
-
- switch (mCurrentMainView) {
- case ViewType.DAY:
- weekDay.setVisibility(View.VISIBLE);
- weekDay.setText(buildDayOfWeek());
- date.setText(buildFullDate());
- break;
- case ViewType.WEEK:
- if (Utils.getShowWeekNumber(mContext)) {
- weekDay.setVisibility(View.VISIBLE);
- weekDay.setText(buildWeekNum());
- } else {
- weekDay.setVisibility(View.GONE);
- }
- date.setText(buildMonthYearDate());
- break;
- case ViewType.MONTH:
- weekDay.setVisibility(View.GONE);
- date.setText(buildMonthYearDate());
- break;
- default:
- v = null;
- break;
- }
- } else {
- if (convertView == null || ((Integer) convertView.getTag()).intValue()
- != R.layout.actionbar_pulldown_menu_top_button_no_date) {
- v = mInflater.inflate(
- R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false);
- // Set the tag to make sure you can recycle it when you get it
- // as a convert view
- v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button_no_date));
- } else {
- v = convertView;
- }
- TextView title = (TextView) v;
- switch (mCurrentMainView) {
- case ViewType.DAY:
- title.setText(mButtonNames [DAY_BUTTON_INDEX]);
- break;
- case ViewType.WEEK:
- title.setText(mButtonNames [WEEK_BUTTON_INDEX]);
- break;
- case ViewType.MONTH:
- title.setText(mButtonNames [MONTH_BUTTON_INDEX]);
- break;
- default:
- v = null;
- break;
- }
- }
- return v;
- }
-
- @Override
- public int getItemViewType(int position) {
- // Only one kind of view is used
- return BUTTON_VIEW_TYPE;
- }
-
- @Override
- public int getViewTypeCount() {
- return VIEW_TYPE_NUM;
- }
-
- @Override
- public boolean isEmpty() {
- return (mButtonNames.length == 0);
- }
-
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- View v = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false);
- TextView viewType = (TextView)v.findViewById(R.id.button_view);
- TextView date = (TextView)v.findViewById(R.id.button_date);
- switch (position) {
- case DAY_BUTTON_INDEX:
- viewType.setText(mButtonNames [DAY_BUTTON_INDEX]);
- if (mShowDate) {
- date.setText(buildMonthDayDate());
- }
- break;
- case WEEK_BUTTON_INDEX:
- viewType.setText(mButtonNames [WEEK_BUTTON_INDEX]);
- if (mShowDate) {
- date.setText(buildWeekDate());
- }
- break;
- case MONTH_BUTTON_INDEX:
- viewType.setText(mButtonNames [MONTH_BUTTON_INDEX]);
- if (mShowDate) {
- date.setText(buildMonthDate());
- }
- break;
- default:
- v = convertView;
- break;
- }
- return v;
- }
-
- // Updates the current viewType
- // Used to match the label on the menu button with the calendar view
- public void setMainView(int viewType) {
- mCurrentMainView = viewType;
- notifyDataSetChanged();
- }
-
- // Update the date that is displayed on buttons
- // Used when the user selects a new day/week/month to watch
- public void setTime(long time) {
- mMilliTime = time;
- notifyDataSetChanged();
- }
-
- // Builds a string with the day of the week and the word yesterday/today/tomorrow
- // before it if applicable.
- private String buildDayOfWeek() {
-
- Time t = new Time(mTimeZone);
- t.set(mMilliTime);
- long julianDay = Time.getJulianDay(mMilliTime,t.gmtoff);
- String dayOfWeek = null;
- mStringBuilder.setLength(0);
-
- if (julianDay == mTodayJulianDay) {
- dayOfWeek = mContext.getString(R.string.agenda_today,
- DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
- } else if (julianDay == mTodayJulianDay - 1) {
- dayOfWeek = mContext.getString(R.string.agenda_yesterday,
- DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
- } else if (julianDay == mTodayJulianDay + 1) {
- dayOfWeek = mContext.getString(R.string.agenda_tomorrow,
- DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
- } else {
- dayOfWeek = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString();
- }
- return dayOfWeek.toUpperCase();
- }
-
- // Builds strings with different formats:
- // Full date: Month,day Year
- // Month year
- // Month day
- // Month
- // Week: month day-day or month day - month day
- private String buildFullDate() {
- mStringBuilder.setLength(0);
- String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString();
- return date;
- }
-
- private String buildMonthYearDate() {
- mStringBuilder.setLength(0);
- String date = DateUtils.formatDateRange(
- mContext,
- mFormatter,
- mMilliTime,
- mMilliTime,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString();
- return date;
- }
-
- private String buildMonthDayDate() {
- mStringBuilder.setLength(0);
- String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR, mTimeZone).toString();
- return date;
- }
-
- private String buildMonthDate() {
- mStringBuilder.setLength(0);
- String date = DateUtils.formatDateRange(
- mContext,
- mFormatter,
- mMilliTime,
- mMilliTime,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR
- | DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString();
- return date;
- }
-
- private String buildWeekDate() {
- // Calculate the start of the week, taking into account the "first day of the week"
- // setting.
-
- Time t = new Time(mTimeZone);
- t.set(mMilliTime);
- int firstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
- int dayOfWeek = t.weekDay;
- int diff = dayOfWeek - firstDayOfWeek;
- if (diff != 0) {
- if (diff < 0) {
- diff += 7;
- }
- t.monthDay -= diff;
- t.normalize(true /* ignore isDst */);
- }
-
- long weekStartTime = t.toMillis(true);
- // The end of the week is 6 days after the start of the week
- long weekEndTime = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS;
-
- // If week start and end is in 2 different months, use short months names
- Time t1 = new Time(mTimeZone);
- t.set(weekEndTime);
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
- if (t.month != t1.month) {
- flags |= DateUtils.FORMAT_ABBREV_MONTH;
- }
-
- mStringBuilder.setLength(0);
- String date = DateUtils.formatDateRange(mContext, mFormatter, weekStartTime,
- weekEndTime, flags, mTimeZone).toString();
- return date;
- }
-
- private String buildWeekNum() {
- int week = Utils.getWeekNumberFromTime(mMilliTime, mContext);
- return mContext.getResources().getQuantityString(R.plurals.weekN, week, week);
- }
-
-}
diff --git a/src/com/android/calendar/CalendarViewAdapter.kt b/src/com/android/calendar/CalendarViewAdapter.kt
new file mode 100644
index 00000000..2fe10272
--- /dev/null
+++ b/src/com/android/calendar/CalendarViewAdapter.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import com.android.calendar.CalendarController.ViewType
+import android.content.Context
+import android.os.Handler
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.TextView
+import java.util.Formatter
+import java.util.Locale
+
+/*
+ * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu
+ * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts
+ *
+ * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu.
+ */
+class CalendarViewAdapter(context: Context, viewType: Int, showDate: Boolean) : BaseAdapter() {
+ private val mButtonNames: Array<String> // Text on buttons
+
+ // Used to define the look of the menu button according to the current view:
+ // Day view: show day of the week + full date underneath
+ // Week view: show the month + year
+ // Month view: show the month + year
+ // Agenda view: show day of the week + full date underneath
+ private var mCurrentMainView: Int
+ private val mInflater: LayoutInflater
+
+ // The current selected event's time, used to calculate the date and day of the week
+ // for the buttons.
+ private var mMilliTime: Long = 0
+ private var mTimeZone: String? = null
+ private var mTodayJulianDay: Long = 0
+ private val mContext: Context = context
+ private val mFormatter: Formatter
+ private val mStringBuilder: StringBuilder
+ private var mMidnightHandler: Handler? = null // Used to run a time update every midnight
+ private val mShowDate: Boolean // Spinner mode indicator (view name or view name with date)
+
+ // Updates time specific variables (time-zone, today's Julian day).
+ private val mTimeUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ refresh(mContext)
+ }
+ }
+
+ // Sets the time zone and today's Julian day to be used by the adapter.
+ // Also, notify listener on the change and resets the midnight update thread.
+ fun refresh(context: Context?) {
+ mTimeZone = Utils.getTimeZone(context, mTimeUpdater)
+ val time = Time(mTimeZone)
+ val now: Long = System.currentTimeMillis()
+ time.set(now)
+ mTodayJulianDay = Time.getJulianDay(now, time.gmtoff).toLong()
+ notifyDataSetChanged()
+ setMidnightHandler()
+ }
+
+ // Sets a thread to run 1 second after midnight and update the current date
+ // This is used to display correctly the date of yesterday/today/tomorrow
+ private fun setMidnightHandler() {
+ mMidnightHandler?.removeCallbacks(mTimeUpdater)
+ // Set the time updater to run at 1 second after midnight
+ val now: Long = System.currentTimeMillis()
+ val time = Time(mTimeZone)
+ time.set(now)
+ val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 -
+ time.second + 1) * 1000).toLong()
+ mMidnightHandler?.postDelayed(mTimeUpdater, runInMillis)
+ }
+
+ // Stops the midnight update thread, called by the activity when it is paused.
+ fun onPause() {
+ mMidnightHandler?.removeCallbacks(mTimeUpdater)
+ }
+
+ // Returns the amount of buttons in the menu
+ @Override
+ override fun getCount(): Int {
+ return mButtonNames.size
+ }
+
+ @Override
+ override fun getItem(position: Int): Any? {
+ return if (position < mButtonNames.size) {
+ mButtonNames[position]
+ } else null
+ }
+
+ @Override
+ override fun getItemId(position: Int): Long {
+ // Item ID is its location in the list
+ return position.toLong()
+ }
+
+ @Override
+ override fun hasStableIds(): Boolean {
+ return false
+ }
+
+ @Override
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+ var v: View?
+ if (mShowDate) {
+ // Check if can recycle the view
+ if (convertView == null || (convertView.getTag() as Int)
+ != R.layout.actionbar_pulldown_menu_top_button as Int) {
+ v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false)
+ // Set the tag to make sure you can recycle it when you get it
+ // as a convert view
+ v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button))
+ } else {
+ v = convertView
+ }
+ val weekDay: TextView = v?.findViewById(R.id.top_button_weekday) as TextView
+ val date: TextView = v?.findViewById(R.id.top_button_date) as TextView
+ when (mCurrentMainView) {
+ ViewType.DAY -> {
+ weekDay.setVisibility(View.VISIBLE)
+ weekDay.setText(buildDayOfWeek())
+ date.setText(buildFullDate())
+ }
+ ViewType.WEEK -> {
+ if (Utils.getShowWeekNumber(mContext)) {
+ weekDay.setVisibility(View.VISIBLE)
+ weekDay.setText(buildWeekNum())
+ } else {
+ weekDay.setVisibility(View.GONE)
+ }
+ date.setText(buildMonthYearDate())
+ }
+ ViewType.MONTH -> {
+ weekDay.setVisibility(View.GONE)
+ date.setText(buildMonthYearDate())
+ }
+ else -> v = null
+ }
+ } else {
+ if (convertView == null || (convertView.getTag() as Int)
+ != R.layout.actionbar_pulldown_menu_top_button_no_date as Int) {
+ v = mInflater.inflate(
+ R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false)
+ // Set the tag to make sure you can recycle it when you get it
+ // as a convert view
+ v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button_no_date))
+ } else {
+ v = convertView
+ }
+ val title: TextView? = v as TextView?
+ when (mCurrentMainView) {
+ ViewType.DAY -> title?.setText(mButtonNames[DAY_BUTTON_INDEX])
+ ViewType.WEEK -> title?.setText(mButtonNames[WEEK_BUTTON_INDEX])
+ ViewType.MONTH -> title?.setText(mButtonNames[MONTH_BUTTON_INDEX])
+ else -> v = null
+ }
+ }
+ return v
+ }
+
+ @Override
+ override fun getItemViewType(position: Int): Int {
+ // Only one kind of view is used
+ return BUTTON_VIEW_TYPE
+ }
+
+ @Override
+ override fun getViewTypeCount(): Int {
+ return VIEW_TYPE_NUM
+ }
+
+ @Override
+ override fun isEmpty(): Boolean {
+ return mButtonNames.size == 0
+ }
+
+ @Override
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+ var v: View? = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false)
+ val viewType: TextView? = v?.findViewById(R.id.button_view) as? TextView
+ val date: TextView? = v?.findViewById(R.id.button_date) as? TextView
+ when (position) {
+ DAY_BUTTON_INDEX -> {
+ viewType?.setText(mButtonNames[DAY_BUTTON_INDEX])
+ if (mShowDate) {
+ date?.setText(buildMonthDayDate())
+ }
+ }
+ WEEK_BUTTON_INDEX -> {
+ viewType?.setText(mButtonNames[WEEK_BUTTON_INDEX])
+ if (mShowDate) {
+ date?.setText(buildWeekDate())
+ }
+ }
+ MONTH_BUTTON_INDEX -> {
+ viewType?.setText(mButtonNames[MONTH_BUTTON_INDEX])
+ if (mShowDate) {
+ date?.setText(buildMonthDate())
+ }
+ }
+ else -> v = convertView
+ }
+ return v
+ }
+
+ // Updates the current viewType
+ // Used to match the label on the menu button with the calendar view
+ fun setMainView(viewType: Int) {
+ mCurrentMainView = viewType
+ notifyDataSetChanged()
+ }
+
+ // Update the date that is displayed on buttons
+ // Used when the user selects a new day/week/month to watch
+ fun setTime(time: Long) {
+ mMilliTime = time
+ notifyDataSetChanged()
+ }
+
+ // Builds a string with the day of the week and the word yesterday/today/tomorrow
+ // before it if applicable.
+ private fun buildDayOfWeek(): String {
+ val t = Time(mTimeZone)
+ t.set(mMilliTime)
+ val julianDay: Long = Time.getJulianDay(mMilliTime, t.gmtoff).toLong()
+ var dayOfWeek: String? = null
+ mStringBuilder.setLength(0)
+ dayOfWeek = if (julianDay == mTodayJulianDay) {
+ mContext.getString(R.string.agenda_today,
+ DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+ } else if (julianDay == mTodayJulianDay - 1) {
+ mContext.getString(R.string.agenda_yesterday,
+ DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+ } else if (julianDay == mTodayJulianDay + 1) {
+ mContext.getString(R.string.agenda_tomorrow,
+ DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+ } else {
+ DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()
+ }
+ return dayOfWeek.toUpperCase()
+ }
+
+ // Builds strings with different formats:
+ // Full date: Month,day Year
+ // Month year
+ // Month day
+ // Month
+ // Week: month day-day or month day - month day
+ private fun buildFullDate(): String {
+ mStringBuilder.setLength(0)
+ return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString()
+ }
+
+ private fun buildMonthYearDate(): String {
+ mStringBuilder.setLength(0)
+ return DateUtils.formatDateRange(
+ mContext,
+ mFormatter,
+ mMilliTime,
+ mMilliTime,
+ DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY
+ or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString()
+ }
+
+ private fun buildMonthDayDate(): String {
+ mStringBuilder.setLength(0)
+ return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+ DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR, mTimeZone).toString()
+ }
+
+ private fun buildMonthDate(): String {
+ mStringBuilder.setLength(0)
+ return DateUtils.formatDateRange(
+ mContext,
+ mFormatter,
+ mMilliTime,
+ mMilliTime,
+ DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR
+ or DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString()
+ }
+
+ private fun buildWeekDate(): String {
+ // Calculate the start of the week, taking into account the "first day of the week"
+ // setting.
+ val t = Time(mTimeZone)
+ t.set(mMilliTime)
+ val firstDayOfWeek: Int = Utils.getFirstDayOfWeek(mContext)
+ val dayOfWeek: Int = t.weekDay
+ var diff = dayOfWeek - firstDayOfWeek
+ if (diff != 0) {
+ if (diff < 0) {
+ diff += 7
+ }
+ t.monthDay -= diff
+ t.normalize(true /* ignore isDst */)
+ }
+ val weekStartTime: Long = t.toMillis(true)
+ // The end of the week is 6 days after the start of the week
+ val weekEndTime: Long = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS
+
+ // If week start and end is in 2 different months, use short months names
+ val t1 = Time(mTimeZone)
+ t.set(weekEndTime)
+ var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR
+ if (t.month !== t1.month) {
+ flags = flags or DateUtils.FORMAT_ABBREV_MONTH
+ }
+ mStringBuilder.setLength(0)
+ return DateUtils.formatDateRange(mContext, mFormatter, weekStartTime,
+ weekEndTime, flags, mTimeZone).toString()
+ }
+
+ private fun buildWeekNum(): String {
+ val week: Int = Utils.getWeekNumberFromTime(mMilliTime, mContext)
+ return mContext.getResources().getQuantityString(R.plurals.weekN, week, week)
+ }
+
+ companion object {
+ private const val TAG = "MenuSpinnerAdapter"
+
+ // Defines the types of view returned by this spinner
+ private const val BUTTON_VIEW_TYPE = 0
+ const val VIEW_TYPE_NUM = 1 // Increase this if you add more view types
+ const val DAY_BUTTON_INDEX = 0
+ const val WEEK_BUTTON_INDEX = 1
+ const val MONTH_BUTTON_INDEX = 2
+ const val AGENDA_BUTTON_INDEX = 3
+ }
+
+ init {
+ mMidnightHandler = Handler()
+ mCurrentMainView = viewType
+ mShowDate = showDate
+
+ // Initialize
+ mButtonNames = context.getResources().getStringArray(R.array.buttons_list)
+ mInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ mStringBuilder = StringBuilder(50)
+ mFormatter = Formatter(mStringBuilder, Locale.getDefault())
+
+ // Sets time specific variables and starts a thread for midnight updates
+ if (showDate) {
+ refresh(context)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/DayFragment.java b/src/com/android/calendar/DayFragment.java
deleted file mode 100644
index a9fb39ed..00000000
--- a/src/com/android/calendar/DayFragment.java
+++ /dev/null
@@ -1,256 +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.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.text.format.Time;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.ProgressBar;
-import android.widget.ViewSwitcher;
-import android.widget.ViewSwitcher.ViewFactory;
-
-/**
- * This is the base class for Day and Week Activities.
- */
-public class DayFragment extends Fragment implements CalendarController.EventHandler, ViewFactory {
- /**
- * The view id used for all the views we create. It's OK to have all child
- * views have the same ID. This ID is used to pick which view receives
- * focus when a view hierarchy is saved / restore
- */
- private static final int VIEW_ID = 1;
-
- protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
-
- protected ProgressBar mProgressBar;
- protected ViewSwitcher mViewSwitcher;
- protected Animation mInAnimationForward;
- protected Animation mOutAnimationForward;
- protected Animation mInAnimationBackward;
- protected Animation mOutAnimationBackward;
- EventLoader mEventLoader;
-
- Time mSelectedDay = new Time();
-
- private final Runnable mTZUpdater = new Runnable() {
- @Override
- public void run() {
- if (!DayFragment.this.isAdded()) {
- return;
- }
- String tz = Utils.getTimeZone(getActivity(), mTZUpdater);
- mSelectedDay.timezone = tz;
- mSelectedDay.normalize(true);
- }
- };
-
- private int mNumDays;
-
- public DayFragment() {
- mSelectedDay.setToNow();
- }
-
- public DayFragment(long timeMillis, int numOfDays) {
- mNumDays = numOfDays;
- if (timeMillis == 0) {
- mSelectedDay.setToNow();
- } else {
- mSelectedDay.set(timeMillis);
- }
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- Context context = getActivity();
-
- mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in);
- mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out);
- mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in);
- mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out);
-
- mEventLoader = new EventLoader(context);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.day_activity, null);
-
- mViewSwitcher = (ViewSwitcher) v.findViewById(R.id.switcher);
- mViewSwitcher.setFactory(this);
- mViewSwitcher.getCurrentView().requestFocus();
- ((DayView) mViewSwitcher.getCurrentView()).updateTitle();
-
- return v;
- }
-
- public View makeView() {
- mTZUpdater.run();
- DayView view = new DayView(getActivity(), CalendarController
- .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays);
- view.setId(VIEW_ID);
- view.setLayoutParams(new ViewSwitcher.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- view.setSelected(mSelectedDay, false, false);
- return view;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mEventLoader.startBackgroundThread();
- mTZUpdater.run();
- eventsChanged();
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- view.handleOnResume();
- view.restartCurrentTimeUpdates();
-
- view = (DayView) mViewSwitcher.getNextView();
- view.handleOnResume();
- view.restartCurrentTimeUpdates();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- view.cleanup();
- view = (DayView) mViewSwitcher.getNextView();
- view.cleanup();
- mEventLoader.stopBackgroundThread();
-
- // Stop events cross-fade animation
- view.stopEventsAnimation();
- ((DayView) mViewSwitcher.getNextView()).stopEventsAnimation();
- }
-
- void startProgressSpinner() {
- // start the progress spinner
- mProgressBar.setVisibility(View.VISIBLE);
- }
-
- void stopProgressSpinner() {
- // stop the progress spinner
- mProgressBar.setVisibility(View.GONE);
- }
-
- private void goTo(Time goToTime, boolean ignoreTime, boolean animateToday) {
- if (mViewSwitcher == null) {
- // The view hasn't been set yet. Just save the time and use it later.
- mSelectedDay.set(goToTime);
- return;
- }
-
- DayView currentView = (DayView) mViewSwitcher.getCurrentView();
-
- // How does goTo time compared to what's already displaying?
- int diff = currentView.compareToVisibleTimeRange(goToTime);
-
- if (diff == 0) {
- // In visible range. No need to switch view
- currentView.setSelected(goToTime, ignoreTime, animateToday);
- } else {
- // Figure out which way to animate
- if (diff > 0) {
- mViewSwitcher.setInAnimation(mInAnimationForward);
- mViewSwitcher.setOutAnimation(mOutAnimationForward);
- } else {
- mViewSwitcher.setInAnimation(mInAnimationBackward);
- mViewSwitcher.setOutAnimation(mOutAnimationBackward);
- }
-
- DayView next = (DayView) mViewSwitcher.getNextView();
- if (ignoreTime) {
- next.setFirstVisibleHour(currentView.getFirstVisibleHour());
- }
-
- next.setSelected(goToTime, ignoreTime, animateToday);
- next.reloadEvents();
- mViewSwitcher.showNext();
- next.requestFocus();
- next.updateTitle();
- next.restartCurrentTimeUpdates();
- }
- }
-
- /**
- * Returns the selected time in milliseconds. The milliseconds are measured
- * in UTC milliseconds from the epoch and uniquely specifies any selectable
- * time.
- *
- * @return the selected time in milliseconds
- */
- public long getSelectedTimeInMillis() {
- if (mViewSwitcher == null) {
- return -1;
- }
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- if (view == null) {
- return -1;
- }
- return view.getSelectedTimeInMillis();
- }
-
- public void eventsChanged() {
- if (mViewSwitcher == null) {
- return;
- }
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- view.clearCachedEvents();
- view.reloadEvents();
-
- view = (DayView) mViewSwitcher.getNextView();
- view.clearCachedEvents();
- }
-
- public DayView getNextView() {
- return (DayView) mViewSwitcher.getNextView();
- }
-
- public long getSupportedEventTypes() {
- return EventType.GO_TO | EventType.EVENTS_CHANGED;
- }
-
- public void handleEvent(EventInfo msg) {
- if (msg.eventType == EventType.GO_TO) {
-// TODO support a range of time
-// TODO support event_id
-// TODO support select message
- goTo(msg.selectedTime, (msg.extraLong & CalendarController.EXTRA_GOTO_DATE) != 0,
- (msg.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0);
- } else if (msg.eventType == EventType.EVENTS_CHANGED) {
- eventsChanged();
- }
- }
-}
diff --git a/src/com/android/calendar/DayFragment.kt b/src/com/android/calendar/DayFragment.kt
new file mode 100644
index 00000000..39e92f5b
--- /dev/null
+++ b/src/com/android/calendar/DayFragment.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import android.app.Fragment
+import android.content.Context
+import android.os.Bundle
+import android.text.format.Time
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout.LayoutParams
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.ProgressBar
+import android.widget.ViewSwitcher
+import android.widget.ViewSwitcher.ViewFactory
+
+/**
+ * This is the base class for Day and Week Activities.
+ */
+class DayFragment : Fragment, CalendarController.EventHandler, ViewFactory {
+ protected var mProgressBar: ProgressBar? = null
+ protected var mViewSwitcher: ViewSwitcher? = null
+ protected var mInAnimationForward: Animation? = null
+ protected var mOutAnimationForward: Animation? = null
+ protected var mInAnimationBackward: Animation? = null
+ protected var mOutAnimationBackward: Animation? = null
+ var mEventLoader: EventLoader? = null
+ var mSelectedDay: Time = Time()
+ private val mTZUpdater: Runnable = object : Runnable {
+ override fun run() {
+ if (!this@DayFragment.isAdded()) {
+ return
+ }
+ val tz: String? = Utils.getTimeZone(getActivity(), this)
+ mSelectedDay.timezone = tz
+ mSelectedDay.normalize(true)
+ }
+ }
+ private var mNumDays = 0
+
+ constructor() {
+ mSelectedDay.setToNow()
+ }
+
+ constructor(timeMillis: Long, numOfDays: Int) {
+ mNumDays = numOfDays
+ if (timeMillis == 0L) {
+ mSelectedDay.setToNow()
+ } else {
+ mSelectedDay.set(timeMillis)
+ }
+ }
+
+ override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ val context: Context = getActivity()
+ mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in)
+ mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out)
+ mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in)
+ mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out)
+ mEventLoader = EventLoader(context)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater?,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val v: View? = inflater?.inflate(R.layout.day_activity, null)
+ mViewSwitcher = v?.findViewById(R.id.switcher) as? ViewSwitcher
+ mViewSwitcher?.setFactory(this)
+ mViewSwitcher?.getCurrentView()?.requestFocus()
+ (mViewSwitcher?.getCurrentView() as? DayView)?.updateTitle()
+ return v
+ }
+
+ override fun makeView(): View {
+ mTZUpdater.run()
+ val view = DayView(getActivity(), CalendarController
+ .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays)
+ view.setId(DayFragment.Companion.VIEW_ID)
+ view.setLayoutParams(LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
+ view.setSelected(mSelectedDay, false, false)
+ return view
+ }
+
+ override fun onResume() {
+ super.onResume()
+ mEventLoader!!.startBackgroundThread()
+ mTZUpdater.run()
+ eventsChanged()
+ var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+ view?.handleOnResume()
+ view?.restartCurrentTimeUpdates()
+ view = mViewSwitcher?.getNextView() as? DayView
+ view?.handleOnResume()
+ view?.restartCurrentTimeUpdates()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle?) {
+ super.onSaveInstanceState(outState)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+ view?.cleanup()
+ view = mViewSwitcher?.getNextView() as? DayView
+ view?.cleanup()
+ mEventLoader!!.stopBackgroundThread()
+
+ // Stop events cross-fade animation
+ view?.stopEventsAnimation()
+ (mViewSwitcher?.getNextView() as? DayView)?.stopEventsAnimation()
+ }
+
+ fun startProgressSpinner() {
+ // start the progress spinner
+ mProgressBar?.setVisibility(View.VISIBLE)
+ }
+
+ fun stopProgressSpinner() {
+ // stop the progress spinner
+ mProgressBar?.setVisibility(View.GONE)
+ }
+
+ private fun goTo(goToTime: Time?, ignoreTime: Boolean, animateToday: Boolean) {
+ if (mViewSwitcher == null) {
+ // The view hasn't been set yet. Just save the time and use it later.
+ mSelectedDay.set(goToTime)
+ return
+ }
+ val currentView: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+
+ // How does goTo time compared to what's already displaying?
+ val diff: Int = currentView?.compareToVisibleTimeRange(goToTime as Time) as Int
+ if (diff == 0) {
+ // In visible range. No need to switch view
+ currentView?.setSelected(goToTime, ignoreTime, animateToday)
+ } else {
+ // Figure out which way to animate
+ if (diff > 0) {
+ mViewSwitcher?.setInAnimation(mInAnimationForward)
+ mViewSwitcher?.setOutAnimation(mOutAnimationForward)
+ } else {
+ mViewSwitcher?.setInAnimation(mInAnimationBackward)
+ mViewSwitcher?.setOutAnimation(mOutAnimationBackward)
+ }
+ val next: DayView? = mViewSwitcher?.getNextView() as? DayView
+ if (ignoreTime) {
+ next!!.firstVisibleHour = currentView.firstVisibleHour
+ }
+ next?.setSelected(goToTime, ignoreTime, animateToday)
+ next?.reloadEvents()
+ mViewSwitcher?.showNext()
+ next?.requestFocus()
+ next?.updateTitle()
+ next?.restartCurrentTimeUpdates()
+ }
+ }
+
+ /**
+ * Returns the selected time in milliseconds. The milliseconds are measured
+ * in UTC milliseconds from the epoch and uniquely specifies any selectable
+ * time.
+ *
+ * @return the selected time in milliseconds
+ */
+ val selectedTimeInMillis: Long
+ get() {
+ if (mViewSwitcher == null) {
+ return -1
+ }
+ val view: DayView = mViewSwitcher?.getCurrentView() as DayView ?: return -1
+ return view.selectedTimeInMillis
+ }
+
+ override fun eventsChanged() {
+ if (mViewSwitcher == null) {
+ return
+ }
+ var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+ view?.clearCachedEvents()
+ view?.reloadEvents()
+ view = mViewSwitcher?.getNextView() as? DayView
+ view?.clearCachedEvents()
+ }
+
+ val nextView: DayView?
+ get() = mViewSwitcher?.getNextView() as? DayView
+ override val supportedEventTypes: Long
+ get() = CalendarController.EventType.GO_TO or CalendarController.EventType.EVENTS_CHANGED
+
+ override fun handleEvent(msg: CalendarController.EventInfo?) {
+ if (msg?.eventType == CalendarController.EventType.GO_TO) {
+// TODO support a range of time
+// TODO support event_id
+// TODO support select message
+ goTo(msg?.selectedTime, msg?.extraLong and CalendarController.EXTRA_GOTO_DATE != 0L,
+ msg?.extraLong and CalendarController.EXTRA_GOTO_TODAY != 0L)
+ } else if (msg?.eventType == CalendarController.EventType.EVENTS_CHANGED) {
+ eventsChanged()
+ }
+ }
+
+ companion object {
+ /**
+ * The view id used for all the views we create. It's OK to have all child
+ * views have the same ID. This ID is used to pick which view receives
+ * focus when a view hierarchy is saved / restore
+ */
+ private const val VIEW_ID = 1
+ protected const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/DayOfMonthDrawable.java b/src/com/android/calendar/DayOfMonthDrawable.java
deleted file mode 100644
index 461ab317..00000000
--- a/src/com/android/calendar/DayOfMonthDrawable.java
+++ /dev/null
@@ -1,77 +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.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-
-/**
- * A custom view to draw the day of the month in the today button in the options menu
- */
-
-public class DayOfMonthDrawable extends Drawable {
-
- private String mDayOfMonth = "1";
- private final Paint mPaint;
- private final Rect mTextBounds = new Rect();
- private static float mTextSize = 14;
-
- public DayOfMonthDrawable(Context c) {
- mTextSize = c.getResources().getDimension(R.dimen.today_icon_text_size);
- mPaint = new Paint();
- mPaint.setAlpha(255);
- mPaint.setColor(0xFF777777);
- mPaint.setTypeface(Typeface.DEFAULT_BOLD);
- mPaint.setTextSize(mTextSize);
- mPaint.setTextAlign(Paint.Align.CENTER);
- }
-
- @Override
- public void draw(Canvas canvas) {
- mPaint.getTextBounds(mDayOfMonth, 0, mDayOfMonth.length(), mTextBounds);
- int textHeight = mTextBounds.bottom - mTextBounds.top;
- Rect bounds = getBounds();
- canvas.drawText(mDayOfMonth, bounds.right / 2, ((float) bounds.bottom + textHeight + 1) / 2,
- mPaint);
- }
-
- @Override
- public void setAlpha(int alpha) {
- mPaint.setAlpha(alpha);
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- // Ignore
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.UNKNOWN;
- }
-
- public void setDayOfMonth(int day) {
- mDayOfMonth = Integer.toString(day);
- invalidateSelf();
- }
-}
diff --git a/src/com/android/calendar/DayOfMonthDrawable.kt b/src/com/android/calendar/DayOfMonthDrawable.kt
new file mode 100644
index 00000000..e348b5a2
--- /dev/null
+++ b/src/com/android/calendar/DayOfMonthDrawable.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+
+/**
+ * A custom view to draw the day of the month in the today button in the options menu
+ */
+class DayOfMonthDrawable(c: Context) : Drawable() {
+ private var mDayOfMonth = "1"
+ private val mPaint: Paint
+ private val mTextBounds: Rect = Rect()
+ override fun draw(canvas: Canvas) {
+ mPaint.getTextBounds(mDayOfMonth, 0, mDayOfMonth.length, mTextBounds)
+ val textHeight: Int = mTextBounds.bottom - mTextBounds.top
+ val bounds: Rect = getBounds()
+ canvas.drawText(
+ mDayOfMonth, (bounds.right).toFloat() / 2f, ((bounds.bottom).toFloat() +
+ textHeight + 1) / 2f, mPaint
+ )
+ }
+
+ override fun setAlpha(alpha: Int) {
+ mPaint.setAlpha(alpha)
+ }
+
+ override fun setColorFilter(cf: ColorFilter?) {
+ // Ignore
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.UNKNOWN
+ }
+
+ fun setDayOfMonth(day: Int) {
+ mDayOfMonth = Integer.toString(day)
+ invalidateSelf()
+ }
+
+ companion object {
+ private var mTextSize = 14f
+ }
+
+ init {
+ mTextSize = c.getResources().getDimension(R.dimen.today_icon_text_size)
+ mPaint = Paint()
+ mPaint.setAlpha(255)
+ mPaint.setColor(-0x888889)
+ mPaint.setTypeface(Typeface.DEFAULT_BOLD)
+ mPaint.setTextSize(mTextSize)
+ mPaint.setTextAlign(Paint.Align.CENTER)
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/DayView.java b/src/com/android/calendar/DayView.java
deleted file mode 100644
index 2fc00b3c..00000000
--- a/src/com/android/calendar/DayView.java
+++ /dev/null
@@ -1,4008 +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.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.app.AlertDialog;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.Interpolator;
-import android.view.animation.TranslateAnimation;
-import android.widget.EdgeEffect;
-import android.widget.ImageView;
-import android.widget.OverScroller;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-import android.widget.ViewSwitcher;
-
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * View for multi-day view. So far only 1 and 7 day have been tested.
- */
-public class DayView extends View implements View.OnCreateContextMenuListener,
- ScaleGestureDetector.OnScaleGestureListener, View.OnClickListener, View.OnLongClickListener
- {
- private static String TAG = "DayView";
- private static boolean DEBUG = false;
- private static boolean DEBUG_SCALING = false;
- private static final String PERIOD_SPACE = ". ";
-
- private static float mScale = 0; // Used for supporting different screen densities
- private static final long INVALID_EVENT_ID = -1; //This is used for remembering a null event
- // Duration of the allday expansion
- private static final long ANIMATION_DURATION = 400;
- // duration of the more allday event text fade
- private static final long ANIMATION_SECONDARY_DURATION = 200;
- // duration of the scroll to go to a specified time
- private static final int GOTO_SCROLL_DURATION = 200;
- // duration for events' cross-fade animation
- private static final int EVENTS_CROSS_FADE_DURATION = 400;
- // duration to show the event clicked
- private static final int CLICK_DISPLAY_DURATION = 50;
-
- private static final int MENU_DAY = 3;
- private static final int MENU_EVENT_VIEW = 5;
- private static final int MENU_EVENT_CREATE = 6;
- private static final int MENU_EVENT_EDIT = 7;
- private static final int MENU_EVENT_DELETE = 8;
-
- private static int DEFAULT_CELL_HEIGHT = 64;
- private static int MAX_CELL_HEIGHT = 150;
- private static int MIN_Y_SPAN = 100;
-
- private boolean mOnFlingCalled;
- private boolean mStartingScroll = false;
- protected boolean mPaused = true;
- private Handler mHandler;
- /**
- * ID of the last event which was displayed with the toast popup.
- *
- * This is used to prevent popping up multiple quick views for the same event, especially
- * during calendar syncs. This becomes valid when an event is selected, either by default
- * on starting calendar or by scrolling to an event. It becomes invalid when the user
- * explicitly scrolls to an empty time slot, changes views, or deletes the event.
- */
- private long mLastPopupEventID;
-
- protected Context mContext;
-
- private static final String[] CALENDARS_PROJECTION = new String[] {
- Calendars._ID, // 0
- Calendars.CALENDAR_ACCESS_LEVEL, // 1
- Calendars.OWNER_ACCOUNT, // 2
- };
- private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
- private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
- private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
-
- private static final int FROM_NONE = 0;
- private static final int FROM_ABOVE = 1;
- private static final int FROM_BELOW = 2;
- private static final int FROM_LEFT = 4;
- private static final int FROM_RIGHT = 8;
-
- private static final int ACCESS_LEVEL_NONE = 0;
- private static final int ACCESS_LEVEL_DELETE = 1;
- private static final int ACCESS_LEVEL_EDIT = 2;
-
- private static int mHorizontalSnapBackThreshold = 128;
-
- private final ContinueScroll mContinueScroll = new ContinueScroll();
-
- // Make this visible within the package for more informative debugging
- Time mBaseDate;
- private Time mCurrentTime;
- //Update the current time line every five minutes if the window is left open that long
- private static final int UPDATE_CURRENT_TIME_DELAY = 300000;
- private final UpdateCurrentTime mUpdateCurrentTime = new UpdateCurrentTime();
- private int mTodayJulianDay;
-
- private final Typeface mBold = Typeface.DEFAULT_BOLD;
- private int mFirstJulianDay;
- private int mLoadedFirstJulianDay = -1;
- private int mLastJulianDay;
-
- private int mMonthLength;
- private int mFirstVisibleDate;
- private int mFirstVisibleDayOfWeek;
- private int[] mEarliestStartHour; // indexed by the week day offset
- private boolean[] mHasAllDayEvent; // indexed by the week day offset
- private String mEventCountTemplate;
- private Event mClickedEvent; // The event the user clicked on
- private Event mSavedClickedEvent;
- private static int mOnDownDelay;
- private int mClickedYLocation;
- private long mDownTouchTime;
-
- private int mEventsAlpha = 255;
- private ObjectAnimator mEventsCrossFadeAnimation;
-
- protected static StringBuilder mStringBuilder = new StringBuilder(50);
- // TODO recreate formatter when locale changes
- protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
- private final Runnable mTZUpdater = new Runnable() {
- @Override
- public void run() {
- String tz = Utils.getTimeZone(mContext, this);
- mBaseDate.timezone = tz;
- mBaseDate.normalize(true);
- mCurrentTime.switchTimezone(tz);
- invalidate();
- }
- };
-
- // Sets the "clicked" color from the clicked event
- private final Runnable mSetClick = new Runnable() {
- @Override
- public void run() {
- mClickedEvent = mSavedClickedEvent;
- mSavedClickedEvent = null;
- DayView.this.invalidate();
- }
- };
-
- // Clears the "clicked" color from the clicked event and launch the event
- private final Runnable mClearClick = new Runnable() {
- @Override
- public void run() {
- if (mClickedEvent != null) {
- mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, mClickedEvent.id,
- mClickedEvent.startMillis, mClickedEvent.endMillis,
- DayView.this.getWidth() / 2, mClickedYLocation,
- getSelectedTimeInMillis());
- }
- mClickedEvent = null;
- DayView.this.invalidate();
- }
- };
-
- private final TodayAnimatorListener mTodayAnimatorListener = new TodayAnimatorListener();
-
- class TodayAnimatorListener extends AnimatorListenerAdapter {
- private volatile Animator mAnimator = null;
- private volatile boolean mFadingIn = false;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- synchronized (this) {
- if (mAnimator != animation) {
- animation.removeAllListeners();
- animation.cancel();
- return;
- }
- if (mFadingIn) {
- if (mTodayAnimator != null) {
- mTodayAnimator.removeAllListeners();
- mTodayAnimator.cancel();
- }
- mTodayAnimator = ObjectAnimator
- .ofInt(DayView.this, "animateTodayAlpha", 255, 0);
- mAnimator = mTodayAnimator;
- mFadingIn = false;
- mTodayAnimator.addListener(this);
- mTodayAnimator.setDuration(600);
- mTodayAnimator.start();
- } else {
- mAnimateToday = false;
- mAnimateTodayAlpha = 0;
- mAnimator.removeAllListeners();
- mAnimator = null;
- mTodayAnimator = null;
- invalidate();
- }
- }
- }
-
- public void setAnimator(Animator animation) {
- mAnimator = animation;
- }
-
- public void setFadingIn(boolean fadingIn) {
- mFadingIn = fadingIn;
- }
-
- }
-
- AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mScrolling = true;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mScrolling = false;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mScrolling = false;
- resetSelectedHour();
- invalidate();
- }
- };
-
- /**
- * This variable helps to avoid unnecessarily reloading events by keeping
- * track of the start millis parameter used for the most recent loading
- * of events. If the next reload matches this, then the events are not
- * reloaded. To force a reload, set this to zero (this is set to zero
- * in the method clearCachedEvents()).
- */
- private long mLastReloadMillis;
-
- private ArrayList<Event> mEvents = new ArrayList<Event>();
- private ArrayList<Event> mAllDayEvents = new ArrayList<Event>();
- private StaticLayout[] mLayouts = null;
- private StaticLayout[] mAllDayLayouts = null;
- private int mSelectionDay; // Julian day
- private int mSelectionHour;
-
- boolean mSelectionAllday;
-
- // Current selection info for accessibility
- private int mSelectionDayForAccessibility; // Julian day
- private int mSelectionHourForAccessibility;
- private Event mSelectedEventForAccessibility;
- // Last selection info for accessibility
- private int mLastSelectionDayForAccessibility;
- private int mLastSelectionHourForAccessibility;
- private Event mLastSelectedEventForAccessibility;
-
-
- /** Width of a day or non-conflicting event */
- private int mCellWidth;
-
- // Pre-allocate these objects and re-use them
- private final Rect mRect = new Rect();
- private final Rect mDestRect = new Rect();
- private final Rect mSelectionRect = new Rect();
- // This encloses the more allDay events icon
- private final Rect mExpandAllDayRect = new Rect();
- // TODO Clean up paint usage
- private final Paint mPaint = new Paint();
- private final Paint mEventTextPaint = new Paint();
- private final Paint mSelectionPaint = new Paint();
- private float[] mLines;
-
- private int mFirstDayOfWeek; // First day of the week
-
- private PopupWindow mPopup;
- private View mPopupView;
-
- // The number of milliseconds to show the popup window
- private static final int POPUP_DISMISS_DELAY = 3000;
- private final DismissPopup mDismissPopup = new DismissPopup();
-
- private boolean mRemeasure = true;
-
- private final EventLoader mEventLoader;
- protected final EventGeometry mEventGeometry;
-
- private static float GRID_LINE_LEFT_MARGIN = 0;
- private static final float GRID_LINE_INNER_WIDTH = 1;
-
- private static final int DAY_GAP = 1;
- private static final int HOUR_GAP = 1;
- // This is the standard height of an allday event with no restrictions
- private static int SINGLE_ALLDAY_HEIGHT = 34;
- /**
- * This is the minimum desired height of a allday event.
- * When unexpanded, allday events will use this height.
- * When expanded allDay events will attempt to grow to fit all
- * events at this height.
- */
- private static float MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = 28.0F; // in pixels
- /**
- * This is how big the unexpanded allday height is allowed to be.
- * It will get adjusted based on screen size
- */
- private static int MAX_UNEXPANDED_ALLDAY_HEIGHT =
- (int) (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4);
- /**
- * This is the minimum size reserved for displaying regular events.
- * The expanded allDay region can't expand into this.
- */
- private static int MIN_HOURS_HEIGHT = 180;
- private static int ALLDAY_TOP_MARGIN = 1;
- // The largest a single allDay event will become.
- private static int MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34;
-
- private static int HOURS_TOP_MARGIN = 2;
- private static int HOURS_LEFT_MARGIN = 2;
- private static int HOURS_RIGHT_MARGIN = 4;
- private static int HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
- private static int NEW_EVENT_MARGIN = 4;
- private static int NEW_EVENT_WIDTH = 2;
- private static int NEW_EVENT_MAX_LENGTH = 16;
-
- private static int CURRENT_TIME_LINE_SIDE_BUFFER = 4;
- private static int CURRENT_TIME_LINE_TOP_OFFSET = 2;
-
- /* package */ static final int MINUTES_PER_HOUR = 60;
- /* package */ static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
- /* package */ static final int MILLIS_PER_MINUTE = 60 * 1000;
- /* package */ static final int MILLIS_PER_HOUR = (3600 * 1000);
- /* package */ static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
-
- // More events text will transition between invisible and this alpha
- private static final int MORE_EVENTS_MAX_ALPHA = 0x4C;
- private static int DAY_HEADER_ONE_DAY_LEFT_MARGIN = 0;
- private static int DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5;
- private static int DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6;
- private static int DAY_HEADER_RIGHT_MARGIN = 4;
- private static int DAY_HEADER_BOTTOM_MARGIN = 3;
- private static float DAY_HEADER_FONT_SIZE = 14;
- private static float DATE_HEADER_FONT_SIZE = 32;
- private static float NORMAL_FONT_SIZE = 12;
- private static float EVENT_TEXT_FONT_SIZE = 12;
- private static float HOURS_TEXT_SIZE = 12;
- private static float AMPM_TEXT_SIZE = 9;
- private static int MIN_HOURS_WIDTH = 96;
- private static int MIN_CELL_WIDTH_FOR_TEXT = 20;
- private static final int MAX_EVENT_TEXT_LEN = 500;
- // smallest height to draw an event with
- private static float MIN_EVENT_HEIGHT = 24.0F; // in pixels
- private static int CALENDAR_COLOR_SQUARE_SIZE = 10;
- private static int EVENT_RECT_TOP_MARGIN = 1;
- private static int EVENT_RECT_BOTTOM_MARGIN = 0;
- private static int EVENT_RECT_LEFT_MARGIN = 1;
- private static int EVENT_RECT_RIGHT_MARGIN = 0;
- private static int EVENT_RECT_STROKE_WIDTH = 2;
- private static int EVENT_TEXT_TOP_MARGIN = 2;
- private static int EVENT_TEXT_BOTTOM_MARGIN = 2;
- private static int EVENT_TEXT_LEFT_MARGIN = 6;
- private static int EVENT_TEXT_RIGHT_MARGIN = 6;
- private static int ALL_DAY_EVENT_RECT_BOTTOM_MARGIN = 1;
- private static int EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN;
- private static int EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN;
- private static int EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
- private static int EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN;
- // margins and sizing for the expand allday icon
- private static int EXPAND_ALL_DAY_BOTTOM_MARGIN = 10;
- // sizing for "box +n" in allDay events
- private static int EVENT_SQUARE_WIDTH = 10;
- private static int EVENT_LINE_PADDING = 4;
- private static int NEW_EVENT_HINT_FONT_SIZE = 12;
-
- private static int mEventTextColor;
- private static int mMoreEventsTextColor;
-
- private static int mWeek_saturdayColor;
- private static int mWeek_sundayColor;
- private static int mCalendarDateBannerTextColor;
- private static int mCalendarAmPmLabel;
- private static int mCalendarGridAreaSelected;
- private static int mCalendarGridLineInnerHorizontalColor;
- private static int mCalendarGridLineInnerVerticalColor;
- private static int mFutureBgColor;
- private static int mFutureBgColorRes;
- private static int mBgColor;
- private static int mNewEventHintColor;
- private static int mCalendarHourLabelColor;
- private static int mMoreAlldayEventsTextAlpha = MORE_EVENTS_MAX_ALPHA;
-
- private float mAnimationDistance = 0;
- private int mViewStartX;
- private int mViewStartY;
- private int mMaxViewStartY;
- private int mViewHeight;
- private int mViewWidth;
- private int mGridAreaHeight = -1;
- private static int mCellHeight = 0; // shared among all DayViews
- private static int mMinCellHeight = 32;
- private int mScrollStartY;
- private int mPreviousDirection;
- private static int mScaledPagingTouchSlop = 0;
-
- /**
- * Vertical distance or span between the two touch points at the start of a
- * scaling gesture
- */
- private float mStartingSpanY = 0;
- /** Height of 1 hour in pixels at the start of a scaling gesture */
- private int mCellHeightBeforeScaleGesture;
- /** The hour at the center two touch points */
- private float mGestureCenterHour = 0;
-
- private boolean mRecalCenterHour = false;
-
- /**
- * Flag to decide whether to handle the up event. Cases where up events
- * should be ignored are 1) right after a scale gesture and 2) finger was
- * down before app launch
- */
- private boolean mHandleActionUp = true;
-
- private int mHoursTextHeight;
- /**
- * The height of the area used for allday events
- */
- private int mAlldayHeight;
- /**
- * The height of the allday event area used during animation
- */
- private int mAnimateDayHeight = 0;
- /**
- * The height of an individual allday event during animation
- */
- private int mAnimateDayEventHeight = (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
- /**
- * Whether to use the expand or collapse icon.
- */
- private static boolean mUseExpandIcon = true;
- /**
- * The height of the day names/numbers
- */
- private static int DAY_HEADER_HEIGHT = 45;
- /**
- * The height of the day names/numbers for multi-day views
- */
- private static int MULTI_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT;
- /**
- * The height of the day names/numbers when viewing a single day
- */
- private static int ONE_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT;
- /**
- * Max of all day events in a given day in this view.
- */
- private int mMaxAlldayEvents;
- /**
- * A count of the number of allday events that were not drawn for each day
- */
- private int[] mSkippedAlldayEvents;
- /**
- * The number of allDay events at which point we start hiding allDay events.
- */
- private int mMaxUnexpandedAlldayEventCount = 4;
- /**
- * Whether or not to expand the allDay area to fill the screen
- */
- private static boolean mShowAllAllDayEvents = false;
-
- protected int mNumDays = 7;
- private int mNumHours = 10;
-
- /** Width of the time line (list of hours) to the left. */
- private int mHoursWidth;
- private int mDateStrWidth;
- /** Top of the scrollable region i.e. below date labels and all day events */
- private int mFirstCell;
- /** First fully visibile hour */
- private int mFirstHour = -1;
- /** Distance between the mFirstCell and the top of first fully visible hour. */
- private int mFirstHourOffset;
- private String[] mHourStrs;
- private String[] mDayStrs;
- private String[] mDayStrs2Letter;
- private boolean mIs24HourFormat;
-
- private final ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
- private boolean mComputeSelectedEvents;
- private boolean mUpdateToast;
- private Event mSelectedEvent;
- private Event mPrevSelectedEvent;
- private final Rect mPrevBox = new Rect();
- protected final Resources mResources;
- protected final Drawable mCurrentTimeLine;
- protected final Drawable mCurrentTimeAnimateLine;
- protected final Drawable mTodayHeaderDrawable;
- protected final Drawable mExpandAlldayDrawable;
- protected final Drawable mCollapseAlldayDrawable;
- protected Drawable mAcceptedOrTentativeEventBoxDrawable;
- private String mAmString;
- private String mPmString;
- private static int sCounter = 0;
-
- ScaleGestureDetector mScaleGestureDetector;
-
- /**
- * The initial state of the touch mode when we enter this view.
- */
- private static final int TOUCH_MODE_INITIAL_STATE = 0;
-
- /**
- * Indicates we just received the touch event and we are waiting to see if
- * it is a tap or a scroll gesture.
- */
- private static final int TOUCH_MODE_DOWN = 1;
-
- /**
- * Indicates the touch gesture is a vertical scroll
- */
- private static final int TOUCH_MODE_VSCROLL = 0x20;
-
- /**
- * Indicates the touch gesture is a horizontal scroll
- */
- private static final int TOUCH_MODE_HSCROLL = 0x40;
-
- private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
- /**
- * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
- */
- private static final int SELECTION_HIDDEN = 0;
- private static final int SELECTION_PRESSED = 1; // D-pad down but not up yet
- private static final int SELECTION_SELECTED = 2;
- private static final int SELECTION_LONGPRESS = 3;
-
- private int mSelectionMode = SELECTION_HIDDEN;
-
- private boolean mScrolling = false;
-
- // Pixels scrolled
- private float mInitialScrollX;
- private float mInitialScrollY;
-
- private boolean mAnimateToday = false;
- private int mAnimateTodayAlpha = 0;
-
- // Animates the height of the allday region
- ObjectAnimator mAlldayAnimator;
- // Animates the height of events in the allday region
- ObjectAnimator mAlldayEventAnimator;
- // Animates the transparency of the more events text
- ObjectAnimator mMoreAlldayEventsAnimator;
- // Animates the current time marker when Today is pressed
- ObjectAnimator mTodayAnimator;
- // whether or not an event is stopping because it was cancelled
- private boolean mCancellingAnimations = false;
- // tracks whether a touch originated in the allday area
- private boolean mTouchStartedInAlldayArea = false;
-
- private final CalendarController mController;
- private final ViewSwitcher mViewSwitcher;
- private final GestureDetector mGestureDetector;
- private final OverScroller mScroller;
- private final EdgeEffect mEdgeEffectTop;
- private final EdgeEffect mEdgeEffectBottom;
- private boolean mCallEdgeEffectOnAbsorb;
- private final int OVERFLING_DISTANCE;
- private float mLastVelocity;
-
- private final ScrollInterpolator mHScrollInterpolator;
- private AccessibilityManager mAccessibilityMgr = null;
- private boolean mIsAccessibilityEnabled = false;
- private boolean mTouchExplorationEnabled = false;
- private final String mNewEventHintString;
-
- public DayView(Context context, CalendarController controller,
- ViewSwitcher viewSwitcher, EventLoader eventLoader, int numDays) {
- super(context);
- mContext = context;
- initAccessibilityVariables();
-
- mResources = context.getResources();
- mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint);
- mNumDays = numDays;
-
- DATE_HEADER_FONT_SIZE = (int) mResources.getDimension(R.dimen.date_header_text_size);
- DAY_HEADER_FONT_SIZE = (int) mResources.getDimension(R.dimen.day_label_text_size);
- ONE_DAY_HEADER_HEIGHT = (int) mResources.getDimension(R.dimen.one_day_header_height);
- DAY_HEADER_BOTTOM_MARGIN = (int) mResources.getDimension(R.dimen.day_header_bottom_margin);
- EXPAND_ALL_DAY_BOTTOM_MARGIN = (int) mResources.getDimension(R.dimen.all_day_bottom_margin);
- HOURS_TEXT_SIZE = (int) mResources.getDimension(R.dimen.hours_text_size);
- AMPM_TEXT_SIZE = (int) mResources.getDimension(R.dimen.ampm_text_size);
- MIN_HOURS_WIDTH = (int) mResources.getDimension(R.dimen.min_hours_width);
- HOURS_LEFT_MARGIN = (int) mResources.getDimension(R.dimen.hours_left_margin);
- HOURS_RIGHT_MARGIN = (int) mResources.getDimension(R.dimen.hours_right_margin);
- MULTI_DAY_HEADER_HEIGHT = (int) mResources.getDimension(R.dimen.day_header_height);
- int eventTextSizeId;
- if (mNumDays == 1) {
- eventTextSizeId = R.dimen.day_view_event_text_size;
- } else {
- eventTextSizeId = R.dimen.week_view_event_text_size;
- }
- EVENT_TEXT_FONT_SIZE = (int) mResources.getDimension(eventTextSizeId);
- NEW_EVENT_HINT_FONT_SIZE = (int) mResources.getDimension(R.dimen.new_event_hint_text_size);
- MIN_EVENT_HEIGHT = mResources.getDimension(R.dimen.event_min_height);
- MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = MIN_EVENT_HEIGHT;
- EVENT_TEXT_TOP_MARGIN = (int) mResources.getDimension(R.dimen.event_text_vertical_margin);
- EVENT_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN;
- EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN;
- EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN;
-
- EVENT_TEXT_LEFT_MARGIN = (int) mResources
- .getDimension(R.dimen.event_text_horizontal_margin);
- EVENT_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
- EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
- EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
-
- if (mScale == 0) {
-
- mScale = mResources.getDisplayMetrics().density;
- if (mScale != 1) {
- SINGLE_ALLDAY_HEIGHT *= mScale;
- ALLDAY_TOP_MARGIN *= mScale;
- MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale;
-
- NORMAL_FONT_SIZE *= mScale;
- GRID_LINE_LEFT_MARGIN *= mScale;
- HOURS_TOP_MARGIN *= mScale;
- MIN_CELL_WIDTH_FOR_TEXT *= mScale;
- MAX_UNEXPANDED_ALLDAY_HEIGHT *= mScale;
- mAnimateDayEventHeight = (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-
- CURRENT_TIME_LINE_SIDE_BUFFER *= mScale;
- CURRENT_TIME_LINE_TOP_OFFSET *= mScale;
-
- MIN_Y_SPAN *= mScale;
- MAX_CELL_HEIGHT *= mScale;
- DEFAULT_CELL_HEIGHT *= mScale;
- DAY_HEADER_HEIGHT *= mScale;
- DAY_HEADER_RIGHT_MARGIN *= mScale;
- DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale;
- DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale;
- DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale;
- CALENDAR_COLOR_SQUARE_SIZE *= mScale;
- EVENT_RECT_TOP_MARGIN *= mScale;
- EVENT_RECT_BOTTOM_MARGIN *= mScale;
- ALL_DAY_EVENT_RECT_BOTTOM_MARGIN *= mScale;
- EVENT_RECT_LEFT_MARGIN *= mScale;
- EVENT_RECT_RIGHT_MARGIN *= mScale;
- EVENT_RECT_STROKE_WIDTH *= mScale;
- EVENT_SQUARE_WIDTH *= mScale;
- EVENT_LINE_PADDING *= mScale;
- NEW_EVENT_MARGIN *= mScale;
- NEW_EVENT_WIDTH *= mScale;
- NEW_EVENT_MAX_LENGTH *= mScale;
- }
- }
- HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
- DAY_HEADER_HEIGHT = mNumDays == 1 ? ONE_DAY_HEADER_HEIGHT : MULTI_DAY_HEADER_HEIGHT;
-
- mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_indicator_holo_light);
- mCurrentTimeAnimateLine = mResources
- .getDrawable(R.drawable.timeline_indicator_activated_holo_light);
- mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light);
- mExpandAlldayDrawable = mResources.getDrawable(R.drawable.ic_expand_holo_light);
- mCollapseAlldayDrawable = mResources.getDrawable(R.drawable.ic_collapse_holo_light);
- mNewEventHintColor = mResources.getColor(R.color.new_event_hint_text_color);
- mAcceptedOrTentativeEventBoxDrawable = mResources
- .getDrawable(R.drawable.panel_month_event_holo_light);
-
- mEventLoader = eventLoader;
- mEventGeometry = new EventGeometry();
- mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
- mEventGeometry.setHourGap(HOUR_GAP);
- mEventGeometry.setCellMargin(DAY_GAP);
- mLastPopupEventID = INVALID_EVENT_ID;
- mController = controller;
- mViewSwitcher = viewSwitcher;
- mGestureDetector = new GestureDetector(context, new CalendarGestureListener());
- mScaleGestureDetector = new ScaleGestureDetector(getContext(), this);
- if (mCellHeight == 0) {
- mCellHeight = Utils.getSharedPreference(mContext,
- GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT);
- }
- mScroller = new OverScroller(context);
- mHScrollInterpolator = new ScrollInterpolator();
- mEdgeEffectTop = new EdgeEffect(context);
- mEdgeEffectBottom = new EdgeEffect(context);
- ViewConfiguration vc = ViewConfiguration.get(context);
- mScaledPagingTouchSlop = vc.getScaledPagingTouchSlop();
- mOnDownDelay = ViewConfiguration.getTapTimeout();
- OVERFLING_DISTANCE = vc.getScaledOverflingDistance();
-
- init(context);
- }
-
- @Override
- protected void onAttachedToWindow() {
- if (mHandler == null) {
- mHandler = getHandler();
- mHandler.post(mUpdateCurrentTime);
- }
- }
-
- private void init(Context context) {
- setFocusable(true);
-
- // Allow focus in touch mode so that we can do keyboard shortcuts
- // even after we've entered touch mode.
- setFocusableInTouchMode(true);
- setClickable(true);
- setOnCreateContextMenuListener(this);
-
- mFirstDayOfWeek = Utils.getFirstDayOfWeek(context);
-
- mCurrentTime = new Time(Utils.getTimeZone(context, mTZUpdater));
- long currentTime = System.currentTimeMillis();
- mCurrentTime.set(currentTime);
- mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
-
- mWeek_saturdayColor = mResources.getColor(R.color.week_saturday);
- mWeek_sundayColor = mResources.getColor(R.color.week_sunday);
- mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color);
- mFutureBgColorRes = mResources.getColor(R.color.calendar_future_bg_color);
- mBgColor = mResources.getColor(R.color.calendar_hour_background);
- mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label);
- mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected);
- mCalendarGridLineInnerHorizontalColor = mResources
- .getColor(R.color.calendar_grid_line_inner_horizontal_color);
- mCalendarGridLineInnerVerticalColor = mResources
- .getColor(R.color.calendar_grid_line_inner_vertical_color);
- mCalendarHourLabelColor = mResources.getColor(R.color.calendar_hour_label);
- mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
- mMoreEventsTextColor = mResources.getColor(R.color.month_event_other_color);
-
- mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
- mEventTextPaint.setTextAlign(Paint.Align.LEFT);
- mEventTextPaint.setAntiAlias(true);
-
- int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
- Paint p = mSelectionPaint;
- p.setColor(gridLineColor);
- p.setStyle(Style.FILL);
- p.setAntiAlias(false);
-
- p = mPaint;
- p.setAntiAlias(true);
-
- // Allocate space for 2 weeks worth of weekday names so that we can
- // easily start the week display at any week day.
- mDayStrs = new String[14];
-
- // Also create an array of 2-letter abbreviations.
- mDayStrs2Letter = new String[14];
-
- for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
- int index = i - Calendar.SUNDAY;
- // e.g. Tue for Tuesday
- mDayStrs[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM)
- .toUpperCase();
- mDayStrs[index + 7] = mDayStrs[index];
- // e.g. Tu for Tuesday
- mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT)
- .toUpperCase();
-
- // If we don't have 2-letter day strings, fall back to 1-letter.
- if (mDayStrs2Letter[index].equals(mDayStrs[index])) {
- mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORTEST);
- }
-
- mDayStrs2Letter[index + 7] = mDayStrs2Letter[index];
- }
-
- // Figure out how much space we need for the 3-letter abbrev names
- // in the worst case.
- p.setTextSize(DATE_HEADER_FONT_SIZE);
- p.setTypeface(mBold);
- String[] dateStrs = {" 28", " 30"};
- mDateStrWidth = computeMaxStringWidth(0, dateStrs, p);
- p.setTextSize(DAY_HEADER_FONT_SIZE);
- mDateStrWidth += computeMaxStringWidth(0, mDayStrs, p);
-
- p.setTextSize(HOURS_TEXT_SIZE);
- p.setTypeface(null);
- handleOnResume();
-
- mAmString = DateUtils.getAMPMString(Calendar.AM).toUpperCase();
- mPmString = DateUtils.getAMPMString(Calendar.PM).toUpperCase();
- String[] ampm = {mAmString, mPmString};
- p.setTextSize(AMPM_TEXT_SIZE);
- mHoursWidth = Math.max(HOURS_MARGIN, computeMaxStringWidth(mHoursWidth, ampm, p)
- + HOURS_RIGHT_MARGIN);
- mHoursWidth = Math.max(MIN_HOURS_WIDTH, mHoursWidth);
-
- LayoutInflater inflater;
- inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPopupView = inflater.inflate(R.layout.bubble_event, null);
- mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mPopup = new PopupWindow(context);
- mPopup.setContentView(mPopupView);
- Resources.Theme dialogTheme = getResources().newTheme();
- dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
- TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
- android.R.attr.windowBackground });
- mPopup.setBackgroundDrawable(ta.getDrawable(0));
- ta.recycle();
-
- // Enable touching the popup window
- mPopupView.setOnClickListener(this);
- // Catch long clicks for creating a new event
- setOnLongClickListener(this);
-
- mBaseDate = new Time(Utils.getTimeZone(context, mTZUpdater));
- long millis = System.currentTimeMillis();
- mBaseDate.set(millis);
-
- mEarliestStartHour = new int[mNumDays];
- mHasAllDayEvent = new boolean[mNumDays];
-
- // mLines is the array of points used with Canvas.drawLines() in
- // drawGridBackground() and drawAllDayEvents(). Its size depends
- // on the max number of lines that can ever be drawn by any single
- // drawLines() call in either of those methods.
- final int maxGridLines = (24 + 1) // max horizontal lines we might draw
- + (mNumDays + 1); // max vertical lines we might draw
- mLines = new float[maxGridLines * 4];
- }
-
- /**
- * This is called when the popup window is pressed.
- */
- public void onClick(View v) {
- if (v == mPopupView) {
- // Pretend it was a trackball click because that will always
- // jump to the "View event" screen.
- switchViews(true /* trackball */);
- }
- }
-
- public void handleOnResume() {
- initAccessibilityVariables();
- if(Utils.getSharedPreference(mContext, OtherPreferences.KEY_OTHER_1, false)) {
- mFutureBgColor = 0;
- } else {
- mFutureBgColor = mFutureBgColorRes;
- }
- mIs24HourFormat = DateFormat.is24HourFormat(mContext);
- mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
- mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
- mLastSelectionDayForAccessibility = 0;
- mLastSelectionHourForAccessibility = 0;
- mLastSelectedEventForAccessibility = null;
- mSelectionMode = SELECTION_HIDDEN;
- }
-
- private void initAccessibilityVariables() {
- mAccessibilityMgr = (AccessibilityManager) mContext
- .getSystemService(Service.ACCESSIBILITY_SERVICE);
- mIsAccessibilityEnabled = mAccessibilityMgr != null && mAccessibilityMgr.isEnabled();
- mTouchExplorationEnabled = isTouchExplorationEnabled();
- }
-
- /**
- * Returns the start of the selected time in milliseconds since the epoch.
- *
- * @return selected time in UTC milliseconds since the epoch.
- */
- long getSelectedTimeInMillis() {
- Time time = new Time(mBaseDate);
- time.setJulianDay(mSelectionDay);
- time.hour = mSelectionHour;
-
- // We ignore the "isDst" field because we want normalize() to figure
- // out the correct DST value and not adjust the selected time based
- // on the current setting of DST.
- return time.normalize(true /* ignore isDst */);
- }
-
- Time getSelectedTime() {
- Time time = new Time(mBaseDate);
- time.setJulianDay(mSelectionDay);
- time.hour = mSelectionHour;
-
- // We ignore the "isDst" field because we want normalize() to figure
- // out the correct DST value and not adjust the selected time based
- // on the current setting of DST.
- time.normalize(true /* ignore isDst */);
- return time;
- }
-
- Time getSelectedTimeForAccessibility() {
- Time time = new Time(mBaseDate);
- time.setJulianDay(mSelectionDayForAccessibility);
- time.hour = mSelectionHourForAccessibility;
-
- // We ignore the "isDst" field because we want normalize() to figure
- // out the correct DST value and not adjust the selected time based
- // on the current setting of DST.
- time.normalize(true /* ignore isDst */);
- return time;
- }
-
- /**
- * Returns the start of the selected time in minutes since midnight,
- * local time. The derived class must ensure that this is consistent
- * with the return value from getSelectedTimeInMillis().
- */
- int getSelectedMinutesSinceMidnight() {
- return mSelectionHour * MINUTES_PER_HOUR;
- }
-
- int getFirstVisibleHour() {
- return mFirstHour;
- }
-
- void setFirstVisibleHour(int firstHour) {
- mFirstHour = firstHour;
- mFirstHourOffset = 0;
- }
-
- public void setSelected(Time time, boolean ignoreTime, boolean animateToday) {
- mBaseDate.set(time);
- setSelectedHour(mBaseDate.hour);
- setSelectedEvent(null);
- mPrevSelectedEvent = null;
- long millis = mBaseDate.toMillis(false /* use isDst */);
- setSelectedDay(Time.getJulianDay(millis, mBaseDate.gmtoff));
- mSelectedEvents.clear();
- mComputeSelectedEvents = true;
-
- int gotoY = Integer.MIN_VALUE;
-
- if (!ignoreTime && mGridAreaHeight != -1) {
- int lastHour = 0;
-
- if (mBaseDate.hour < mFirstHour) {
- // Above visible region
- gotoY = mBaseDate.hour * (mCellHeight + HOUR_GAP);
- } else {
- lastHour = (mGridAreaHeight - mFirstHourOffset) / (mCellHeight + HOUR_GAP)
- + mFirstHour;
-
- if (mBaseDate.hour >= lastHour) {
- // Below visible region
-
- // target hour + 1 (to give it room to see the event) -
- // grid height (to get the y of the top of the visible
- // region)
- gotoY = (int) ((mBaseDate.hour + 1 + mBaseDate.minute / 60.0f)
- * (mCellHeight + HOUR_GAP) - mGridAreaHeight);
- }
- }
-
- if (DEBUG) {
- Log.e(TAG, "Go " + gotoY + " 1st " + mFirstHour + ":" + mFirstHourOffset + "CH "
- + (mCellHeight + HOUR_GAP) + " lh " + lastHour + " gh " + mGridAreaHeight
- + " ymax " + mMaxViewStartY);
- }
-
- if (gotoY > mMaxViewStartY) {
- gotoY = mMaxViewStartY;
- } else if (gotoY < 0 && gotoY != Integer.MIN_VALUE) {
- gotoY = 0;
- }
- }
-
- recalc();
-
- mRemeasure = true;
- invalidate();
-
- boolean delayAnimateToday = false;
- if (gotoY != Integer.MIN_VALUE) {
- ValueAnimator scrollAnim = ObjectAnimator.ofInt(this, "viewStartY", mViewStartY, gotoY);
- scrollAnim.setDuration(GOTO_SCROLL_DURATION);
- scrollAnim.setInterpolator(new AccelerateDecelerateInterpolator());
- scrollAnim.addListener(mAnimatorListener);
- scrollAnim.start();
- delayAnimateToday = true;
- }
- if (animateToday) {
- synchronized (mTodayAnimatorListener) {
- if (mTodayAnimator != null) {
- mTodayAnimator.removeAllListeners();
- mTodayAnimator.cancel();
- }
- mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
- mAnimateTodayAlpha, 255);
- mAnimateToday = true;
- mTodayAnimatorListener.setFadingIn(true);
- mTodayAnimatorListener.setAnimator(mTodayAnimator);
- mTodayAnimator.addListener(mTodayAnimatorListener);
- mTodayAnimator.setDuration(150);
- if (delayAnimateToday) {
- mTodayAnimator.setStartDelay(GOTO_SCROLL_DURATION);
- }
- mTodayAnimator.start();
- }
- }
- sendAccessibilityEventAsNeeded(false);
- }
-
- // Called from animation framework via reflection. Do not remove
- public void setViewStartY(int viewStartY) {
- if (viewStartY > mMaxViewStartY) {
- viewStartY = mMaxViewStartY;
- }
-
- mViewStartY = viewStartY;
-
- computeFirstHour();
- invalidate();
- }
-
- public void setAnimateTodayAlpha(int todayAlpha) {
- mAnimateTodayAlpha = todayAlpha;
- invalidate();
- }
-
- public Time getSelectedDay() {
- Time time = new Time(mBaseDate);
- time.setJulianDay(mSelectionDay);
- time.hour = mSelectionHour;
-
- // We ignore the "isDst" field because we want normalize() to figure
- // out the correct DST value and not adjust the selected time based
- // on the current setting of DST.
- time.normalize(true /* ignore isDst */);
- return time;
- }
-
- public void updateTitle() {
- Time start = new Time(mBaseDate);
- start.normalize(true);
- Time end = new Time(start);
- end.monthDay += mNumDays - 1;
- // Move it forward one minute so the formatter doesn't lose a day
- end.minute += 1;
- end.normalize(true);
-
- long formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
- if (mNumDays != 1) {
- // Don't show day of the month if for multi-day view
- formatFlags |= DateUtils.FORMAT_NO_MONTH_DAY;
-
- // Abbreviate the month if showing multiple months
- if (start.month != end.month) {
- formatFlags |= DateUtils.FORMAT_ABBREV_MONTH;
- }
- }
-
- mController.sendEvent(this, EventType.UPDATE_TITLE, start, end, null, -1, ViewType.CURRENT,
- formatFlags, null, null);
- }
-
- /**
- * return a negative number if "time" is comes before the visible time
- * range, a positive number if "time" is after the visible time range, and 0
- * if it is in the visible time range.
- */
- public int compareToVisibleTimeRange(Time time) {
-
- int savedHour = mBaseDate.hour;
- int savedMinute = mBaseDate.minute;
- int savedSec = mBaseDate.second;
-
- mBaseDate.hour = 0;
- mBaseDate.minute = 0;
- mBaseDate.second = 0;
-
- if (DEBUG) {
- Log.d(TAG, "Begin " + mBaseDate.toString());
- Log.d(TAG, "Diff " + time.toString());
- }
-
- // Compare beginning of range
- int diff = Time.compare(time, mBaseDate);
- if (diff > 0) {
- // Compare end of range
- mBaseDate.monthDay += mNumDays;
- mBaseDate.normalize(true);
- diff = Time.compare(time, mBaseDate);
-
- if (DEBUG) Log.d(TAG, "End " + mBaseDate.toString());
-
- mBaseDate.monthDay -= mNumDays;
- mBaseDate.normalize(true);
- if (diff < 0) {
- // in visible time
- diff = 0;
- } else if (diff == 0) {
- // Midnight of following day
- diff = 1;
- }
- }
-
- if (DEBUG) Log.d(TAG, "Diff: " + diff);
-
- mBaseDate.hour = savedHour;
- mBaseDate.minute = savedMinute;
- mBaseDate.second = savedSec;
- return diff;
- }
-
- private void recalc() {
- // Set the base date to the beginning of the week if we are displaying
- // 7 days at a time.
- if (mNumDays == 7) {
- adjustToBeginningOfWeek(mBaseDate);
- }
-
- final long start = mBaseDate.toMillis(false /* use isDst */);
- mFirstJulianDay = Time.getJulianDay(start, mBaseDate.gmtoff);
- mLastJulianDay = mFirstJulianDay + mNumDays - 1;
-
- mMonthLength = mBaseDate.getActualMaximum(Time.MONTH_DAY);
- mFirstVisibleDate = mBaseDate.monthDay;
- mFirstVisibleDayOfWeek = mBaseDate.weekDay;
- }
-
- private void adjustToBeginningOfWeek(Time time) {
- int dayOfWeek = time.weekDay;
- int diff = dayOfWeek - mFirstDayOfWeek;
- if (diff != 0) {
- if (diff < 0) {
- diff += 7;
- }
- time.monthDay -= diff;
- time.normalize(true /* ignore isDst */);
- }
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldw, int oldh) {
- mViewWidth = width;
- mViewHeight = height;
- mEdgeEffectTop.setSize(mViewWidth, mViewHeight);
- mEdgeEffectBottom.setSize(mViewWidth, mViewHeight);
- int gridAreaWidth = width - mHoursWidth;
- mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
-
- // This would be about 1 day worth in a 7 day view
- mHorizontalSnapBackThreshold = width / 7;
-
- Paint p = new Paint();
- p.setTextSize(HOURS_TEXT_SIZE);
- mHoursTextHeight = (int) Math.abs(p.ascent());
- remeasure(width, height);
- }
-
- /**
- * Measures the space needed for various parts of the view after
- * loading new events. This can change if there are all-day events.
- */
- private void remeasure(int width, int height) {
- // Shrink to fit available space but make sure we can display at least two events
- MAX_UNEXPANDED_ALLDAY_HEIGHT = (int) (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4);
- MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.min(MAX_UNEXPANDED_ALLDAY_HEIGHT, height / 6);
- MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.max(MAX_UNEXPANDED_ALLDAY_HEIGHT,
- (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 2);
- mMaxUnexpandedAlldayEventCount =
- (int) (MAX_UNEXPANDED_ALLDAY_HEIGHT / MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
-
- // First, clear the array of earliest start times, and the array
- // indicating presence of an all-day event.
- for (int day = 0; day < mNumDays; day++) {
- mEarliestStartHour[day] = 25; // some big number
- mHasAllDayEvent[day] = false;
- }
-
- int maxAllDayEvents = mMaxAlldayEvents;
-
- // The min is where 24 hours cover the entire visible area
- mMinCellHeight = Math.max((height - DAY_HEADER_HEIGHT) / 24, (int) MIN_EVENT_HEIGHT);
- if (mCellHeight < mMinCellHeight) {
- mCellHeight = mMinCellHeight;
- }
-
- // Calculate mAllDayHeight
- mFirstCell = DAY_HEADER_HEIGHT;
- int allDayHeight = 0;
- if (maxAllDayEvents > 0) {
- int maxAllAllDayHeight = height - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
- // If there is at most one all-day event per day, then use less
- // space (but more than the space for a single event).
- if (maxAllDayEvents == 1) {
- allDayHeight = SINGLE_ALLDAY_HEIGHT;
- } else if (maxAllDayEvents <= mMaxUnexpandedAlldayEventCount){
- // Allow the all-day area to grow in height depending on the
- // number of all-day events we need to show, up to a limit.
- allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
- if (allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
- allDayHeight = MAX_UNEXPANDED_ALLDAY_HEIGHT;
- }
- } else {
- // if we have more than the magic number, check if we're animating
- // and if not adjust the sizes appropriately
- if (mAnimateDayHeight != 0) {
- // Don't shrink the space past the final allDay space. The animation
- // continues to hide the last event so the more events text can
- // fade in.
- allDayHeight = Math.max(mAnimateDayHeight, MAX_UNEXPANDED_ALLDAY_HEIGHT);
- } else {
- // Try to fit all the events in
- allDayHeight = (int) (maxAllDayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
- // But clip the area depending on which mode we're in
- if (!mShowAllAllDayEvents && allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
- allDayHeight = (int) (mMaxUnexpandedAlldayEventCount *
- MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
- } else if (allDayHeight > maxAllAllDayHeight) {
- allDayHeight = maxAllAllDayHeight;
- }
- }
- }
- mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN;
- } else {
- mSelectionAllday = false;
- }
- mAlldayHeight = allDayHeight;
-
- mGridAreaHeight = height - mFirstCell;
-
- // Set up the expand icon position
- int allDayIconWidth = mExpandAlldayDrawable.getIntrinsicWidth();
- mExpandAllDayRect.left = Math.max((mHoursWidth - allDayIconWidth) / 2,
- EVENT_ALL_DAY_TEXT_LEFT_MARGIN);
- mExpandAllDayRect.right = Math.min(mExpandAllDayRect.left + allDayIconWidth, mHoursWidth
- - EVENT_ALL_DAY_TEXT_RIGHT_MARGIN);
- mExpandAllDayRect.bottom = mFirstCell - EXPAND_ALL_DAY_BOTTOM_MARGIN;
- mExpandAllDayRect.top = mExpandAllDayRect.bottom
- - mExpandAlldayDrawable.getIntrinsicHeight();
-
- mNumHours = mGridAreaHeight / (mCellHeight + HOUR_GAP);
- mEventGeometry.setHourHeight(mCellHeight);
-
- final long minimumDurationMillis = (long)
- (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f));
- Event.computePositions(mEvents, minimumDurationMillis);
-
- // Compute the top of our reachable view
- mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
- if (DEBUG) {
- Log.e(TAG, "mViewStartY: " + mViewStartY);
- Log.e(TAG, "mMaxViewStartY: " + mMaxViewStartY);
- }
- if (mViewStartY > mMaxViewStartY) {
- mViewStartY = mMaxViewStartY;
- computeFirstHour();
- }
-
- if (mFirstHour == -1) {
- initFirstHour();
- mFirstHourOffset = 0;
- }
-
- // When we change the base date, the number of all-day events may
- // change and that changes the cell height. When we switch dates,
- // we use the mFirstHourOffset from the previous view, but that may
- // be too large for the new view if the cell height is smaller.
- if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
- mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
- }
- mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
-
- final int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
- //When we get new events we don't want to dismiss the popup unless the event changes
- if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent.id) {
- mPopup.dismiss();
- }
- mPopup.setWidth(eventAreaWidth - 20);
- mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
- }
-
- /**
- * Initialize the state for another view. The given view is one that has
- * its own bitmap and will use an animation to replace the current view.
- * The current view and new view are either both Week views or both Day
- * views. They differ in their base date.
- *
- * @param view the view to initialize.
- */
- private void initView(DayView view) {
- view.setSelectedHour(mSelectionHour);
- view.mSelectedEvents.clear();
- view.mComputeSelectedEvents = true;
- view.mFirstHour = mFirstHour;
- view.mFirstHourOffset = mFirstHourOffset;
- view.remeasure(getWidth(), getHeight());
- view.initAllDayHeights();
-
- view.setSelectedEvent(null);
- view.mPrevSelectedEvent = null;
- view.mFirstDayOfWeek = mFirstDayOfWeek;
- if (view.mEvents.size() > 0) {
- view.mSelectionAllday = mSelectionAllday;
- } else {
- view.mSelectionAllday = false;
- }
-
- // Redraw the screen so that the selection box will be redrawn. We may
- // have scrolled to a different part of the day in some other view
- // so the selection box in this view may no longer be visible.
- view.recalc();
- }
-
- /**
- * Switch to another view based on what was selected (an event or a free
- * slot) and how it was selected (by touch or by trackball).
- *
- * @param trackBallSelection true if the selection was made using the
- * trackball.
- */
- private void switchViews(boolean trackBallSelection) {
- Event selectedEvent = mSelectedEvent;
-
- mPopup.dismiss();
- mLastPopupEventID = INVALID_EVENT_ID;
- if (mNumDays > 1) {
- // This is the Week view.
- // With touch, we always switch to Day/Agenda View
- // With track ball, if we selected a free slot, then create an event.
- // If we selected a specific event, switch to EventInfo view.
- if (trackBallSelection) {
- if (selectedEvent != null) {
- if (mIsAccessibilityEnabled) {
- mAccessibilityMgr.interrupt();
- }
- }
- }
- }
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- mScrolling = false;
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- return super.onKeyDown(keyCode, event);
- }
-
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- return true;
- }
-
- private boolean isTouchExplorationEnabled() {
- return mIsAccessibilityEnabled && mAccessibilityMgr.isTouchExplorationEnabled();
- }
-
- private void sendAccessibilityEventAsNeeded(boolean speakEvents) {
- if (!mIsAccessibilityEnabled) {
- return;
- }
- boolean dayChanged = mLastSelectionDayForAccessibility != mSelectionDayForAccessibility;
- boolean hourChanged = mLastSelectionHourForAccessibility != mSelectionHourForAccessibility;
- if (dayChanged || hourChanged ||
- mLastSelectedEventForAccessibility != mSelectedEventForAccessibility) {
- mLastSelectionDayForAccessibility = mSelectionDayForAccessibility;
- mLastSelectionHourForAccessibility = mSelectionHourForAccessibility;
- mLastSelectedEventForAccessibility = mSelectedEventForAccessibility;
-
- StringBuilder b = new StringBuilder();
-
- // Announce only the changes i.e. day or hour or both
- if (dayChanged) {
- b.append(getSelectedTimeForAccessibility().format("%A "));
- }
- if (hourChanged) {
- b.append(getSelectedTimeForAccessibility().format(mIs24HourFormat ? "%k" : "%l%p"));
- }
- if (dayChanged || hourChanged) {
- b.append(PERIOD_SPACE);
- }
-
- if (speakEvents) {
- if (mEventCountTemplate == null) {
- mEventCountTemplate = mContext.getString(R.string.template_announce_item_index);
- }
-
- // Read out the relevant event(s)
- int numEvents = mSelectedEvents.size();
- if (numEvents > 0) {
- if (mSelectedEventForAccessibility == null) {
- // Read out all the events
- int i = 1;
- for (Event calEvent : mSelectedEvents) {
- if (numEvents > 1) {
- // Read out x of numEvents if there are more than one event
- mStringBuilder.setLength(0);
- b.append(mFormatter.format(mEventCountTemplate, i++, numEvents));
- b.append(" ");
- }
- appendEventAccessibilityString(b, calEvent);
- }
- } else {
- if (numEvents > 1) {
- // Read out x of numEvents if there are more than one event
- mStringBuilder.setLength(0);
- b.append(mFormatter.format(mEventCountTemplate, mSelectedEvents
- .indexOf(mSelectedEventForAccessibility) + 1, numEvents));
- b.append(" ");
- }
- appendEventAccessibilityString(b, mSelectedEventForAccessibility);
- }
- }
- }
-
- if (dayChanged || hourChanged || speakEvents) {
- AccessibilityEvent event = AccessibilityEvent
- .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- CharSequence msg = b.toString();
- event.getText().add(msg);
- event.setAddedCount(msg.length());
- sendAccessibilityEventUnchecked(event);
- }
- }
- }
-
- /**
- * @param b
- * @param calEvent
- */
- private void appendEventAccessibilityString(StringBuilder b, Event calEvent) {
- b.append(calEvent.getTitleAndLocation());
- b.append(PERIOD_SPACE);
- String when;
- int flags = DateUtils.FORMAT_SHOW_DATE;
- if (calEvent.allDay) {
- flags |= DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY;
- } else {
- flags |= DateUtils.FORMAT_SHOW_TIME;
- if (DateFormat.is24HourFormat(mContext)) {
- flags |= DateUtils.FORMAT_24HOUR;
- }
- }
- when = Utils.formatDateRange(mContext, calEvent.startMillis, calEvent.endMillis, flags);
- b.append(when);
- b.append(PERIOD_SPACE);
- }
-
- private class GotoBroadcaster implements Animation.AnimationListener {
- private final int mCounter;
- private final Time mStart;
- private final Time mEnd;
-
- public GotoBroadcaster(Time start, Time end) {
- mCounter = ++sCounter;
- mStart = start;
- mEnd = end;
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- view.mViewStartX = 0;
- view = (DayView) mViewSwitcher.getNextView();
- view.mViewStartX = 0;
-
- if (mCounter == sCounter) {
- mController.sendEvent(this, EventType.GO_TO, mStart, mEnd, null, -1,
- ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
- }
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
-
- @Override
- public void onAnimationStart(Animation animation) {
- }
- }
-
- private View switchViews(boolean forward, float xOffSet, float width, float velocity) {
- mAnimationDistance = width - xOffSet;
- if (DEBUG) {
- Log.d(TAG, "switchViews(" + forward + ") O:" + xOffSet + " Dist:" + mAnimationDistance);
- }
-
- float progress = Math.abs(xOffSet) / width;
- if (progress > 1.0f) {
- progress = 1.0f;
- }
-
- float inFromXValue, inToXValue;
- float outFromXValue, outToXValue;
- if (forward) {
- inFromXValue = 1.0f - progress;
- inToXValue = 0.0f;
- outFromXValue = -progress;
- outToXValue = -1.0f;
- } else {
- inFromXValue = progress - 1.0f;
- inToXValue = 0.0f;
- outFromXValue = progress;
- outToXValue = 1.0f;
- }
-
- final Time start = new Time(mBaseDate.timezone);
- start.set(mController.getTime());
- if (forward) {
- start.monthDay += mNumDays;
- } else {
- start.monthDay -= mNumDays;
- }
- mController.setTime(start.normalize(true));
-
- Time newSelected = start;
-
- if (mNumDays == 7) {
- newSelected = new Time(start);
- adjustToBeginningOfWeek(start);
- }
-
- final Time end = new Time(start);
- end.monthDay += mNumDays - 1;
-
- // We have to allocate these animation objects each time we switch views
- // because that is the only way to set the animation parameters.
- TranslateAnimation inAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, inFromXValue,
- Animation.RELATIVE_TO_SELF, inToXValue,
- Animation.ABSOLUTE, 0.0f,
- Animation.ABSOLUTE, 0.0f);
-
- TranslateAnimation outAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, outFromXValue,
- Animation.RELATIVE_TO_SELF, outToXValue,
- Animation.ABSOLUTE, 0.0f,
- Animation.ABSOLUTE, 0.0f);
-
- long duration = calculateDuration(width - Math.abs(xOffSet), width, velocity);
- inAnimation.setDuration(duration);
- inAnimation.setInterpolator(mHScrollInterpolator);
- outAnimation.setInterpolator(mHScrollInterpolator);
- outAnimation.setDuration(duration);
- outAnimation.setAnimationListener(new GotoBroadcaster(start, end));
- mViewSwitcher.setInAnimation(inAnimation);
- mViewSwitcher.setOutAnimation(outAnimation);
-
- DayView view = (DayView) mViewSwitcher.getCurrentView();
- view.cleanup();
- mViewSwitcher.showNext();
- view = (DayView) mViewSwitcher.getCurrentView();
- view.setSelected(newSelected, true, false);
- view.requestFocus();
- view.reloadEvents();
- view.updateTitle();
- view.restartCurrentTimeUpdates();
-
- return view;
- }
-
- // This is called after scrolling stops to move the selected hour
- // to the visible part of the screen.
- private void resetSelectedHour() {
- if (mSelectionHour < mFirstHour + 1) {
- setSelectedHour(mFirstHour + 1);
- setSelectedEvent(null);
- mSelectedEvents.clear();
- mComputeSelectedEvents = true;
- } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
- setSelectedHour(mFirstHour + mNumHours - 3);
- setSelectedEvent(null);
- mSelectedEvents.clear();
- mComputeSelectedEvents = true;
- }
- }
-
- private void initFirstHour() {
- mFirstHour = mSelectionHour - mNumHours / 5;
- if (mFirstHour < 0) {
- mFirstHour = 0;
- } else if (mFirstHour + mNumHours > 24) {
- mFirstHour = 24 - mNumHours;
- }
- }
-
- /**
- * Recomputes the first full hour that is visible on screen after the
- * screen is scrolled.
- */
- private void computeFirstHour() {
- // Compute the first full hour that is visible on screen
- mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP);
- mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY;
- }
-
- private void adjustHourSelection() {
- if (mSelectionHour < 0) {
- setSelectedHour(0);
- if (mMaxAlldayEvents > 0) {
- mPrevSelectedEvent = null;
- mSelectionAllday = true;
- }
- }
-
- if (mSelectionHour > 23) {
- setSelectedHour(23);
- }
-
- // If the selected hour is at least 2 time slots from the top and
- // bottom of the screen, then don't scroll the view.
- if (mSelectionHour < mFirstHour + 1) {
- // If there are all-days events for the selected day but there
- // are no more normal events earlier in the day, then jump to
- // the all-day event area.
- // Exception 1: allow the user to scroll to 8am with the trackball
- // before jumping to the all-day event area.
- // Exception 2: if 12am is on screen, then allow the user to select
- // 12am before going up to the all-day event area.
- int daynum = mSelectionDay - mFirstJulianDay;
- if (daynum < mEarliestStartHour.length && daynum >= 0
- && mMaxAlldayEvents > 0
- && mEarliestStartHour[daynum] > mSelectionHour
- && mFirstHour > 0 && mFirstHour < 8) {
- mPrevSelectedEvent = null;
- mSelectionAllday = true;
- setSelectedHour(mFirstHour + 1);
- return;
- }
-
- if (mFirstHour > 0) {
- mFirstHour -= 1;
- mViewStartY -= (mCellHeight + HOUR_GAP);
- if (mViewStartY < 0) {
- mViewStartY = 0;
- }
- return;
- }
- }
-
- if (mSelectionHour > mFirstHour + mNumHours - 3) {
- if (mFirstHour < 24 - mNumHours) {
- mFirstHour += 1;
- mViewStartY += (mCellHeight + HOUR_GAP);
- if (mViewStartY > mMaxViewStartY) {
- mViewStartY = mMaxViewStartY;
- }
- return;
- } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
- mViewStartY = mMaxViewStartY;
- }
- }
- }
-
- void clearCachedEvents() {
- mLastReloadMillis = 0;
- }
-
- private final Runnable mCancelCallback = new Runnable() {
- public void run() {
- clearCachedEvents();
- }
- };
-
- /* package */ void reloadEvents() {
- // Protect against this being called before this view has been
- // initialized.
-// if (mContext == null) {
-// return;
-// }
-
- // Make sure our time zones are up to date
- mTZUpdater.run();
-
- setSelectedEvent(null);
- mPrevSelectedEvent = null;
- mSelectedEvents.clear();
-
- // The start date is the beginning of the week at 12am
- Time weekStart = new Time(Utils.getTimeZone(mContext, mTZUpdater));
- weekStart.set(mBaseDate);
- weekStart.hour = 0;
- weekStart.minute = 0;
- weekStart.second = 0;
- long millis = weekStart.normalize(true /* ignore isDst */);
-
- // Avoid reloading events unnecessarily.
- if (millis == mLastReloadMillis) {
- return;
- }
- mLastReloadMillis = millis;
-
- // load events in the background
-// mContext.startProgressSpinner();
- final ArrayList<Event> events = new ArrayList<Event>();
- mEventLoader.loadEventsInBackground(mNumDays, events, mFirstJulianDay, new Runnable() {
-
- public void run() {
- boolean fadeinEvents = mFirstJulianDay != mLoadedFirstJulianDay;
- mEvents = events;
- mLoadedFirstJulianDay = mFirstJulianDay;
- if (mAllDayEvents == null) {
- mAllDayEvents = new ArrayList<Event>();
- } else {
- mAllDayEvents.clear();
- }
-
- // Create a shorter array for all day events
- for (Event e : events) {
- if (e.drawAsAllday()) {
- mAllDayEvents.add(e);
- }
- }
-
- // New events, new layouts
- if (mLayouts == null || mLayouts.length < events.size()) {
- mLayouts = new StaticLayout[events.size()];
- } else {
- Arrays.fill(mLayouts, null);
- }
-
- if (mAllDayLayouts == null || mAllDayLayouts.length < mAllDayEvents.size()) {
- mAllDayLayouts = new StaticLayout[events.size()];
- } else {
- Arrays.fill(mAllDayLayouts, null);
- }
-
- computeEventRelations();
-
- mRemeasure = true;
- mComputeSelectedEvents = true;
- recalc();
-
- // Start animation to cross fade the events
- if (fadeinEvents) {
- if (mEventsCrossFadeAnimation == null) {
- mEventsCrossFadeAnimation =
- ObjectAnimator.ofInt(DayView.this, "EventsAlpha", 0, 255);
- mEventsCrossFadeAnimation.setDuration(EVENTS_CROSS_FADE_DURATION);
- }
- mEventsCrossFadeAnimation.start();
- } else{
- invalidate();
- }
- }
- }, mCancelCallback);
- }
-
- public void setEventsAlpha(int alpha) {
- mEventsAlpha = alpha;
- invalidate();
- }
-
- public int getEventsAlpha() {
- return mEventsAlpha;
- }
-
- public void stopEventsAnimation() {
- if (mEventsCrossFadeAnimation != null) {
- mEventsCrossFadeAnimation.cancel();
- }
- mEventsAlpha = 255;
- }
-
- private void computeEventRelations() {
- // Compute the layout relation between each event before measuring cell
- // width, as the cell width should be adjusted along with the relation.
- //
- // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
- // We should mark them as "overwapped". Though they are not overwapped logically, but
- // minimum cell height implicitly expands the cell height of A and it should look like
- // (1:00pm - 1:15pm) after the cell height adjustment.
-
- // Compute the space needed for the all-day events, if any.
- // Make a pass over all the events, and keep track of the maximum
- // number of all-day events in any one day. Also, keep track of
- // the earliest event in each day.
- int maxAllDayEvents = 0;
- final ArrayList<Event> events = mEvents;
- final int len = events.size();
- // Num of all-day-events on each day.
- final int eventsCount[] = new int[mLastJulianDay - mFirstJulianDay + 1];
- Arrays.fill(eventsCount, 0);
- for (int ii = 0; ii < len; ii++) {
- Event event = events.get(ii);
- if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
- continue;
- }
- if (event.drawAsAllday()) {
- // Count all the events being drawn as allDay events
- final int firstDay = Math.max(event.startDay, mFirstJulianDay);
- final int lastDay = Math.min(event.endDay, mLastJulianDay);
- for (int day = firstDay; day <= lastDay; day++) {
- final int count = ++eventsCount[day - mFirstJulianDay];
- if (maxAllDayEvents < count) {
- maxAllDayEvents = count;
- }
- }
-
- int daynum = event.startDay - mFirstJulianDay;
- int durationDays = event.endDay - event.startDay + 1;
- if (daynum < 0) {
- durationDays += daynum;
- daynum = 0;
- }
- if (daynum + durationDays > mNumDays) {
- durationDays = mNumDays - daynum;
- }
- for (int day = daynum; durationDays > 0; day++, durationDays--) {
- mHasAllDayEvent[day] = true;
- }
- } else {
- int daynum = event.startDay - mFirstJulianDay;
- int hour = event.startTime / 60;
- if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
- mEarliestStartHour[daynum] = hour;
- }
-
- // Also check the end hour in case the event spans more than
- // one day.
- daynum = event.endDay - mFirstJulianDay;
- hour = event.endTime / 60;
- if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
- mEarliestStartHour[daynum] = hour;
- }
- }
- }
- mMaxAlldayEvents = maxAllDayEvents;
- initAllDayHeights();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mRemeasure) {
- remeasure(getWidth(), getHeight());
- mRemeasure = false;
- }
- canvas.save();
-
- float yTranslate = -mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight;
- // offset canvas by the current drag and header position
- canvas.translate(-mViewStartX, yTranslate);
- // clip to everything below the allDay area
- Rect dest = mDestRect;
- dest.top = (int) (mFirstCell - yTranslate);
- dest.bottom = (int) (mViewHeight - yTranslate);
- dest.left = 0;
- dest.right = mViewWidth;
- canvas.save();
- canvas.clipRect(dest);
- // Draw the movable part of the view
- doDraw(canvas);
- // restore to having no clip
- canvas.restore();
-
- if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
- float xTranslate;
- if (mViewStartX > 0) {
- xTranslate = mViewWidth;
- } else {
- xTranslate = -mViewWidth;
- }
- // Move the canvas around to prep it for the next view
- // specifically, shift it by a screen and undo the
- // yTranslation which will be redone in the nextView's onDraw().
- canvas.translate(xTranslate, -yTranslate);
- DayView nextView = (DayView) mViewSwitcher.getNextView();
-
- // Prevent infinite recursive calls to onDraw().
- nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
- nextView.onDraw(canvas);
- // Move it back for this view
- canvas.translate(-xTranslate, 0);
- } else {
- // If we drew another view we already translated it back
- // If we didn't draw another view we should be at the edge of the
- // screen
- canvas.translate(mViewStartX, -yTranslate);
- }
-
- // Draw the fixed areas (that don't scroll) directly to the canvas.
- drawAfterScroll(canvas);
- if (mComputeSelectedEvents && mUpdateToast) {
- mUpdateToast = false;
- }
- mComputeSelectedEvents = false;
-
- // Draw overscroll glow
- if (!mEdgeEffectTop.isFinished()) {
- if (DAY_HEADER_HEIGHT != 0) {
- canvas.translate(0, DAY_HEADER_HEIGHT);
- }
- if (mEdgeEffectTop.draw(canvas)) {
- invalidate();
- }
- if (DAY_HEADER_HEIGHT != 0) {
- canvas.translate(0, -DAY_HEADER_HEIGHT);
- }
- }
- if (!mEdgeEffectBottom.isFinished()) {
- canvas.rotate(180, mViewWidth/2, mViewHeight/2);
- if (mEdgeEffectBottom.draw(canvas)) {
- invalidate();
- }
- }
- canvas.restore();
- }
-
- private void drawAfterScroll(Canvas canvas) {
- Paint p = mPaint;
- Rect r = mRect;
-
- drawAllDayHighlights(r, canvas, p);
- if (mMaxAlldayEvents != 0) {
- drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p);
- drawUpperLeftCorner(r, canvas, p);
- }
-
- drawScrollLine(r, canvas, p);
- drawDayHeaderLoop(r, canvas, p);
-
- // Draw the AM and PM indicators if we're in 12 hour mode
- if (!mIs24HourFormat) {
- drawAmPm(canvas, p);
- }
- }
-
- // This isn't really the upper-left corner. It's the square area just
- // below the upper-left corner, above the hours and to the left of the
- // all-day area.
- private void drawUpperLeftCorner(Rect r, Canvas canvas, Paint p) {
- setupHourTextPaint(p);
- if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
- // Draw the allDay expand/collapse icon
- if (mUseExpandIcon) {
- mExpandAlldayDrawable.setBounds(mExpandAllDayRect);
- mExpandAlldayDrawable.draw(canvas);
- } else {
- mCollapseAlldayDrawable.setBounds(mExpandAllDayRect);
- mCollapseAlldayDrawable.draw(canvas);
- }
- }
- }
-
- private void drawScrollLine(Rect r, Canvas canvas, Paint p) {
- final int right = computeDayLeftPosition(mNumDays);
- final int y = mFirstCell - 1;
-
- p.setAntiAlias(false);
- p.setStyle(Style.FILL);
-
- p.setColor(mCalendarGridLineInnerHorizontalColor);
- p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
- canvas.drawLine(GRID_LINE_LEFT_MARGIN, y, right, y, p);
- p.setAntiAlias(true);
- }
-
- // Computes the x position for the left side of the given day (base 0)
- private int computeDayLeftPosition(int day) {
- int effectiveWidth = mViewWidth - mHoursWidth;
- return day * effectiveWidth / mNumDays + mHoursWidth;
- }
-
- private void drawAllDayHighlights(Rect r, Canvas canvas, Paint p) {
- if (mFutureBgColor != 0) {
- // First, color the labels area light gray
- r.top = 0;
- r.bottom = DAY_HEADER_HEIGHT;
- r.left = 0;
- r.right = mViewWidth;
- p.setColor(mBgColor);
- p.setStyle(Style.FILL);
- canvas.drawRect(r, p);
- // and the area that says All day
- r.top = DAY_HEADER_HEIGHT;
- r.bottom = mFirstCell - 1;
- r.left = 0;
- r.right = mHoursWidth;
- canvas.drawRect(r, p);
-
- int startIndex = -1;
-
- int todayIndex = mTodayJulianDay - mFirstJulianDay;
- if (todayIndex < 0) {
- // Future
- startIndex = 0;
- } else if (todayIndex >= 1 && todayIndex + 1 < mNumDays) {
- // Multiday - tomorrow is visible.
- startIndex = todayIndex + 1;
- }
-
- if (startIndex >= 0) {
- // Draw the future highlight
- r.top = 0;
- r.bottom = mFirstCell - 1;
- r.left = computeDayLeftPosition(startIndex) + 1;
- r.right = computeDayLeftPosition(mNumDays);
- p.setColor(mFutureBgColor);
- p.setStyle(Style.FILL);
- canvas.drawRect(r, p);
- }
- }
- }
-
- private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) {
- // Draw the horizontal day background banner
- // p.setColor(mCalendarDateBannerBackground);
- // r.top = 0;
- // r.bottom = DAY_HEADER_HEIGHT;
- // r.left = 0;
- // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
- // canvas.drawRect(r, p);
- //
- // Fill the extra space on the right side with the default background
- // r.left = r.right;
- // r.right = mViewWidth;
- // p.setColor(mCalendarGridAreaBackground);
- // canvas.drawRect(r, p);
- if (mNumDays == 1 && ONE_DAY_HEADER_HEIGHT == 0) {
- return;
- }
-
- p.setTypeface(mBold);
- p.setTextAlign(Paint.Align.RIGHT);
- int cell = mFirstJulianDay;
-
- String[] dayNames;
- if (mDateStrWidth < mCellWidth) {
- dayNames = mDayStrs;
- } else {
- dayNames = mDayStrs2Letter;
- }
-
- p.setAntiAlias(true);
- for (int day = 0; day < mNumDays; day++, cell++) {
- int dayOfWeek = day + mFirstVisibleDayOfWeek;
- if (dayOfWeek >= 14) {
- dayOfWeek -= 14;
- }
-
- int color = mCalendarDateBannerTextColor;
- if (mNumDays == 1) {
- if (dayOfWeek == Time.SATURDAY) {
- color = mWeek_saturdayColor;
- } else if (dayOfWeek == Time.SUNDAY) {
- color = mWeek_sundayColor;
- }
- } else {
- final int column = day % 7;
- if (Utils.isSaturday(column, mFirstDayOfWeek)) {
- color = mWeek_saturdayColor;
- } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
- color = mWeek_sundayColor;
- }
- }
-
- p.setColor(color);
- drawDayHeader(dayNames[dayOfWeek], day, cell, canvas, p);
- }
- p.setTypeface(null);
- }
-
- private void drawAmPm(Canvas canvas, Paint p) {
- p.setColor(mCalendarAmPmLabel);
- p.setTextSize(AMPM_TEXT_SIZE);
- p.setTypeface(mBold);
- p.setAntiAlias(true);
- p.setTextAlign(Paint.Align.RIGHT);
- String text = mAmString;
- if (mFirstHour >= 12) {
- text = mPmString;
- }
- int y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP;
- canvas.drawText(text, HOURS_LEFT_MARGIN, y, p);
-
- if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
- // Also draw the "PM"
- text = mPmString;
- y = mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP)
- + 2 * mHoursTextHeight + HOUR_GAP;
- canvas.drawText(text, HOURS_LEFT_MARGIN, y, p);
- }
- }
-
- private void drawCurrentTimeLine(Rect r, final int day, final int top, Canvas canvas,
- Paint p) {
- r.left = computeDayLeftPosition(day) - CURRENT_TIME_LINE_SIDE_BUFFER + 1;
- r.right = computeDayLeftPosition(day + 1) + CURRENT_TIME_LINE_SIDE_BUFFER + 1;
-
- r.top = top - CURRENT_TIME_LINE_TOP_OFFSET;
- r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight();
-
- mCurrentTimeLine.setBounds(r);
- mCurrentTimeLine.draw(canvas);
- if (mAnimateToday) {
- mCurrentTimeAnimateLine.setBounds(r);
- mCurrentTimeAnimateLine.setAlpha(mAnimateTodayAlpha);
- mCurrentTimeAnimateLine.draw(canvas);
- }
- }
-
- private void doDraw(Canvas canvas) {
- Paint p = mPaint;
- Rect r = mRect;
-
- if (mFutureBgColor != 0) {
- drawBgColors(r, canvas, p);
- }
- drawGridBackground(r, canvas, p);
- drawHours(r, canvas, p);
-
- // Draw each day
- int cell = mFirstJulianDay;
- p.setAntiAlias(false);
- int alpha = p.getAlpha();
- p.setAlpha(mEventsAlpha);
- for (int day = 0; day < mNumDays; day++, cell++) {
- // TODO Wow, this needs cleanup. drawEvents loop through all the
- // events on every call.
- drawEvents(cell, day, HOUR_GAP, canvas, p);
- // If this is today
- if (cell == mTodayJulianDay) {
- int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
- + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
-
- // And the current time shows up somewhere on the screen
- if (lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
- drawCurrentTimeLine(r, day, lineY, canvas, p);
- }
- }
- }
- p.setAntiAlias(true);
- p.setAlpha(alpha);
- }
-
- private void drawHours(Rect r, Canvas canvas, Paint p) {
- setupHourTextPaint(p);
-
- int y = HOUR_GAP + mHoursTextHeight + HOURS_TOP_MARGIN;
-
- for (int i = 0; i < 24; i++) {
- String time = mHourStrs[i];
- canvas.drawText(time, HOURS_LEFT_MARGIN, y, p);
- y += mCellHeight + HOUR_GAP;
- }
- }
-
- private void setupHourTextPaint(Paint p) {
- p.setColor(mCalendarHourLabelColor);
- p.setTextSize(HOURS_TEXT_SIZE);
- p.setTypeface(Typeface.DEFAULT);
- p.setTextAlign(Paint.Align.RIGHT);
- p.setAntiAlias(true);
- }
-
- private void drawDayHeader(String dayStr, int day, int cell, Canvas canvas, Paint p) {
- int dateNum = mFirstVisibleDate + day;
- int x;
- if (dateNum > mMonthLength) {
- dateNum -= mMonthLength;
- }
- p.setAntiAlias(true);
-
- int todayIndex = mTodayJulianDay - mFirstJulianDay;
- // Draw day of the month
- String dateNumStr = String.valueOf(dateNum);
- if (mNumDays > 1) {
- float y = DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN;
-
- // Draw day of the month
- x = computeDayLeftPosition(day + 1) - DAY_HEADER_RIGHT_MARGIN;
- p.setTextAlign(Align.RIGHT);
- p.setTextSize(DATE_HEADER_FONT_SIZE);
-
- p.setTypeface(todayIndex == day ? mBold : Typeface.DEFAULT);
- canvas.drawText(dateNumStr, x, y, p);
-
- // Draw day of the week
- x -= p.measureText(" " + dateNumStr);
- p.setTextSize(DAY_HEADER_FONT_SIZE);
- p.setTypeface(Typeface.DEFAULT);
- canvas.drawText(dayStr, x, y, p);
- } else {
- float y = ONE_DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN;
- p.setTextAlign(Align.LEFT);
-
-
- // Draw day of the week
- x = computeDayLeftPosition(day) + DAY_HEADER_ONE_DAY_LEFT_MARGIN;
- p.setTextSize(DAY_HEADER_FONT_SIZE);
- p.setTypeface(Typeface.DEFAULT);
- canvas.drawText(dayStr, x, y, p);
-
- // Draw day of the month
- x += p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN;
- p.setTextSize(DATE_HEADER_FONT_SIZE);
- p.setTypeface(todayIndex == day ? mBold : Typeface.DEFAULT);
- canvas.drawText(dateNumStr, x, y, p);
- }
- }
-
- private void drawGridBackground(Rect r, Canvas canvas, Paint p) {
- Paint.Style savedStyle = p.getStyle();
-
- final float stopX = computeDayLeftPosition(mNumDays);
- float y = 0;
- final float deltaY = mCellHeight + HOUR_GAP;
- int linesIndex = 0;
- final float startY = 0;
- final float stopY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
- float x = mHoursWidth;
-
- // Draw the inner horizontal grid lines
- p.setColor(mCalendarGridLineInnerHorizontalColor);
- p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
- p.setAntiAlias(false);
- y = 0;
- linesIndex = 0;
- for (int hour = 0; hour <= 24; hour++) {
- mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN;
- mLines[linesIndex++] = y;
- mLines[linesIndex++] = stopX;
- mLines[linesIndex++] = y;
- y += deltaY;
- }
- if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
- canvas.drawLines(mLines, 0, linesIndex, p);
- linesIndex = 0;
- p.setColor(mCalendarGridLineInnerVerticalColor);
- }
-
- // Draw the inner vertical grid lines
- for (int day = 0; day <= mNumDays; day++) {
- x = computeDayLeftPosition(day);
- mLines[linesIndex++] = x;
- mLines[linesIndex++] = startY;
- mLines[linesIndex++] = x;
- mLines[linesIndex++] = stopY;
- }
- canvas.drawLines(mLines, 0, linesIndex, p);
-
- // Restore the saved style.
- p.setStyle(savedStyle);
- p.setAntiAlias(true);
- }
-
- /**
- * @param r
- * @param canvas
- * @param p
- */
- private void drawBgColors(Rect r, Canvas canvas, Paint p) {
- int todayIndex = mTodayJulianDay - mFirstJulianDay;
- // Draw the hours background color
- r.top = mDestRect.top;
- r.bottom = mDestRect.bottom;
- r.left = 0;
- r.right = mHoursWidth;
- p.setColor(mBgColor);
- p.setStyle(Style.FILL);
- p.setAntiAlias(false);
- canvas.drawRect(r, p);
-
- // Draw background for grid area
- if (mNumDays == 1 && todayIndex == 0) {
- // Draw a white background for the time later than current time
- int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
- + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
- if (lineY < mViewStartY + mViewHeight) {
- lineY = Math.max(lineY, mViewStartY);
- r.left = mHoursWidth;
- r.right = mViewWidth;
- r.top = lineY;
- r.bottom = mViewStartY + mViewHeight;
- p.setColor(mFutureBgColor);
- canvas.drawRect(r, p);
- }
- } else if (todayIndex >= 0 && todayIndex < mNumDays) {
- // Draw today with a white background for the time later than current time
- int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
- + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
- if (lineY < mViewStartY + mViewHeight) {
- lineY = Math.max(lineY, mViewStartY);
- r.left = computeDayLeftPosition(todayIndex) + 1;
- r.right = computeDayLeftPosition(todayIndex + 1);
- r.top = lineY;
- r.bottom = mViewStartY + mViewHeight;
- p.setColor(mFutureBgColor);
- canvas.drawRect(r, p);
- }
-
- // Paint Tomorrow and later days with future color
- if (todayIndex + 1 < mNumDays) {
- r.left = computeDayLeftPosition(todayIndex + 1) + 1;
- r.right = computeDayLeftPosition(mNumDays);
- r.top = mDestRect.top;
- r.bottom = mDestRect.bottom;
- p.setColor(mFutureBgColor);
- canvas.drawRect(r, p);
- }
- } else if (todayIndex < 0) {
- // Future
- r.left = computeDayLeftPosition(0) + 1;
- r.right = computeDayLeftPosition(mNumDays);
- r.top = mDestRect.top;
- r.bottom = mDestRect.bottom;
- p.setColor(mFutureBgColor);
- canvas.drawRect(r, p);
- }
- p.setAntiAlias(true);
- }
-
- private int computeMaxStringWidth(int currentMax, String[] strings, Paint p) {
- float maxWidthF = 0.0f;
-
- int len = strings.length;
- for (int i = 0; i < len; i++) {
- float width = p.measureText(strings[i]);
- maxWidthF = Math.max(width, maxWidthF);
- }
- int maxWidth = (int) (maxWidthF + 0.5);
- if (maxWidth < currentMax) {
- maxWidth = currentMax;
- }
- return maxWidth;
- }
-
- private void saveSelectionPosition(float left, float top, float right, float bottom) {
- mPrevBox.left = (int) left;
- mPrevBox.right = (int) right;
- mPrevBox.top = (int) top;
- mPrevBox.bottom = (int) bottom;
- }
-
- private void setupTextRect(Rect r) {
- if (r.bottom <= r.top || r.right <= r.left) {
- r.bottom = r.top;
- r.right = r.left;
- return;
- }
-
- if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
- r.top += EVENT_TEXT_TOP_MARGIN;
- r.bottom -= EVENT_TEXT_BOTTOM_MARGIN;
- }
- if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
- r.left += EVENT_TEXT_LEFT_MARGIN;
- r.right -= EVENT_TEXT_RIGHT_MARGIN;
- }
- }
-
- private void setupAllDayTextRect(Rect r) {
- if (r.bottom <= r.top || r.right <= r.left) {
- r.bottom = r.top;
- r.right = r.left;
- return;
- }
-
- if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
- r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN;
- r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN;
- }
- if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
- r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN;
- r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN;
- }
- }
-
- /**
- * Return the layout for a numbered event. Create it if not already existing
- */
- private StaticLayout getEventLayout(StaticLayout[] layouts, int i, Event event, Paint paint,
- Rect r) {
- if (i < 0 || i >= layouts.length) {
- return null;
- }
-
- StaticLayout layout = layouts[i];
- // Check if we have already initialized the StaticLayout and that
- // the width hasn't changed (due to vertical resizing which causes
- // re-layout of events at min height)
- if (layout == null || r.width() != layout.getWidth()) {
- SpannableStringBuilder bob = new SpannableStringBuilder();
- if (event.title != null) {
- // MAX - 1 since we add a space
- bob.append(drawTextSanitizer(event.title.toString(), MAX_EVENT_TEXT_LEN - 1));
- bob.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length(), 0);
- bob.append(' ');
- }
- if (event.location != null) {
- bob.append(drawTextSanitizer(event.location.toString(),
- MAX_EVENT_TEXT_LEN - bob.length()));
- }
-
- switch (event.selfAttendeeStatus) {
- case Attendees.ATTENDEE_STATUS_INVITED:
- paint.setColor(event.color);
- break;
- case Attendees.ATTENDEE_STATUS_DECLINED:
- paint.setColor(mEventTextColor);
- paint.setAlpha(Utils.DECLINED_EVENT_TEXT_ALPHA);
- break;
- case Attendees.ATTENDEE_STATUS_NONE: // Your own events
- case Attendees.ATTENDEE_STATUS_ACCEPTED:
- case Attendees.ATTENDEE_STATUS_TENTATIVE:
- default:
- paint.setColor(mEventTextColor);
- break;
- }
-
- // Leave a one pixel boundary on the left and right of the rectangle for the event
- layout = new StaticLayout(bob, 0, bob.length(), new TextPaint(paint), r.width(),
- Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width());
-
- layouts[i] = layout;
- }
- layout.getPaint().setAlpha(mEventsAlpha);
- return layout;
- }
-
- private void drawAllDayEvents(int firstDay, int numDays, Canvas canvas, Paint p) {
-
- p.setTextSize(NORMAL_FONT_SIZE);
- p.setTextAlign(Paint.Align.LEFT);
- Paint eventTextPaint = mEventTextPaint;
-
- final float startY = DAY_HEADER_HEIGHT;
- final float stopY = startY + mAlldayHeight + ALLDAY_TOP_MARGIN;
- float x = 0;
- int linesIndex = 0;
-
- // Draw the inner vertical grid lines
- p.setColor(mCalendarGridLineInnerVerticalColor);
- x = mHoursWidth;
- p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
- // Line bounding the top of the all day area
- mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN;
- mLines[linesIndex++] = startY;
- mLines[linesIndex++] = computeDayLeftPosition(mNumDays);
- mLines[linesIndex++] = startY;
-
- for (int day = 0; day <= mNumDays; day++) {
- x = computeDayLeftPosition(day);
- mLines[linesIndex++] = x;
- mLines[linesIndex++] = startY;
- mLines[linesIndex++] = x;
- mLines[linesIndex++] = stopY;
- }
- p.setAntiAlias(false);
- canvas.drawLines(mLines, 0, linesIndex, p);
- p.setStyle(Style.FILL);
-
- int y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
- int lastDay = firstDay + numDays - 1;
- final ArrayList<Event> events = mAllDayEvents;
- int numEvents = events.size();
- // Whether or not we should draw the more events text
- boolean hasMoreEvents = false;
- // size of the allDay area
- float drawHeight = mAlldayHeight;
- // max number of events being drawn in one day of the allday area
- float numRectangles = mMaxAlldayEvents;
- // Where to cut off drawn allday events
- int allDayEventClip = DAY_HEADER_HEIGHT + mAlldayHeight + ALLDAY_TOP_MARGIN;
- // The number of events that weren't drawn in each day
- mSkippedAlldayEvents = new int[numDays];
- if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount && !mShowAllAllDayEvents &&
- mAnimateDayHeight == 0) {
- // We draw one fewer event than will fit so that more events text
- // can be drawn
- numRectangles = mMaxUnexpandedAlldayEventCount - 1;
- // We also clip the events above the more events text
- allDayEventClip -= MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
- hasMoreEvents = true;
- } else if (mAnimateDayHeight != 0) {
- // clip at the end of the animating space
- allDayEventClip = DAY_HEADER_HEIGHT + mAnimateDayHeight + ALLDAY_TOP_MARGIN;
- }
-
- int alpha = eventTextPaint.getAlpha();
- eventTextPaint.setAlpha(mEventsAlpha);
- for (int i = 0; i < numEvents; i++) {
- Event event = events.get(i);
- int startDay = event.startDay;
- int endDay = event.endDay;
- if (startDay > lastDay || endDay < firstDay) {
- continue;
- }
- if (startDay < firstDay) {
- startDay = firstDay;
- }
- if (endDay > lastDay) {
- endDay = lastDay;
- }
- int startIndex = startDay - firstDay;
- int endIndex = endDay - firstDay;
- float height = mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount ? mAnimateDayEventHeight :
- drawHeight / numRectangles;
-
- // Prevent a single event from getting too big
- if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
- height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
- }
-
- // Leave a one-pixel space between the vertical day lines and the
- // event rectangle.
- event.left = computeDayLeftPosition(startIndex);
- event.right = computeDayLeftPosition(endIndex + 1) - DAY_GAP;
- event.top = y + height * event.getColumn();
- event.bottom = event.top + height - ALL_DAY_EVENT_RECT_BOTTOM_MARGIN;
- if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
- // check if we should skip this event. We skip if it starts
- // after the clip bound or ends after the skip bound and we're
- // not animating.
- if (event.top >= allDayEventClip) {
- incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex);
- continue;
- } else if (event.bottom > allDayEventClip) {
- if (hasMoreEvents) {
- incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex);
- continue;
- }
- event.bottom = allDayEventClip;
- }
- }
- Rect r = drawEventRect(event, canvas, p, eventTextPaint, (int) event.top,
- (int) event.bottom);
- setupAllDayTextRect(r);
- StaticLayout layout = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r);
- drawEventText(layout, r, canvas, r.top, r.bottom, true);
-
- // Check if this all-day event intersects the selected day
- if (mSelectionAllday && mComputeSelectedEvents) {
- if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
- mSelectedEvents.add(event);
- }
- }
- }
- eventTextPaint.setAlpha(alpha);
-
- if (mMoreAlldayEventsTextAlpha != 0 && mSkippedAlldayEvents != null) {
- // If the more allday text should be visible, draw it.
- alpha = p.getAlpha();
- p.setAlpha(mEventsAlpha);
- p.setColor(mMoreAlldayEventsTextAlpha << 24 & mMoreEventsTextColor);
- for (int i = 0; i < mSkippedAlldayEvents.length; i++) {
- if (mSkippedAlldayEvents[i] > 0) {
- drawMoreAlldayEvents(canvas, mSkippedAlldayEvents[i], i, p);
- }
- }
- p.setAlpha(alpha);
- }
-
- if (mSelectionAllday) {
- // Compute the neighbors for the list of all-day events that
- // intersect the selected day.
- computeAllDayNeighbors();
-
- // Set the selection position to zero so that when we move down
- // to the normal event area, we will highlight the topmost event.
- saveSelectionPosition(0f, 0f, 0f, 0f);
- }
- }
-
- // Helper method for counting the number of allday events skipped on each day
- private void incrementSkipCount(int[] counts, int startIndex, int endIndex) {
- if (counts == null || startIndex < 0 || endIndex > counts.length) {
- return;
- }
- for (int i = startIndex; i <= endIndex; i++) {
- counts[i]++;
- }
- }
-
- // Draws the "box +n" text for hidden allday events
- protected void drawMoreAlldayEvents(Canvas canvas, int remainingEvents, int day, Paint p) {
- int x = computeDayLeftPosition(day) + EVENT_ALL_DAY_TEXT_LEFT_MARGIN;
- int y = (int) (mAlldayHeight - .5f * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - .5f
- * EVENT_SQUARE_WIDTH + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN);
- Rect r = mRect;
- r.top = y;
- r.left = x;
- r.bottom = y + EVENT_SQUARE_WIDTH;
- r.right = x + EVENT_SQUARE_WIDTH;
- p.setColor(mMoreEventsTextColor);
- p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH);
- p.setStyle(Style.STROKE);
- p.setAntiAlias(false);
- canvas.drawRect(r, p);
- p.setAntiAlias(true);
- p.setStyle(Style.FILL);
- p.setTextSize(EVENT_TEXT_FONT_SIZE);
- String text = mResources.getQuantityString(R.plurals.month_more_events, remainingEvents);
- y += EVENT_SQUARE_WIDTH;
- x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING;
- canvas.drawText(String.format(text, remainingEvents), x, y, p);
- }
-
- private void computeAllDayNeighbors() {
- int len = mSelectedEvents.size();
- if (len == 0 || mSelectedEvent != null) {
- return;
- }
-
- // First, clear all the links
- for (int ii = 0; ii < len; ii++) {
- Event ev = mSelectedEvents.get(ii);
- ev.nextUp = null;
- ev.nextDown = null;
- ev.nextLeft = null;
- ev.nextRight = null;
- }
-
- // For each event in the selected event list "mSelectedEvents", find
- // its neighbors in the up and down directions. This could be done
- // more efficiently by sorting on the Event.getColumn() field, but
- // the list is expected to be very small.
-
- // Find the event in the same row as the previously selected all-day
- // event, if any.
- int startPosition = -1;
- if (mPrevSelectedEvent != null && mPrevSelectedEvent.drawAsAllday()) {
- startPosition = mPrevSelectedEvent.getColumn();
- }
- int maxPosition = -1;
- Event startEvent = null;
- Event maxPositionEvent = null;
- for (int ii = 0; ii < len; ii++) {
- Event ev = mSelectedEvents.get(ii);
- int position = ev.getColumn();
- if (position == startPosition) {
- startEvent = ev;
- } else if (position > maxPosition) {
- maxPositionEvent = ev;
- maxPosition = position;
- }
- for (int jj = 0; jj < len; jj++) {
- if (jj == ii) {
- continue;
- }
- Event neighbor = mSelectedEvents.get(jj);
- int neighborPosition = neighbor.getColumn();
- if (neighborPosition == position - 1) {
- ev.nextUp = neighbor;
- } else if (neighborPosition == position + 1) {
- ev.nextDown = neighbor;
- }
- }
- }
- if (startEvent != null) {
- setSelectedEvent(startEvent);
- } else {
- setSelectedEvent(maxPositionEvent);
- }
- }
-
- private void drawEvents(int date, int dayIndex, int top, Canvas canvas, Paint p) {
- Paint eventTextPaint = mEventTextPaint;
- int left = computeDayLeftPosition(dayIndex) + 1;
- int cellWidth = computeDayLeftPosition(dayIndex + 1) - left + 1;
- int cellHeight = mCellHeight;
-
- // Use the selected hour as the selection region
- Rect selectionArea = mSelectionRect;
- selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
- selectionArea.bottom = selectionArea.top + cellHeight;
- selectionArea.left = left;
- selectionArea.right = selectionArea.left + cellWidth;
-
- final ArrayList<Event> events = mEvents;
- int numEvents = events.size();
- EventGeometry geometry = mEventGeometry;
-
- final int viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight;
-
- int alpha = eventTextPaint.getAlpha();
- eventTextPaint.setAlpha(mEventsAlpha);
- for (int i = 0; i < numEvents; i++) {
- Event event = events.get(i);
- if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
- continue;
- }
-
- // Don't draw it if it is not visible
- if (event.bottom < mViewStartY || event.top > viewEndY) {
- continue;
- }
-
- if (date == mSelectionDay && !mSelectionAllday && mComputeSelectedEvents
- && geometry.eventIntersectsSelection(event, selectionArea)) {
- mSelectedEvents.add(event);
- }
-
- Rect r = drawEventRect(event, canvas, p, eventTextPaint, mViewStartY, viewEndY);
- setupTextRect(r);
-
- // Don't draw text if it is not visible
- if (r.top > viewEndY || r.bottom < mViewStartY) {
- continue;
- }
- StaticLayout layout = getEventLayout(mLayouts, i, event, eventTextPaint, r);
- // TODO: not sure why we are 4 pixels off
- drawEventText(layout, r, canvas, mViewStartY + 4, mViewStartY + mViewHeight
- - DAY_HEADER_HEIGHT - mAlldayHeight, false);
- }
- eventTextPaint.setAlpha(alpha);
- }
-
- private Rect drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint,
- int visibleTop, int visibleBot) {
- // Draw the Event Rect
- Rect r = mRect;
- r.top = Math.max((int) event.top + EVENT_RECT_TOP_MARGIN, visibleTop);
- r.bottom = Math.min((int) event.bottom - EVENT_RECT_BOTTOM_MARGIN, visibleBot);
- r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
- r.right = (int) event.right;
-
- int color = event.color;
- switch (event.selfAttendeeStatus) {
- case Attendees.ATTENDEE_STATUS_INVITED:
- if (event != mClickedEvent) {
- p.setStyle(Style.STROKE);
- }
- break;
- case Attendees.ATTENDEE_STATUS_DECLINED:
- if (event != mClickedEvent) {
- color = Utils.getDeclinedColorFromColor(color);
- }
- case Attendees.ATTENDEE_STATUS_NONE: // Your own events
- case Attendees.ATTENDEE_STATUS_ACCEPTED:
- case Attendees.ATTENDEE_STATUS_TENTATIVE:
- default:
- p.setStyle(Style.FILL_AND_STROKE);
- break;
- }
-
- p.setAntiAlias(false);
-
- int floorHalfStroke = (int) Math.floor(EVENT_RECT_STROKE_WIDTH / 2.0f);
- int ceilHalfStroke = (int) Math.ceil(EVENT_RECT_STROKE_WIDTH / 2.0f);
- r.top = Math.max((int) event.top + EVENT_RECT_TOP_MARGIN + floorHalfStroke, visibleTop);
- r.bottom = Math.min((int) event.bottom - EVENT_RECT_BOTTOM_MARGIN - ceilHalfStroke,
- visibleBot);
- r.left += floorHalfStroke;
- r.right -= ceilHalfStroke;
- p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH);
- p.setColor(color);
- int alpha = p.getAlpha();
- p.setAlpha(mEventsAlpha);
- canvas.drawRect(r, p);
- p.setAlpha(alpha);
- p.setStyle(Style.FILL);
-
- // Setup rect for drawEventText which follows
- r.top = (int) event.top + EVENT_RECT_TOP_MARGIN;
- r.bottom = (int) event.bottom - EVENT_RECT_BOTTOM_MARGIN;
- r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
- r.right = (int) event.right - EVENT_RECT_RIGHT_MARGIN;
- return r;
- }
-
- private final Pattern drawTextSanitizerFilter = Pattern.compile("[\t\n],");
-
- // Sanitize a string before passing it to drawText or else we get little
- // squares. For newlines and tabs before a comma, delete the character.
- // Otherwise, just replace them with a space.
- private String drawTextSanitizer(String string, int maxEventTextLen) {
- Matcher m = drawTextSanitizerFilter.matcher(string);
- string = m.replaceAll(",");
-
- int len = string.length();
- if (maxEventTextLen <= 0) {
- string = "";
- len = 0;
- } else if (len > maxEventTextLen) {
- string = string.substring(0, maxEventTextLen);
- len = maxEventTextLen;
- }
-
- return string.replace('\n', ' ');
- }
-
- private void drawEventText(StaticLayout eventLayout, Rect rect, Canvas canvas, int top,
- int bottom, boolean center) {
- // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
-
- int width = rect.right - rect.left;
- int height = rect.bottom - rect.top;
-
- // If the rectangle is too small for text, then return
- if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
- return;
- }
-
- int totalLineHeight = 0;
- int lineCount = eventLayout.getLineCount();
- for (int i = 0; i < lineCount; i++) {
- int lineBottom = eventLayout.getLineBottom(i);
- if (lineBottom <= height) {
- totalLineHeight = lineBottom;
- } else {
- break;
- }
- }
-
- // + 2 is small workaround when the font is slightly bigger then the rect. This will
- // still allow the text to be shown without overflowing into the other all day rects.
- if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight + 2 < top) {
- return;
- }
-
- // Use a StaticLayout to format the string.
- canvas.save();
- // canvas.translate(rect.left, rect.top + (rect.bottom - rect.top / 2));
- int padding = center? (rect.bottom - rect.top - totalLineHeight) / 2 : 0;
- canvas.translate(rect.left, rect.top + padding);
- rect.left = 0;
- rect.right = width;
- rect.top = 0;
- rect.bottom = totalLineHeight;
-
- // There's a bug somewhere. If this rect is outside of a previous
- // cliprect, this becomes a no-op. What happens is that the text draw
- // past the event rect. The current fix is to not draw the staticLayout
- // at all if it is completely out of bound.
- canvas.clipRect(rect);
- eventLayout.draw(canvas);
- canvas.restore();
- }
-
- // The following routines are called from the parent activity when certain
- // touch events occur.
- private void doDown(MotionEvent ev) {
- mTouchMode = TOUCH_MODE_DOWN;
- mViewStartX = 0;
- mOnFlingCalled = false;
- mHandler.removeCallbacks(mContinueScroll);
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- // Save selection information: we use setSelectionFromPosition to find the selected event
- // in order to show the "clicked" color. But since it is also setting the selected info
- // for new events, we need to restore the old info after calling the function.
- Event oldSelectedEvent = mSelectedEvent;
- int oldSelectionDay = mSelectionDay;
- int oldSelectionHour = mSelectionHour;
- if (setSelectionFromPosition(x, y, false)) {
- // If a time was selected (a blue selection box is visible) and the click location
- // is in the selected time, do not show a click on an event to prevent a situation
- // of both a selection and an event are clicked when they overlap.
- boolean pressedSelected = (mSelectionMode != SELECTION_HIDDEN)
- && oldSelectionDay == mSelectionDay && oldSelectionHour == mSelectionHour;
- if (!pressedSelected && mSelectedEvent != null) {
- mSavedClickedEvent = mSelectedEvent;
- mDownTouchTime = System.currentTimeMillis();
- postDelayed (mSetClick,mOnDownDelay);
- } else {
- eventClickCleanup();
- }
- }
- mSelectedEvent = oldSelectedEvent;
- mSelectionDay = oldSelectionDay;
- mSelectionHour = oldSelectionHour;
- invalidate();
- }
-
- // Kicks off all the animations when the expand allday area is tapped
- private void doExpandAllDayClick() {
- mShowAllAllDayEvents = !mShowAllAllDayEvents;
-
- ObjectAnimator.setFrameDelay(0);
-
- // Determine the starting height
- if (mAnimateDayHeight == 0) {
- mAnimateDayHeight = mShowAllAllDayEvents ?
- mAlldayHeight - (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT : mAlldayHeight;
- }
- // Cancel current animations
- mCancellingAnimations = true;
- if (mAlldayAnimator != null) {
- mAlldayAnimator.cancel();
- }
- if (mAlldayEventAnimator != null) {
- mAlldayEventAnimator.cancel();
- }
- if (mMoreAlldayEventsAnimator != null) {
- mMoreAlldayEventsAnimator.cancel();
- }
- mCancellingAnimations = false;
- // get new animators
- mAlldayAnimator = getAllDayAnimator();
- mAlldayEventAnimator = getAllDayEventAnimator();
- mMoreAlldayEventsAnimator = ObjectAnimator.ofInt(this,
- "moreAllDayEventsTextAlpha",
- mShowAllAllDayEvents ? MORE_EVENTS_MAX_ALPHA : 0,
- mShowAllAllDayEvents ? 0 : MORE_EVENTS_MAX_ALPHA);
-
- // Set up delays and start the animators
- mAlldayAnimator.setStartDelay(mShowAllAllDayEvents ? ANIMATION_SECONDARY_DURATION : 0);
- mAlldayAnimator.start();
- mMoreAlldayEventsAnimator.setStartDelay(mShowAllAllDayEvents ? 0 : ANIMATION_DURATION);
- mMoreAlldayEventsAnimator.setDuration(ANIMATION_SECONDARY_DURATION);
- mMoreAlldayEventsAnimator.start();
- if (mAlldayEventAnimator != null) {
- // This is the only animator that can return null, so check it
- mAlldayEventAnimator
- .setStartDelay(mShowAllAllDayEvents ? ANIMATION_SECONDARY_DURATION : 0);
- mAlldayEventAnimator.start();
- }
- }
-
- /**
- * Figures out the initial heights for allDay events and space when
- * a view is being set up.
- */
- public void initAllDayHeights() {
- if (mMaxAlldayEvents <= mMaxUnexpandedAlldayEventCount) {
- return;
- }
- if (mShowAllAllDayEvents) {
- int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
- maxADHeight = Math.min(maxADHeight,
- (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
- mAnimateDayEventHeight = maxADHeight / mMaxAlldayEvents;
- } else {
- mAnimateDayEventHeight = (int)MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
- }
- }
-
- // Sets up an animator for changing the height of allday events
- private ObjectAnimator getAllDayEventAnimator() {
- // First calculate the absolute max height
- int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
- // Now expand to fit but not beyond the absolute max
- maxADHeight =
- Math.min(maxADHeight, (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
- // calculate the height of individual events in order to fit
- int fitHeight = maxADHeight / mMaxAlldayEvents;
- int currentHeight = mAnimateDayEventHeight;
- int desiredHeight =
- mShowAllAllDayEvents ? fitHeight : (int)MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
- // if there's nothing to animate just return
- if (currentHeight == desiredHeight) {
- return null;
- }
-
- // Set up the animator with the calculated values
- ObjectAnimator animator = ObjectAnimator.ofInt(this, "animateDayEventHeight",
- currentHeight, desiredHeight);
- animator.setDuration(ANIMATION_DURATION);
- return animator;
- }
-
- // Sets up an animator for changing the height of the allday area
- private ObjectAnimator getAllDayAnimator() {
- // Calculate the absolute max height
- int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
- // Find the desired height but don't exceed abs max
- maxADHeight =
- Math.min(maxADHeight, (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
- // calculate the current and desired heights
- int currentHeight = mAnimateDayHeight != 0 ? mAnimateDayHeight : mAlldayHeight;
- int desiredHeight = mShowAllAllDayEvents ? maxADHeight :
- (int) (MAX_UNEXPANDED_ALLDAY_HEIGHT - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - 1);
-
- // Set up the animator with the calculated values
- ObjectAnimator animator = ObjectAnimator.ofInt(this, "animateDayHeight",
- currentHeight, desiredHeight);
- animator.setDuration(ANIMATION_DURATION);
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCancellingAnimations) {
- // when finished, set this to 0 to signify not animating
- mAnimateDayHeight = 0;
- mUseExpandIcon = !mShowAllAllDayEvents;
- }
- mRemeasure = true;
- invalidate();
- }
- });
- return animator;
- }
-
- // setter for the 'box +n' alpha text used by the animator
- public void setMoreAllDayEventsTextAlpha(int alpha) {
- mMoreAlldayEventsTextAlpha = alpha;
- invalidate();
- }
-
- // setter for the height of the allday area used by the animator
- public void setAnimateDayHeight(int height) {
- mAnimateDayHeight = height;
- mRemeasure = true;
- invalidate();
- }
-
- // setter for the height of allday events used by the animator
- public void setAnimateDayEventHeight(int height) {
- mAnimateDayEventHeight = height;
- mRemeasure = true;
- invalidate();
- }
-
- private void doSingleTapUp(MotionEvent ev) {
- if (!mHandleActionUp || mScrolling) {
- return;
- }
-
- int x = (int) ev.getX();
- int y = (int) ev.getY();
- int selectedDay = mSelectionDay;
- int selectedHour = mSelectionHour;
-
- if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
- // check if the tap was in the allday expansion area
- int bottom = mFirstCell;
- if((x < mHoursWidth && y > DAY_HEADER_HEIGHT && y < DAY_HEADER_HEIGHT + mAlldayHeight)
- || (!mShowAllAllDayEvents && mAnimateDayHeight == 0 && y < bottom &&
- y >= bottom - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT)) {
- doExpandAllDayClick();
- return;
- }
- }
-
- boolean validPosition = setSelectionFromPosition(x, y, false);
- if (!validPosition) {
- if (y < DAY_HEADER_HEIGHT) {
- Time selectedTime = new Time(mBaseDate);
- selectedTime.setJulianDay(mSelectionDay);
- selectedTime.hour = mSelectionHour;
- selectedTime.normalize(true /* ignore isDst */);
- mController.sendEvent(this, EventType.GO_TO, null, null, selectedTime, -1,
- ViewType.DAY, CalendarController.EXTRA_GOTO_DATE, null, null);
- }
- return;
- }
-
- boolean hasSelection = mSelectionMode != SELECTION_HIDDEN;
- boolean pressedSelected = (hasSelection || mTouchExplorationEnabled)
- && selectedDay == mSelectionDay && selectedHour == mSelectionHour;
-
- if (mSelectedEvent != null) {
- // If the tap is on an event, launch the "View event" view
- if (mIsAccessibilityEnabled) {
- mAccessibilityMgr.interrupt();
- }
-
- mSelectionMode = SELECTION_HIDDEN;
-
- int yLocation =
- (int)((mSelectedEvent.top + mSelectedEvent.bottom)/2);
- // Y location is affected by the position of the event in the scrolling
- // view (mViewStartY) and the presence of all day events (mFirstCell)
- if (!mSelectedEvent.allDay) {
- yLocation += (mFirstCell - mViewStartY);
- }
- mClickedYLocation = yLocation;
- long clearDelay = (CLICK_DISPLAY_DURATION + mOnDownDelay) -
- (System.currentTimeMillis() - mDownTouchTime);
- if (clearDelay > 0) {
- this.postDelayed(mClearClick, clearDelay);
- } else {
- this.post(mClearClick);
- }
- }
- invalidate();
- }
-
- private void doLongPress(MotionEvent ev) {
- eventClickCleanup();
- if (mScrolling) {
- return;
- }
-
- // Scale gesture in progress
- if (mStartingSpanY != 0) {
- return;
- }
-
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- boolean validPosition = setSelectionFromPosition(x, y, false);
- if (!validPosition) {
- // return if the touch wasn't on an area of concern
- return;
- }
-
- invalidate();
- performLongClick();
- }
-
- private void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
- cancelAnimation();
- if (mStartingScroll) {
- mInitialScrollX = 0;
- mInitialScrollY = 0;
- mStartingScroll = false;
- }
-
- mInitialScrollX += deltaX;
- mInitialScrollY += deltaY;
- int distanceX = (int) mInitialScrollX;
- int distanceY = (int) mInitialScrollY;
-
- final float focusY = getAverageY(e2);
- if (mRecalCenterHour) {
- // Calculate the hour that correspond to the average of the Y touch points
- mGestureCenterHour = (mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight)
- / (mCellHeight + DAY_GAP);
- mRecalCenterHour = false;
- }
-
- // If we haven't figured out the predominant scroll direction yet,
- // then do it now.
- if (mTouchMode == TOUCH_MODE_DOWN) {
- int absDistanceX = Math.abs(distanceX);
- int absDistanceY = Math.abs(distanceY);
- mScrollStartY = mViewStartY;
- mPreviousDirection = 0;
-
- if (absDistanceX > absDistanceY) {
- int slopFactor = mScaleGestureDetector.isInProgress() ? 20 : 2;
- if (absDistanceX > mScaledPagingTouchSlop * slopFactor) {
- mTouchMode = TOUCH_MODE_HSCROLL;
- mViewStartX = distanceX;
- initNextView(-mViewStartX);
- }
- } else {
- mTouchMode = TOUCH_MODE_VSCROLL;
- }
- } else if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
- // We are already scrolling horizontally, so check if we
- // changed the direction of scrolling so that the other week
- // is now visible.
- mViewStartX = distanceX;
- if (distanceX != 0) {
- int direction = (distanceX > 0) ? 1 : -1;
- if (direction != mPreviousDirection) {
- // The user has switched the direction of scrolling
- // so re-init the next view
- initNextView(-mViewStartX);
- mPreviousDirection = direction;
- }
- }
- }
-
- if ((mTouchMode & TOUCH_MODE_VSCROLL) != 0) {
- // Calculate the top of the visible region in the calendar grid.
- // Increasing/decrease this will scroll the calendar grid up/down.
- mViewStartY = (int) ((mGestureCenterHour * (mCellHeight + DAY_GAP))
- - focusY + DAY_HEADER_HEIGHT + mAlldayHeight);
-
- // If dragging while already at the end, do a glow
- final int pulledToY = (int) (mScrollStartY + deltaY);
- if (pulledToY < 0) {
- mEdgeEffectTop.onPull(deltaY / mViewHeight);
- if (!mEdgeEffectBottom.isFinished()) {
- mEdgeEffectBottom.onRelease();
- }
- } else if (pulledToY > mMaxViewStartY) {
- mEdgeEffectBottom.onPull(deltaY / mViewHeight);
- if (!mEdgeEffectTop.isFinished()) {
- mEdgeEffectTop.onRelease();
- }
- }
-
- if (mViewStartY < 0) {
- mViewStartY = 0;
- mRecalCenterHour = true;
- } else if (mViewStartY > mMaxViewStartY) {
- mViewStartY = mMaxViewStartY;
- mRecalCenterHour = true;
- }
- if (mRecalCenterHour) {
- // Calculate the hour that correspond to the average of the Y touch points
- mGestureCenterHour = (mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight)
- / (mCellHeight + DAY_GAP);
- mRecalCenterHour = false;
- }
- computeFirstHour();
- }
-
- mScrolling = true;
-
- mSelectionMode = SELECTION_HIDDEN;
- invalidate();
- }
-
- private float getAverageY(MotionEvent me) {
- int count = me.getPointerCount();
- float focusY = 0;
- for (int i = 0; i < count; i++) {
- focusY += me.getY(i);
- }
- focusY /= count;
- return focusY;
- }
-
- private void cancelAnimation() {
- Animation in = mViewSwitcher.getInAnimation();
- if (in != null) {
- // cancel() doesn't terminate cleanly.
- in.scaleCurrentDuration(0);
- }
- Animation out = mViewSwitcher.getOutAnimation();
- if (out != null) {
- // cancel() doesn't terminate cleanly.
- out.scaleCurrentDuration(0);
- }
- }
-
- private void doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- cancelAnimation();
-
- mSelectionMode = SELECTION_HIDDEN;
- eventClickCleanup();
-
- mOnFlingCalled = true;
-
- if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
- // Horizontal fling.
- // initNextView(deltaX);
- mTouchMode = TOUCH_MODE_INITIAL_STATE;
- if (DEBUG) Log.d(TAG, "doFling: velocityX " + velocityX);
- int deltaX = (int) e2.getX() - (int) e1.getX();
- switchViews(deltaX < 0, mViewStartX, mViewWidth, velocityX);
- mViewStartX = 0;
- return;
- }
-
- if ((mTouchMode & TOUCH_MODE_VSCROLL) == 0) {
- if (DEBUG) Log.d(TAG, "doFling: no fling");
- return;
- }
-
- // Vertical fling.
- mTouchMode = TOUCH_MODE_INITIAL_STATE;
- mViewStartX = 0;
-
- if (DEBUG) {
- Log.d(TAG, "doFling: mViewStartY" + mViewStartY + " velocityY " + velocityY);
- }
-
- // Continue scrolling vertically
- mScrolling = true;
- mScroller.fling(0 /* startX */, mViewStartY /* startY */, 0 /* velocityX */,
- (int) -velocityY, 0 /* minX */, 0 /* maxX */, 0 /* minY */,
- mMaxViewStartY /* maxY */, OVERFLING_DISTANCE, OVERFLING_DISTANCE);
-
- // When flinging down, show a glow when it hits the end only if it
- // wasn't started at the top
- if (velocityY > 0 && mViewStartY != 0) {
- mCallEdgeEffectOnAbsorb = true;
- }
- // When flinging up, show a glow when it hits the end only if it wasn't
- // started at the bottom
- else if (velocityY < 0 && mViewStartY != mMaxViewStartY) {
- mCallEdgeEffectOnAbsorb = true;
- }
- mHandler.post(mContinueScroll);
- }
-
- private boolean initNextView(int deltaX) {
- // Change the view to the previous day or week
- DayView view = (DayView) mViewSwitcher.getNextView();
- Time date = view.mBaseDate;
- date.set(mBaseDate);
- boolean switchForward;
- if (deltaX > 0) {
- date.monthDay -= mNumDays;
- view.setSelectedDay(mSelectionDay - mNumDays);
- switchForward = false;
- } else {
- date.monthDay += mNumDays;
- view.setSelectedDay(mSelectionDay + mNumDays);
- switchForward = true;
- }
- date.normalize(true /* ignore isDst */);
- initView(view);
- view.layout(getLeft(), getTop(), getRight(), getBottom());
- view.reloadEvents();
- return switchForward;
- }
-
- // ScaleGestureDetector.OnScaleGestureListener
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- mHandleActionUp = false;
- float gestureCenterInPixels = detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight;
- mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP);
-
- mStartingSpanY = Math.max(MIN_Y_SPAN, Math.abs(detector.getCurrentSpanY()));
- mCellHeightBeforeScaleGesture = mCellHeight;
-
- if (DEBUG_SCALING) {
- float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
- Log.d(TAG, "onScaleBegin: mGestureCenterHour:" + mGestureCenterHour
- + "\tViewStartHour: " + ViewStartHour + "\tmViewStartY:" + mViewStartY
- + "\tmCellHeight:" + mCellHeight + " SpanY:" + detector.getCurrentSpanY());
- }
-
- return true;
- }
-
- // ScaleGestureDetector.OnScaleGestureListener
- public boolean onScale(ScaleGestureDetector detector) {
- float spanY = Math.max(MIN_Y_SPAN, Math.abs(detector.getCurrentSpanY()));
-
- mCellHeight = (int) (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY);
-
- if (mCellHeight < mMinCellHeight) {
- // If mStartingSpanY is too small, even a small increase in the
- // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
- mStartingSpanY = spanY;
- mCellHeight = mMinCellHeight;
- mCellHeightBeforeScaleGesture = mMinCellHeight;
- } else if (mCellHeight > MAX_CELL_HEIGHT) {
- mStartingSpanY = spanY;
- mCellHeight = MAX_CELL_HEIGHT;
- mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT;
- }
-
- int gestureCenterInPixels = (int) detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight;
- mViewStartY = (int) (mGestureCenterHour * (mCellHeight + DAY_GAP)) - gestureCenterInPixels;
- mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
-
- if (DEBUG_SCALING) {
- float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
- Log.d(TAG, "onScale: mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: "
- + ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:"
- + mCellHeight + " SpanY:" + detector.getCurrentSpanY());
- }
-
- if (mViewStartY < 0) {
- mViewStartY = 0;
- mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
- / (float) (mCellHeight + DAY_GAP);
- } else if (mViewStartY > mMaxViewStartY) {
- mViewStartY = mMaxViewStartY;
- mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
- / (float) (mCellHeight + DAY_GAP);
- }
- computeFirstHour();
-
- mRemeasure = true;
- invalidate();
- return true;
- }
-
- // ScaleGestureDetector.OnScaleGestureListener
- public void onScaleEnd(ScaleGestureDetector detector) {
- mScrollStartY = mViewStartY;
- mInitialScrollY = 0;
- mInitialScrollX = 0;
- mStartingSpanY = 0;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- if (DEBUG) Log.e(TAG, "" + action + " ev.getPointerCount() = " + ev.getPointerCount());
-
- if ((ev.getActionMasked() == MotionEvent.ACTION_DOWN) ||
- (ev.getActionMasked() == MotionEvent.ACTION_UP) ||
- (ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) ||
- (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN)) {
- mRecalCenterHour = true;
- }
-
- if ((mTouchMode & TOUCH_MODE_HSCROLL) == 0) {
- mScaleGestureDetector.onTouchEvent(ev);
- }
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mStartingScroll = true;
- if (DEBUG) {
- Log.e(TAG, "ACTION_DOWN ev.getDownTime = " + ev.getDownTime() + " Cnt="
- + ev.getPointerCount());
- }
-
- int bottom = mAlldayHeight + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
- if (ev.getY() < bottom) {
- mTouchStartedInAlldayArea = true;
- } else {
- mTouchStartedInAlldayArea = false;
- }
- mHandleActionUp = true;
- mGestureDetector.onTouchEvent(ev);
- return true;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG) Log.e(TAG, "ACTION_MOVE Cnt=" + ev.getPointerCount() + DayView.this);
- mGestureDetector.onTouchEvent(ev);
- return true;
-
- case MotionEvent.ACTION_UP:
- if (DEBUG) Log.e(TAG, "ACTION_UP Cnt=" + ev.getPointerCount() + mHandleActionUp);
- mEdgeEffectTop.onRelease();
- mEdgeEffectBottom.onRelease();
- mStartingScroll = false;
- mGestureDetector.onTouchEvent(ev);
- if (!mHandleActionUp) {
- mHandleActionUp = true;
- mViewStartX = 0;
- invalidate();
- return true;
- }
-
- if (mOnFlingCalled) {
- return true;
- }
-
- // If we were scrolling, then reset the selected hour so that it
- // is visible.
- if (mScrolling) {
- mScrolling = false;
- resetSelectedHour();
- invalidate();
- }
-
- if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
- mTouchMode = TOUCH_MODE_INITIAL_STATE;
- if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
- // The user has gone beyond the threshold so switch views
- if (DEBUG) Log.d(TAG, "- horizontal scroll: switch views");
- switchViews(mViewStartX > 0, mViewStartX, mViewWidth, 0);
- mViewStartX = 0;
- return true;
- } else {
- // Not beyond the threshold so invalidate which will cause
- // the view to snap back. Also call recalc() to ensure
- // that we have the correct starting date and title.
- if (DEBUG) Log.d(TAG, "- horizontal scroll: snap back");
- recalc();
- invalidate();
- mViewStartX = 0;
- }
- }
-
- return true;
-
- // This case isn't expected to happen.
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG) Log.e(TAG, "ACTION_CANCEL");
- mGestureDetector.onTouchEvent(ev);
- mScrolling = false;
- resetSelectedHour();
- return true;
-
- default:
- if (DEBUG) Log.e(TAG, "Not MotionEvent " + ev.toString());
- if (mGestureDetector.onTouchEvent(ev)) {
- return true;
- }
- return super.onTouchEvent(ev);
- }
- }
-
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- MenuItem item;
-
- // If the trackball is held down, then the context menu pops up and
- // we never get onKeyUp() for the long-press. So check for it here
- // and change the selection to the long-press state.
- if (mSelectionMode != SELECTION_LONGPRESS) {
- invalidate();
- }
-
- final long startMillis = getSelectedTimeInMillis();
- int flags = DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
- | DateUtils.FORMAT_SHOW_WEEKDAY;
- final String title = Utils.formatDateRange(mContext, startMillis, startMillis, flags);
- menu.setHeaderTitle(title);
-
- mPopup.dismiss();
- }
-
- /**
- * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
- * If the touch position is not within the displayed grid, then this
- * method returns false.
- *
- * @param x the x position of the touch
- * @param y the y position of the touch
- * @param keepOldSelection - do not change the selection info (used for invoking accessibility
- * messages)
- * @return true if the touch position is valid
- */
- private boolean setSelectionFromPosition(int x, final int y, boolean keepOldSelection) {
-
- Event savedEvent = null;
- int savedDay = 0;
- int savedHour = 0;
- boolean savedAllDay = false;
- if (keepOldSelection) {
- // Store selection info and restore it at the end. This way, we can invoke the
- // right accessibility message without affecting the selection.
- savedEvent = mSelectedEvent;
- savedDay = mSelectionDay;
- savedHour = mSelectionHour;
- savedAllDay = mSelectionAllday;
- }
- if (x < mHoursWidth) {
- x = mHoursWidth;
- }
-
- int day = (x - mHoursWidth) / (mCellWidth + DAY_GAP);
- if (day >= mNumDays) {
- day = mNumDays - 1;
- }
- day += mFirstJulianDay;
- setSelectedDay(day);
-
- if (y < DAY_HEADER_HEIGHT) {
- sendAccessibilityEventAsNeeded(false);
- return false;
- }
-
- setSelectedHour(mFirstHour); /* First fully visible hour */
-
- if (y < mFirstCell) {
- mSelectionAllday = true;
- } else {
- // y is now offset from top of the scrollable region
- int adjustedY = y - mFirstCell;
-
- if (adjustedY < mFirstHourOffset) {
- setSelectedHour(mSelectionHour - 1); /* In the partially visible hour */
- } else {
- setSelectedHour(mSelectionHour +
- (adjustedY - mFirstHourOffset) / (mCellHeight + HOUR_GAP));
- }
-
- mSelectionAllday = false;
- }
-
- findSelectedEvent(x, y);
-
- sendAccessibilityEventAsNeeded(true);
-
- // Restore old values
- if (keepOldSelection) {
- mSelectedEvent = savedEvent;
- mSelectionDay = savedDay;
- mSelectionHour = savedHour;
- mSelectionAllday = savedAllDay;
- }
- return true;
- }
-
- private void findSelectedEvent(int x, int y) {
- int date = mSelectionDay;
- int cellWidth = mCellWidth;
- ArrayList<Event> events = mEvents;
- int numEvents = events.size();
- int left = computeDayLeftPosition(mSelectionDay - mFirstJulianDay);
- int top = 0;
- setSelectedEvent(null);
-
- mSelectedEvents.clear();
- if (mSelectionAllday) {
- float yDistance;
- float minYdistance = 10000.0f; // any large number
- Event closestEvent = null;
- float drawHeight = mAlldayHeight;
- int yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
- int maxUnexpandedColumn = mMaxUnexpandedAlldayEventCount;
- if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
- // Leave a gap for the 'box +n' text
- maxUnexpandedColumn--;
- }
- events = mAllDayEvents;
- numEvents = events.size();
- for (int i = 0; i < numEvents; i++) {
- Event event = events.get(i);
- if (!event.drawAsAllday() ||
- (!mShowAllAllDayEvents && event.getColumn() >= maxUnexpandedColumn)) {
- // Don't check non-allday events or events that aren't shown
- continue;
- }
-
- if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
- float numRectangles = mShowAllAllDayEvents ? mMaxAlldayEvents
- : mMaxUnexpandedAlldayEventCount;
- float height = drawHeight / numRectangles;
- if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
- height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
- }
- float eventTop = yOffset + height * event.getColumn();
- float eventBottom = eventTop + height;
- if (eventTop < y && eventBottom > y) {
- // If the touch is inside the event rectangle, then
- // add the event.
- mSelectedEvents.add(event);
- closestEvent = event;
- break;
- } else {
- // Find the closest event
- if (eventTop >= y) {
- yDistance = eventTop - y;
- } else {
- yDistance = y - eventBottom;
- }
- if (yDistance < minYdistance) {
- minYdistance = yDistance;
- closestEvent = event;
- }
- }
- }
- }
- setSelectedEvent(closestEvent);
- return;
- }
-
- // Adjust y for the scrollable bitmap
- y += mViewStartY - mFirstCell;
-
- // Use a region around (x,y) for the selection region
- Rect region = mRect;
- region.left = x - 10;
- region.right = x + 10;
- region.top = y - 10;
- region.bottom = y + 10;
-
- EventGeometry geometry = mEventGeometry;
-
- for (int i = 0; i < numEvents; i++) {
- Event event = events.get(i);
- // Compute the event rectangle.
- if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
- continue;
- }
-
- // If the event intersects the selection region, then add it to
- // mSelectedEvents.
- if (geometry.eventIntersectsSelection(event, region)) {
- mSelectedEvents.add(event);
- }
- }
-
- // If there are any events in the selected region, then assign the
- // closest one to mSelectedEvent.
- if (mSelectedEvents.size() > 0) {
- int len = mSelectedEvents.size();
- Event closestEvent = null;
- float minDist = mViewWidth + mViewHeight; // some large distance
- for (int index = 0; index < len; index++) {
- Event ev = mSelectedEvents.get(index);
- float dist = geometry.pointToEvent(x, y, ev);
- if (dist < minDist) {
- minDist = dist;
- closestEvent = ev;
- }
- }
- setSelectedEvent(closestEvent);
-
- // Keep the selected hour and day consistent with the selected
- // event. They could be different if we touched on an empty hour
- // slot very close to an event in the previous hour slot. In
- // that case we will select the nearby event.
- int startDay = mSelectedEvent.startDay;
- int endDay = mSelectedEvent.endDay;
- if (mSelectionDay < startDay) {
- setSelectedDay(startDay);
- } else if (mSelectionDay > endDay) {
- setSelectedDay(endDay);
- }
-
- int startHour = mSelectedEvent.startTime / 60;
- int endHour;
- if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
- endHour = (mSelectedEvent.endTime - 1) / 60;
- } else {
- endHour = mSelectedEvent.endTime / 60;
- }
-
- if (mSelectionHour < startHour && mSelectionDay == startDay) {
- setSelectedHour(startHour);
- } else if (mSelectionHour > endHour && mSelectionDay == endDay) {
- setSelectedHour(endHour);
- }
- }
- }
-
- // Encapsulates the code to continue the scrolling after the
- // finger is lifted. Instead of stopping the scroll immediately,
- // the scroll continues to "free spin" and gradually slows down.
- private class ContinueScroll implements Runnable {
-
- public void run() {
- mScrolling = mScrolling && mScroller.computeScrollOffset();
- if (!mScrolling || mPaused) {
- resetSelectedHour();
- invalidate();
- return;
- }
-
- mViewStartY = mScroller.getCurrY();
-
- if (mCallEdgeEffectOnAbsorb) {
- if (mViewStartY < 0) {
- mEdgeEffectTop.onAbsorb((int) mLastVelocity);
- mCallEdgeEffectOnAbsorb = false;
- } else if (mViewStartY > mMaxViewStartY) {
- mEdgeEffectBottom.onAbsorb((int) mLastVelocity);
- mCallEdgeEffectOnAbsorb = false;
- }
- mLastVelocity = mScroller.getCurrVelocity();
- }
-
- if (mScrollStartY == 0 || mScrollStartY == mMaxViewStartY) {
- // Allow overscroll/springback only on a fling,
- // not a pull/fling from the end
- if (mViewStartY < 0) {
- mViewStartY = 0;
- } else if (mViewStartY > mMaxViewStartY) {
- mViewStartY = mMaxViewStartY;
- }
- }
-
- computeFirstHour();
- mHandler.post(this);
- invalidate();
- }
- }
-
- /**
- * Cleanup the pop-up and timers.
- */
- public void cleanup() {
- // Protect against null-pointer exceptions
- if (mPopup != null) {
- mPopup.dismiss();
- }
- mPaused = true;
- mLastPopupEventID = INVALID_EVENT_ID;
- if (mHandler != null) {
- mHandler.removeCallbacks(mDismissPopup);
- mHandler.removeCallbacks(mUpdateCurrentTime);
- }
-
- Utils.setSharedPreference(mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
- mCellHeight);
- // Clear all click animations
- eventClickCleanup();
- // Turn off redraw
- mRemeasure = false;
- // Turn off scrolling to make sure the view is in the correct state if we fling back to it
- mScrolling = false;
- }
-
- private void eventClickCleanup() {
- this.removeCallbacks(mClearClick);
- this.removeCallbacks(mSetClick);
- mClickedEvent = null;
- mSavedClickedEvent = null;
- }
-
- private void setSelectedEvent(Event e) {
- mSelectedEvent = e;
- mSelectedEventForAccessibility = e;
- }
-
- private void setSelectedHour(int h) {
- mSelectionHour = h;
- mSelectionHourForAccessibility = h;
- }
- private void setSelectedDay(int d) {
- mSelectionDay = d;
- mSelectionDayForAccessibility = d;
- }
-
- /**
- * Restart the update timer
- */
- public void restartCurrentTimeUpdates() {
- mPaused = false;
- if (mHandler != null) {
- mHandler.removeCallbacks(mUpdateCurrentTime);
- mHandler.post(mUpdateCurrentTime);
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- cleanup();
- super.onDetachedFromWindow();
- }
-
- class DismissPopup implements Runnable {
-
- public void run() {
- // Protect against null-pointer exceptions
- if (mPopup != null) {
- mPopup.dismiss();
- }
- }
- }
-
- class UpdateCurrentTime implements Runnable {
-
- public void run() {
- long currentTime = System.currentTimeMillis();
- mCurrentTime.set(currentTime);
- //% causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
- if (!DayView.this.mPaused) {
- mHandler.postDelayed(mUpdateCurrentTime, UPDATE_CURRENT_TIME_DELAY
- - (currentTime % UPDATE_CURRENT_TIME_DELAY));
- }
- mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
- invalidate();
- }
- }
-
- class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent ev) {
- if (DEBUG) Log.e(TAG, "GestureDetector.onSingleTapUp");
- DayView.this.doSingleTapUp(ev);
- return true;
- }
-
- @Override
- public void onLongPress(MotionEvent ev) {
- if (DEBUG) Log.e(TAG, "GestureDetector.onLongPress");
- DayView.this.doLongPress(ev);
- }
-
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (DEBUG) Log.e(TAG, "GestureDetector.onScroll");
- eventClickCleanup();
- if (mTouchStartedInAlldayArea) {
- if (Math.abs(distanceX) < Math.abs(distanceY)) {
- // Make sure that click feedback is gone when you scroll from the
- // all day area
- invalidate();
- return false;
- }
- // don't scroll vertically if this started in the allday area
- distanceY = 0;
- }
- DayView.this.doScroll(e1, e2, distanceX, distanceY);
- return true;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (DEBUG) Log.e(TAG, "GestureDetector.onFling");
-
- if (mTouchStartedInAlldayArea) {
- if (Math.abs(velocityX) < Math.abs(velocityY)) {
- return false;
- }
- // don't fling vertically if this started in the allday area
- velocityY = 0;
- }
- DayView.this.doFling(e1, e2, velocityX, velocityY);
- return true;
- }
-
- @Override
- public boolean onDown(MotionEvent ev) {
- if (DEBUG) Log.e(TAG, "GestureDetector.onDown");
- DayView.this.doDown(ev);
- return true;
- }
- }
-
- @Override
- public boolean onLongClick(View v) {
- return true;
- }
-
- // The rest of this file was borrowed from Launcher2 - PagedView.java
- private static final int MINIMUM_SNAP_VELOCITY = 2200;
-
- private class ScrollInterpolator implements Interpolator {
- public ScrollInterpolator() {
- }
-
- public float getInterpolation(float t) {
- t -= 1.0f;
- t = t * t * t * t * t + 1;
-
- if ((1 - t) * mAnimationDistance < 1) {
- cancelAnimation();
- }
-
- return t;
- }
- }
-
- private long calculateDuration(float delta, float width, float velocity) {
- /*
- * Here we compute a "distance" that will be used in the computation of
- * the overall snap duration. This is a function of the actual distance
- * that needs to be traveled; we keep this value close to half screen
- * size in order to reduce the variance in snap duration as a function
- * of the distance the page needs to travel.
- */
- final float halfScreenSize = width / 2;
- float distanceRatio = delta / width;
- float distanceInfluenceForSnapDuration = distanceInfluenceForSnapDuration(distanceRatio);
- float distance = halfScreenSize + halfScreenSize * distanceInfluenceForSnapDuration;
-
- velocity = Math.abs(velocity);
- velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
-
- /*
- * we want the page's snap velocity to approximately match the velocity
- * at which the user flings, so we scale the duration by a value near to
- * the derivative of the scroll interpolator at zero, ie. 5. We use 6 to
- * make it a little slower.
- */
- long duration = 6 * Math.round(1000 * Math.abs(distance / velocity));
- if (DEBUG) {
- Log.e(TAG, "halfScreenSize:" + halfScreenSize + " delta:" + delta + " distanceRatio:"
- + distanceRatio + " distance:" + distance + " velocity:" + velocity
- + " duration:" + duration + " distanceInfluenceForSnapDuration:"
- + distanceInfluenceForSnapDuration);
- }
- return duration;
- }
-
- /*
- * We want the duration of the page snap animation to be influenced by the
- * distance that the screen has to travel, however, we don't want this
- * duration to be effected in a purely linear fashion. Instead, we use this
- * method to moderate the effect that the distance of travel has on the
- * overall snap duration.
- */
- private float distanceInfluenceForSnapDuration(float f) {
- f -= 0.5f; // center the values about 0.
- f *= 0.3f * Math.PI / 2.0f;
- return (float) Math.sin(f);
- }
-}
diff --git a/src/com/android/calendar/DayView.kt b/src/com/android/calendar/DayView.kt
new file mode 100644
index 00000000..42621638
--- /dev/null
+++ b/src/com/android/calendar/DayView.kt
@@ -0,0 +1,3990 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.app.Service
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.text.Layout.Alignment
+import android.text.SpannableStringBuilder
+import android.text.StaticLayout
+import android.text.TextPaint
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.text.style.StyleSpan
+import android.util.Log
+import android.view.ContextMenu
+import android.view.ContextMenu.ContextMenuInfo
+import android.view.GestureDetector
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.view.animation.Animation
+import android.view.animation.Interpolator
+import android.view.animation.TranslateAnimation
+import android.widget.EdgeEffect
+import android.widget.OverScroller
+import android.widget.PopupWindow
+import android.widget.ViewSwitcher
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Calendar
+import java.util.Formatter
+import java.util.Locale
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+/**
+ * View for multi-day view. So far only 1 and 7 day have been tested.
+ */
+class DayView(
+ context: Context?,
+ controller: CalendarController?,
+ viewSwitcher: ViewSwitcher?,
+ eventLoader: EventLoader?,
+ numDays: Int
+) : View(context), View.OnCreateContextMenuListener, ScaleGestureDetector.OnScaleGestureListener,
+ View.OnClickListener, View.OnLongClickListener {
+ private var mOnFlingCalled = false
+ private var mStartingScroll = false
+ protected var mPaused = true
+ private var mHandler: Handler? = null
+
+ /**
+ * ID of the last event which was displayed with the toast popup.
+ *
+ * This is used to prevent popping up multiple quick views for the same event, especially
+ * during calendar syncs. This becomes valid when an event is selected, either by default
+ * on starting calendar or by scrolling to an event. It becomes invalid when the user
+ * explicitly scrolls to an empty time slot, changes views, or deletes the event.
+ */
+ private var mLastPopupEventID: Long
+ protected var mContext: Context? = null
+ private val mContinueScroll: ContinueScroll = ContinueScroll()
+
+ // Make this visible within the package for more informative debugging
+ var mBaseDate: Time? = null
+ private var mCurrentTime: Time? = null
+ private val mUpdateCurrentTime: UpdateCurrentTime = UpdateCurrentTime()
+ private var mTodayJulianDay = 0
+ private val mBold: Typeface = Typeface.DEFAULT_BOLD
+ private var mFirstJulianDay = 0
+ private var mLoadedFirstJulianDay = -1
+ private var mLastJulianDay = 0
+ private var mMonthLength = 0
+ private var mFirstVisibleDate = 0
+ private var mFirstVisibleDayOfWeek = 0
+ private var mEarliestStartHour: IntArray? = null // indexed by the week day offset
+ private var mHasAllDayEvent: BooleanArray? = null // indexed by the week day offset
+ private var mEventCountTemplate: String? = null
+ private var mClickedEvent: Event? = null // The event the user clicked on
+ private var mSavedClickedEvent: Event? = null
+ private var mClickedYLocation = 0
+ private var mDownTouchTime: Long = 0
+ private var mEventsAlpha = 255
+ private var mEventsCrossFadeAnimation: ObjectAnimator? = null
+ private val mTZUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ val tz: String? = Utils.getTimeZone(mContext, this)
+ mBaseDate!!.timezone = tz
+ mBaseDate?.normalize(true)
+ mCurrentTime?.switchTimezone(tz)
+ invalidate()
+ }
+ }
+
+ // Sets the "clicked" color from the clicked event
+ private val mSetClick: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ mClickedEvent = mSavedClickedEvent
+ mSavedClickedEvent = null
+ this@DayView.invalidate()
+ }
+ }
+
+ // Clears the "clicked" color from the clicked event and launch the event
+ private val mClearClick: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (mClickedEvent != null) {
+ mController?.sendEventRelatedEvent(
+ this as Object?, EventType.VIEW_EVENT, mClickedEvent!!.id,
+ mClickedEvent!!.startMillis, mClickedEvent!!.endMillis,
+ this@DayView.getWidth() / 2, mClickedYLocation,
+ selectedTimeInMillis
+ )
+ }
+ mClickedEvent = null
+ this@DayView.invalidate()
+ }
+ }
+ private val mTodayAnimatorListener: TodayAnimatorListener = TodayAnimatorListener()
+
+ internal inner class TodayAnimatorListener : AnimatorListenerAdapter() {
+ @Volatile
+ private var mAnimator: Animator? = null
+
+ @Volatile
+ private var mFadingIn = false
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ synchronized(this) {
+ if (mAnimator !== animation) {
+ animation.removeAllListeners()
+ animation.cancel()
+ return
+ }
+ if (mFadingIn) {
+ if (mTodayAnimator != null) {
+ mTodayAnimator?.removeAllListeners()
+ mTodayAnimator?.cancel()
+ }
+ mTodayAnimator = ObjectAnimator
+ .ofInt(this@DayView, "animateTodayAlpha", 255, 0)
+ mAnimator = mTodayAnimator
+ mFadingIn = false
+ mTodayAnimator?.addListener(this)
+ mTodayAnimator?.setDuration(600)
+ mTodayAnimator?.start()
+ } else {
+ mAnimateToday = false
+ mAnimateTodayAlpha = 0
+ mAnimator?.removeAllListeners()
+ mAnimator = null
+ mTodayAnimator = null
+ invalidate()
+ }
+ }
+ }
+
+ fun setAnimator(animation: Animator?) {
+ mAnimator = animation
+ }
+
+ fun setFadingIn(fadingIn: Boolean) {
+ mFadingIn = fadingIn
+ }
+ }
+
+ var mAnimatorListener: AnimatorListenerAdapter = object : AnimatorListenerAdapter() {
+ @Override
+ override fun onAnimationStart(animation: Animator) {
+ mScrolling = true
+ }
+
+ @Override
+ override fun onAnimationCancel(animation: Animator) {
+ mScrolling = false
+ }
+
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ mScrolling = false
+ resetSelectedHour()
+ invalidate()
+ }
+ }
+
+ /**
+ * This variable helps to avoid unnecessarily reloading events by keeping
+ * track of the start millis parameter used for the most recent loading
+ * of events. If the next reload matches this, then the events are not
+ * reloaded. To force a reload, set this to zero (this is set to zero
+ * in the method clearCachedEvents()).
+ */
+ private var mLastReloadMillis: Long = 0
+ private var mEvents: ArrayList<Event> = ArrayList<Event>()
+ private var mAllDayEvents: ArrayList<Event>? = ArrayList<Event>()
+ private var mLayouts: Array<StaticLayout?>? = null
+ private var mAllDayLayouts: Array<StaticLayout?>? = null
+ private var mSelectionDay = 0 // Julian day
+ private var mSelectionHour = 0
+ var mSelectionAllday = false
+
+ // Current selection info for accessibility
+ private var mSelectionDayForAccessibility = 0 // Julian day
+ private var mSelectionHourForAccessibility = 0
+ private var mSelectedEventForAccessibility: Event? = null
+
+ // Last selection info for accessibility
+ private var mLastSelectionDayForAccessibility = 0
+ private var mLastSelectionHourForAccessibility = 0
+ private var mLastSelectedEventForAccessibility: Event? = null
+
+ /** Width of a day or non-conflicting event */
+ private var mCellWidth = 0
+
+ // Pre-allocate these objects and re-use them
+ private val mRect: Rect = Rect()
+ private val mDestRect: Rect = Rect()
+ private val mSelectionRect: Rect = Rect()
+
+ // This encloses the more allDay events icon
+ private val mExpandAllDayRect: Rect = Rect()
+
+ // TODO Clean up paint usage
+ private val mPaint: Paint = Paint()
+ private val mEventTextPaint: Paint = Paint()
+ private val mSelectionPaint: Paint = Paint()
+ private var mLines: FloatArray = emptyArray<Float>().toFloatArray()
+ private var mFirstDayOfWeek = 0 // First day of the week
+ private var mPopup: PopupWindow? = null
+ private var mPopupView: View? = null
+ private val mDismissPopup: DismissPopup = DismissPopup()
+ private var mRemeasure = true
+ private val mEventLoader: EventLoader
+ protected val mEventGeometry: EventGeometry
+ private var mAnimationDistance = 0f
+ private var mViewStartX = 0
+ private var mViewStartY = 0
+ private var mMaxViewStartY = 0
+ private var mViewHeight = 0
+ private var mViewWidth = 0
+ private var mGridAreaHeight = -1
+ private var mScrollStartY = 0
+ private var mPreviousDirection = 0
+
+ /**
+ * Vertical distance or span between the two touch points at the start of a
+ * scaling gesture
+ */
+ private var mStartingSpanY = 0f
+
+ /** Height of 1 hour in pixels at the start of a scaling gesture */
+ private var mCellHeightBeforeScaleGesture = 0
+
+ /** The hour at the center two touch points */
+ private var mGestureCenterHour = 0f
+ private var mRecalCenterHour = false
+
+ /**
+ * Flag to decide whether to handle the up event. Cases where up events
+ * should be ignored are 1) right after a scale gesture and 2) finger was
+ * down before app launch
+ */
+ private var mHandleActionUp = true
+ private var mHoursTextHeight = 0
+
+ /**
+ * The height of the area used for allday events
+ */
+ private var mAlldayHeight = 0
+
+ /**
+ * The height of the allday event area used during animation
+ */
+ private var mAnimateDayHeight = 0
+
+ /**
+ * The height of an individual allday event during animation
+ */
+ private var mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+
+ /**
+ * Max of all day events in a given day in this view.
+ */
+ private var mMaxAlldayEvents = 0
+
+ /**
+ * A count of the number of allday events that were not drawn for each day
+ */
+ private var mSkippedAlldayEvents: IntArray? = null
+
+ /**
+ * The number of allDay events at which point we start hiding allDay events.
+ */
+ private var mMaxUnexpandedAlldayEventCount = 4
+ protected var mNumDays = 7
+ private var mNumHours = 10
+
+ /** Width of the time line (list of hours) to the left. */
+ private var mHoursWidth = 0
+ private var mDateStrWidth = 0
+
+ /** Top of the scrollable region i.e. below date labels and all day events */
+ private var mFirstCell = 0
+
+ /** First fully visible hour */
+ private var mFirstHour = -1
+
+ /** Distance between the mFirstCell and the top of first fully visible hour. */
+ private var mFirstHourOffset = 0
+ private var mHourStrs: Array<String>? = null
+ private var mDayStrs: Array<String?>? = null
+ private var mDayStrs2Letter: Array<String?>? = null
+ private var mIs24HourFormat = false
+ private val mSelectedEvents: ArrayList<Event> = ArrayList<Event>()
+ private var mComputeSelectedEvents = false
+ private var mUpdateToast = false
+ private var mSelectedEvent: Event? = null
+ private var mPrevSelectedEvent: Event? = null
+ private val mPrevBox: Rect = Rect()
+ protected val mResources: Resources
+ protected val mCurrentTimeLine: Drawable
+ protected val mCurrentTimeAnimateLine: Drawable
+ protected val mTodayHeaderDrawable: Drawable
+ protected val mExpandAlldayDrawable: Drawable
+ protected val mCollapseAlldayDrawable: Drawable
+ protected var mAcceptedOrTentativeEventBoxDrawable: Drawable
+ private var mAmString: String? = null
+ private var mPmString: String? = null
+ var mScaleGestureDetector: ScaleGestureDetector
+ private var mTouchMode = TOUCH_MODE_INITIAL_STATE
+ private var mSelectionMode = SELECTION_HIDDEN
+ private var mScrolling = false
+
+ // Pixels scrolled
+ private var mInitialScrollX = 0f
+ private var mInitialScrollY = 0f
+ private var mAnimateToday = false
+ private var mAnimateTodayAlpha = 0
+
+ // Animates the height of the allday region
+ var mAlldayAnimator: ObjectAnimator? = null
+
+ // Animates the height of events in the allday region
+ var mAlldayEventAnimator: ObjectAnimator? = null
+
+ // Animates the transparency of the more events text
+ var mMoreAlldayEventsAnimator: ObjectAnimator? = null
+
+ // Animates the current time marker when Today is pressed
+ var mTodayAnimator: ObjectAnimator? = null
+
+ // whether or not an event is stopping because it was cancelled
+ private var mCancellingAnimations = false
+
+ // tracks whether a touch originated in the allday area
+ private var mTouchStartedInAlldayArea = false
+ private val mController: CalendarController
+ private val mViewSwitcher: ViewSwitcher
+ private val mGestureDetector: GestureDetector
+ private val mScroller: OverScroller
+ private val mEdgeEffectTop: EdgeEffect
+ private val mEdgeEffectBottom: EdgeEffect
+ private var mCallEdgeEffectOnAbsorb = false
+ private val OVERFLING_DISTANCE: Int
+ private var mLastVelocity = 0f
+ private val mHScrollInterpolator: ScrollInterpolator
+ private var mAccessibilityMgr: AccessibilityManager? = null
+ private var mIsAccessibilityEnabled = false
+ private var mTouchExplorationEnabled = false
+ private val mNewEventHintString: String
+ @Override
+ protected override fun onAttachedToWindow() {
+ if (mHandler == null) {
+ mHandler = getHandler()
+ mHandler?.post(mUpdateCurrentTime)
+ }
+ }
+
+ private fun init(context: Context) {
+ setFocusable(true)
+
+ // Allow focus in touch mode so that we can do keyboard shortcuts
+ // even after we've entered touch mode.
+ setFocusableInTouchMode(true)
+ setClickable(true)
+ setOnCreateContextMenuListener(this)
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(context)
+ mCurrentTime = Time(Utils.getTimeZone(context, mTZUpdater))
+ val currentTime: Long = System.currentTimeMillis()
+ mCurrentTime?.set(currentTime)
+ mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
+ mWeek_saturdayColor = mResources.getColor(R.color.week_saturday)
+ mWeek_sundayColor = mResources.getColor(R.color.week_sunday)
+ mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color)
+ mFutureBgColorRes = mResources.getColor(R.color.calendar_future_bg_color)
+ mBgColor = mResources.getColor(R.color.calendar_hour_background)
+ mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label)
+ mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected)
+ mCalendarGridLineInnerHorizontalColor = mResources
+ .getColor(R.color.calendar_grid_line_inner_horizontal_color)
+ mCalendarGridLineInnerVerticalColor = mResources
+ .getColor(R.color.calendar_grid_line_inner_vertical_color)
+ mCalendarHourLabelColor = mResources.getColor(R.color.calendar_hour_label)
+ mEventTextColor = mResources.getColor(R.color.calendar_event_text_color)
+ mMoreEventsTextColor = mResources.getColor(R.color.month_event_other_color)
+ mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE)
+ mEventTextPaint.setTextAlign(Paint.Align.LEFT)
+ mEventTextPaint.setAntiAlias(true)
+ val gridLineColor: Int = mResources.getColor(R.color.calendar_grid_line_highlight_color)
+ var p: Paint = mSelectionPaint
+ p.setColor(gridLineColor)
+ p.setStyle(Style.FILL)
+ p.setAntiAlias(false)
+ p = mPaint
+ p.setAntiAlias(true)
+
+ // Allocate space for 2 weeks worth of weekday names so that we can
+ // easily start the week display at any week day.
+ mDayStrs = arrayOfNulls(14)
+
+ // Also create an array of 2-letter abbreviations.
+ mDayStrs2Letter = arrayOfNulls(14)
+ for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+ val index: Int = i - Calendar.SUNDAY
+ // e.g. Tue for Tuesday
+ mDayStrs!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM)
+ .toUpperCase()
+ mDayStrs!![index + 7] = mDayStrs!![index]
+ // e.g. Tu for Tuesday
+ mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT)
+ .toUpperCase()
+
+ // If we don't have 2-letter day strings, fall back to 1-letter.
+ if (mDayStrs2Letter!![index]!!.equals(mDayStrs!![index])) {
+ mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i,
+ DateUtils.LENGTH_SHORTEST)
+ }
+ mDayStrs2Letter!![index + 7] = mDayStrs2Letter!![index]
+ }
+
+ // Figure out how much space we need for the 3-letter abbrev names
+ // in the worst case.
+ p.setTextSize(DATE_HEADER_FONT_SIZE)
+ p.setTypeface(mBold)
+ val dateStrs = arrayOf<String?>(" 28", " 30")
+ mDateStrWidth = computeMaxStringWidth(0, dateStrs, p)
+ p.setTextSize(DAY_HEADER_FONT_SIZE)
+ mDateStrWidth += computeMaxStringWidth(0, mDayStrs as Array<String?>, p)
+ p.setTextSize(HOURS_TEXT_SIZE)
+ p.setTypeface(null)
+ handleOnResume()
+ mAmString = DateUtils.getAMPMString(Calendar.AM).toUpperCase()
+ mPmString = DateUtils.getAMPMString(Calendar.PM).toUpperCase()
+ val ampm = arrayOf(mAmString, mPmString)
+ p.setTextSize(AMPM_TEXT_SIZE)
+ mHoursWidth = Math.max(
+ HOURS_MARGIN, computeMaxStringWidth(mHoursWidth, ampm, p) +
+ HOURS_RIGHT_MARGIN
+ )
+ mHoursWidth = Math.max(MIN_HOURS_WIDTH, mHoursWidth)
+ val inflater: LayoutInflater
+ inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ mPopupView = inflater.inflate(R.layout.bubble_event, null)
+ mPopupView?.setLayoutParams(
+ LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
+ mPopup = PopupWindow(context)
+ mPopup?.setContentView(mPopupView)
+ val dialogTheme: Resources.Theme = getResources().newTheme()
+ dialogTheme.applyStyle(android.R.style.Theme_Dialog, true)
+ val ta: TypedArray = dialogTheme.obtainStyledAttributes(
+ intArrayOf(
+ android.R.attr.windowBackground
+ )
+ )
+ mPopup?.setBackgroundDrawable(ta.getDrawable(0))
+ ta.recycle()
+
+ // Enable touching the popup window
+ mPopupView?.setOnClickListener(this)
+ // Catch long clicks for creating a new event
+ setOnLongClickListener(this)
+ mBaseDate = Time(Utils.getTimeZone(context, mTZUpdater))
+ val millis: Long = System.currentTimeMillis()
+ mBaseDate?.set(millis)
+ mEarliestStartHour = IntArray(mNumDays)
+ mHasAllDayEvent = BooleanArray(mNumDays)
+
+ // mLines is the array of points used with Canvas.drawLines() in
+ // drawGridBackground() and drawAllDayEvents(). Its size depends
+ // on the max number of lines that can ever be drawn by any single
+ // drawLines() call in either of those methods.
+ val maxGridLines = (24 + 1 + // max horizontal lines we might draw
+ (mNumDays + 1)) // max vertical lines we might draw
+ mLines = FloatArray(maxGridLines * 4)
+ }
+
+ /**
+ * This is called when the popup window is pressed.
+ */
+ override fun onClick(v: View) {
+ if (v === mPopupView) {
+ // Pretend it was a trackball click because that will always
+ // jump to the "View event" screen.
+ switchViews(true /* trackball */)
+ }
+ }
+
+ fun handleOnResume() {
+ initAccessibilityVariables()
+ if (Utils.getSharedPreference(mContext, OtherPreferences.KEY_OTHER_1, false)) {
+ mFutureBgColor = 0
+ } else {
+ mFutureBgColor = mFutureBgColorRes
+ }
+ mIs24HourFormat = DateFormat.is24HourFormat(mContext)
+ mHourStrs = if (mIs24HourFormat) CalendarData.s24Hours else CalendarData.s12HoursNoAmPm
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mLastSelectionDayForAccessibility = 0
+ mLastSelectionHourForAccessibility = 0
+ mLastSelectedEventForAccessibility = null
+ mSelectionMode = SELECTION_HIDDEN
+ }
+
+ private fun initAccessibilityVariables() {
+ mAccessibilityMgr = mContext
+ ?.getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ mIsAccessibilityEnabled = mAccessibilityMgr != null && mAccessibilityMgr!!.isEnabled()
+ mTouchExplorationEnabled = isTouchExplorationEnabled
+ } /* ignore isDst */ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ /**
+ * Returns the start of the selected time in milliseconds since the epoch.
+ *
+ * @return selected time in UTC milliseconds since the epoch.
+ */
+ val selectedTimeInMillis: Long
+ get() {
+ val time = Time(mBaseDate)
+ time.setJulianDay(mSelectionDay)
+ time.hour = mSelectionHour
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ return time.normalize(true /* ignore isDst */)
+ } /* ignore isDst */
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ val selectedTime: Time
+ get() {
+ val time = Time(mBaseDate)
+ time.setJulianDay(mSelectionDay)
+ time.hour = mSelectionHour
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ time.normalize(true /* ignore isDst */)
+ return time
+ } /* ignore isDst */
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ val selectedTimeForAccessibility: Time
+ get() {
+ val time = Time(mBaseDate)
+ time.setJulianDay(mSelectionDayForAccessibility)
+ time.hour = mSelectionHourForAccessibility
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ time.normalize(true /* ignore isDst */)
+ return time
+ }
+
+ /**
+ * Returns the start of the selected time in minutes since midnight,
+ * local time. The derived class must ensure that this is consistent
+ * with the return value from getSelectedTimeInMillis().
+ */
+ val selectedMinutesSinceMidnight: Int
+ get() = mSelectionHour * MINUTES_PER_HOUR
+ var firstVisibleHour: Int
+ get() = mFirstHour
+ set(firstHour) {
+ mFirstHour = firstHour
+ mFirstHourOffset = 0
+ }
+
+ fun setSelected(time: Time?, ignoreTime: Boolean, animateToday: Boolean) {
+ mBaseDate?.set(time)
+ setSelectedHour(mBaseDate!!.hour)
+ setSelectedEvent(null)
+ mPrevSelectedEvent = null
+ val millis: Long = mBaseDate!!.toMillis(false /* use isDst */)
+ setSelectedDay(Time.getJulianDay(millis, mBaseDate!!.gmtoff))
+ mSelectedEvents.clear()
+ mComputeSelectedEvents = true
+ var gotoY: Int = Integer.MIN_VALUE
+ if (!ignoreTime && mGridAreaHeight != -1) {
+ var lastHour = 0
+ if (mBaseDate!!.hour < mFirstHour) {
+ // Above visible region
+ gotoY = mBaseDate!!.hour * (mCellHeight + HOUR_GAP)
+ } else {
+ lastHour = ((mGridAreaHeight - mFirstHourOffset) / (mCellHeight + HOUR_GAP) +
+ mFirstHour)
+ if (mBaseDate!!.hour >= lastHour) {
+ // Below visible region
+
+ // target hour + 1 (to give it room to see the event) -
+ // grid height (to get the y of the top of the visible
+ // region)
+ gotoY = ((mBaseDate!!.hour + 1 + mBaseDate!!.minute / 60.0f) *
+ (mCellHeight + HOUR_GAP) - mGridAreaHeight).toInt()
+ }
+ }
+ if (DEBUG) {
+ Log.e(
+ TAG, "Go " + gotoY + " 1st " + mFirstHour + ":" + mFirstHourOffset + "CH " +
+ (mCellHeight + HOUR_GAP) + " lh " + lastHour + " gh " + mGridAreaHeight +
+ " ymax " + mMaxViewStartY
+ )
+ }
+ if (gotoY > mMaxViewStartY) {
+ gotoY = mMaxViewStartY
+ } else if (gotoY < 0 && gotoY != Integer.MIN_VALUE) {
+ gotoY = 0
+ }
+ }
+ recalc()
+ mRemeasure = true
+ invalidate()
+ var delayAnimateToday = false
+ if (gotoY != Integer.MIN_VALUE) {
+ val scrollAnim: ValueAnimator =
+ ObjectAnimator.ofInt(this, "viewStartY", mViewStartY, gotoY)
+ scrollAnim.setDuration(GOTO_SCROLL_DURATION.toLong())
+ scrollAnim.setInterpolator(AccelerateDecelerateInterpolator())
+ scrollAnim.addListener(mAnimatorListener)
+ scrollAnim.start()
+ delayAnimateToday = true
+ }
+ if (animateToday) {
+ synchronized(mTodayAnimatorListener) {
+ if (mTodayAnimator != null) {
+ mTodayAnimator?.removeAllListeners()
+ mTodayAnimator?.cancel()
+ }
+ mTodayAnimator = ObjectAnimator.ofInt(
+ this, "animateTodayAlpha",
+ mAnimateTodayAlpha, 255
+ )
+ mAnimateToday = true
+ mTodayAnimatorListener.setFadingIn(true)
+ mTodayAnimatorListener.setAnimator(mTodayAnimator)
+ mTodayAnimator?.addListener(mTodayAnimatorListener)
+ mTodayAnimator?.setDuration(150)
+ if (delayAnimateToday) {
+ mTodayAnimator?.setStartDelay(GOTO_SCROLL_DURATION.toLong())
+ }
+ mTodayAnimator?.start()
+ }
+ }
+ sendAccessibilityEventAsNeeded(false)
+ }
+
+ // Called from animation framework via reflection. Do not remove
+ fun setViewStartY(viewStartY: Int) {
+ var viewStartY = viewStartY
+ if (viewStartY > mMaxViewStartY) {
+ viewStartY = mMaxViewStartY
+ }
+ mViewStartY = viewStartY
+ computeFirstHour()
+ invalidate()
+ }
+
+ fun setAnimateTodayAlpha(todayAlpha: Int) {
+ mAnimateTodayAlpha = todayAlpha
+ invalidate()
+ } /* ignore isDst */
+
+ fun getSelectedDay(): Time {
+ val time = Time(mBaseDate)
+ time.setJulianDay(mSelectionDay)
+ time.hour = mSelectionHour
+
+ // We ignore the "isDst" field because we want normalize() to figure
+ // out the correct DST value and not adjust the selected time based
+ // on the current setting of DST.
+ time.normalize(true /* ignore isDst */)
+ return time
+ }
+
+ fun updateTitle() {
+ val start = Time(mBaseDate)
+ start.normalize(true)
+ val end = Time(start)
+ end.monthDay += mNumDays - 1
+ // Move it forward one minute so the formatter doesn't lose a day
+ end.minute += 1
+ end.normalize(true)
+ var formatFlags: Long = DateUtils.FORMAT_SHOW_DATE.toLong() or
+ DateUtils.FORMAT_SHOW_YEAR.toLong()
+ if (mNumDays != 1) {
+ // Don't show day of the month if for multi-day view
+ formatFlags = formatFlags or DateUtils.FORMAT_NO_MONTH_DAY.toLong()
+
+ // Abbreviate the month if showing multiple months
+ if (start.month !== end.month) {
+ formatFlags = formatFlags or DateUtils.FORMAT_ABBREV_MONTH.toLong()
+ }
+ }
+ mController.sendEvent(
+ this as Object?, EventType.UPDATE_TITLE, start, end, null, -1, ViewType.CURRENT,
+ formatFlags, null, null
+ )
+ }
+
+ /**
+ * return a negative number if "time" is comes before the visible time
+ * range, a positive number if "time" is after the visible time range, and 0
+ * if it is in the visible time range.
+ */
+ fun compareToVisibleTimeRange(time: Time): Int {
+ val savedHour: Int = mBaseDate!!.hour
+ val savedMinute: Int = mBaseDate!!.minute
+ val savedSec: Int = mBaseDate!!.second
+ mBaseDate!!.hour = 0
+ mBaseDate!!.minute = 0
+ mBaseDate!!.second = 0
+ if (DEBUG) {
+ Log.d(TAG, "Begin " + mBaseDate.toString())
+ Log.d(TAG, "Diff " + time.toString())
+ }
+
+ // Compare beginning of range
+ var diff: Int = Time.compare(time, mBaseDate)
+ if (diff > 0) {
+ // Compare end of range
+ mBaseDate!!.monthDay += mNumDays
+ mBaseDate?.normalize(true)
+ diff = Time.compare(time, mBaseDate)
+ if (DEBUG) Log.d(TAG, "End " + mBaseDate.toString())
+ mBaseDate!!.monthDay -= mNumDays
+ mBaseDate?.normalize(true)
+ if (diff < 0) {
+ // in visible time
+ diff = 0
+ } else if (diff == 0) {
+ // Midnight of following day
+ diff = 1
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Diff: $diff")
+ mBaseDate!!.hour = savedHour
+ mBaseDate!!.minute = savedMinute
+ mBaseDate!!.second = savedSec
+ return diff
+ }
+
+ private fun recalc() {
+ // Set the base date to the beginning of the week if we are displaying
+ // 7 days at a time.
+ if (mNumDays == 7) {
+ adjustToBeginningOfWeek(mBaseDate)
+ }
+ val start: Long = mBaseDate!!.toMillis(false /* use isDst */)
+ mFirstJulianDay = Time.getJulianDay(start, mBaseDate!!.gmtoff)
+ mLastJulianDay = mFirstJulianDay + mNumDays - 1
+ mMonthLength = mBaseDate!!.getActualMaximum(Time.MONTH_DAY)
+ mFirstVisibleDate = mBaseDate!!.monthDay
+ mFirstVisibleDayOfWeek = mBaseDate!!.weekDay
+ }
+
+ private fun adjustToBeginningOfWeek(time: Time?) {
+ val dayOfWeek: Int = time!!.weekDay
+ var diff = dayOfWeek - mFirstDayOfWeek
+ if (diff != 0) {
+ if (diff < 0) {
+ diff += 7
+ }
+ time!!.monthDay -= diff
+ time?.normalize(true /* ignore isDst */)
+ }
+ }
+
+ @Override
+ protected override fun onSizeChanged(width: Int, height: Int, oldw: Int, oldh: Int) {
+ mViewWidth = width
+ mViewHeight = height
+ mEdgeEffectTop.setSize(mViewWidth, mViewHeight)
+ mEdgeEffectBottom.setSize(mViewWidth, mViewHeight)
+ val gridAreaWidth = width - mHoursWidth
+ mCellWidth = (gridAreaWidth - mNumDays * DAY_GAP) / mNumDays
+
+ // This would be about 1 day worth in a 7 day view
+ mHorizontalSnapBackThreshold = width / 7
+ val p = Paint()
+ p.setTextSize(HOURS_TEXT_SIZE)
+ mHoursTextHeight = Math.abs(p.ascent()).toInt()
+ remeasure(width, height)
+ }
+
+ /**
+ * Measures the space needed for various parts of the view after
+ * loading new events. This can change if there are all-day events.
+ */
+ private fun remeasure(width: Int, height: Int) {
+ // Shrink to fit available space but make sure we can display at least two events
+ MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
+ MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.min(MAX_UNEXPANDED_ALLDAY_HEIGHT, height / 6)
+ MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.max(
+ MAX_UNEXPANDED_ALLDAY_HEIGHT,
+ MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt() * 2
+ )
+ mMaxUnexpandedAlldayEventCount =
+ (MAX_UNEXPANDED_ALLDAY_HEIGHT / MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+
+ // First, clear the array of earliest start times, and the array
+ // indicating presence of an all-day event.
+ for (day in 0 until mNumDays) {
+ mEarliestStartHour!![day] = 25 // some big number
+ mHasAllDayEvent!![day] = false
+ }
+ val maxAllDayEvents = mMaxAlldayEvents
+
+ // The min is where 24 hours cover the entire visible area
+ mMinCellHeight = Math.max((height - DAY_HEADER_HEIGHT) / 24, MIN_EVENT_HEIGHT.toInt())
+ if (mCellHeight < mMinCellHeight) {
+ mCellHeight = mMinCellHeight
+ }
+
+ // Calculate mAllDayHeight
+ mFirstCell = DAY_HEADER_HEIGHT
+ var allDayHeight = 0
+ if (maxAllDayEvents > 0) {
+ val maxAllAllDayHeight = height - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+ // If there is at most one all-day event per day, then use less
+ // space (but more than the space for a single event).
+ if (maxAllDayEvents == 1) {
+ allDayHeight = SINGLE_ALLDAY_HEIGHT
+ } else if (maxAllDayEvents <= mMaxUnexpandedAlldayEventCount) {
+ // Allow the all-day area to grow in height depending on the
+ // number of all-day events we need to show, up to a limit.
+ allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT
+ if (allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
+ allDayHeight = MAX_UNEXPANDED_ALLDAY_HEIGHT
+ }
+ } else {
+ // if we have more than the magic number, check if we're animating
+ // and if not adjust the sizes appropriately
+ if (mAnimateDayHeight != 0) {
+ // Don't shrink the space past the final allDay space. The animation
+ // continues to hide the last event so the more events text can
+ // fade in.
+ allDayHeight = Math.max(mAnimateDayHeight, MAX_UNEXPANDED_ALLDAY_HEIGHT)
+ } else {
+ // Try to fit all the events in
+ allDayHeight = (maxAllDayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+ // But clip the area depending on which mode we're in
+ if (!mShowAllAllDayEvents && allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
+ allDayHeight = (mMaxUnexpandedAlldayEventCount *
+ MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+ } else if (allDayHeight > maxAllAllDayHeight) {
+ allDayHeight = maxAllAllDayHeight
+ }
+ }
+ }
+ mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN
+ } else {
+ mSelectionAllday = false
+ }
+ mAlldayHeight = allDayHeight
+ mGridAreaHeight = height - mFirstCell
+
+ // Set up the expand icon position
+ val allDayIconWidth: Int = mExpandAlldayDrawable.getIntrinsicWidth()
+ mExpandAllDayRect.left = Math.max(
+ (mHoursWidth - allDayIconWidth) / 2,
+ EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+ )
+ mExpandAllDayRect.right = Math.min(
+ mExpandAllDayRect.left + allDayIconWidth, mHoursWidth -
+ EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
+ )
+ mExpandAllDayRect.bottom = mFirstCell - EXPAND_ALL_DAY_BOTTOM_MARGIN
+ mExpandAllDayRect.top = (mExpandAllDayRect.bottom -
+ mExpandAlldayDrawable.getIntrinsicHeight())
+ mNumHours = mGridAreaHeight / (mCellHeight + HOUR_GAP)
+ mEventGeometry.setHourHeight(mCellHeight.toFloat())
+ val minimumDurationMillis =
+ (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f)).toLong()
+ Event.computePositions(mEvents, minimumDurationMillis)
+
+ // Compute the top of our reachable view
+ mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
+ if (DEBUG) {
+ Log.e(TAG, "mViewStartY: $mViewStartY")
+ Log.e(TAG, "mMaxViewStartY: $mMaxViewStartY")
+ }
+ if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY
+ computeFirstHour()
+ }
+ if (mFirstHour == -1) {
+ initFirstHour()
+ mFirstHourOffset = 0
+ }
+
+ // When we change the base date, the number of all-day events may
+ // change and that changes the cell height. When we switch dates,
+ // we use the mFirstHourOffset from the previous view, but that may
+ // be too large for the new view if the cell height is smaller.
+ if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
+ mFirstHourOffset = mCellHeight + HOUR_GAP - 1
+ }
+ mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset
+ val eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP)
+ // When we get new events we don't want to dismiss the popup unless the event changes
+ if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent!!.id) {
+ mPopup?.dismiss()
+ }
+ mPopup?.setWidth(eventAreaWidth - 20)
+ mPopup?.setHeight(WindowManager.LayoutParams.WRAP_CONTENT)
+ }
+
+ /**
+ * Initialize the state for another view. The given view is one that has
+ * its own bitmap and will use an animation to replace the current view.
+ * The current view and new view are either both Week views or both Day
+ * views. They differ in their base date.
+ *
+ * @param view the view to initialize.
+ */
+ private fun initView(view: DayView) {
+ view.setSelectedHour(mSelectionHour)
+ view.mSelectedEvents.clear()
+ view.mComputeSelectedEvents = true
+ view.mFirstHour = mFirstHour
+ view.mFirstHourOffset = mFirstHourOffset
+ view.remeasure(getWidth(), getHeight())
+ view.initAllDayHeights()
+ view.setSelectedEvent(null)
+ view.mPrevSelectedEvent = null
+ view.mFirstDayOfWeek = mFirstDayOfWeek
+ if (view.mEvents.size > 0) {
+ view.mSelectionAllday = mSelectionAllday
+ } else {
+ view.mSelectionAllday = false
+ }
+
+ // Redraw the screen so that the selection box will be redrawn. We may
+ // have scrolled to a different part of the day in some other view
+ // so the selection box in this view may no longer be visible.
+ view.recalc()
+ }
+
+ /**
+ * Switch to another view based on what was selected (an event or a free
+ * slot) and how it was selected (by touch or by trackball).
+ *
+ * @param trackBallSelection true if the selection was made using the
+ * trackball.
+ */
+ private fun switchViews(trackBallSelection: Boolean) {
+ val selectedEvent: Event? = mSelectedEvent
+ mPopup?.dismiss()
+ mLastPopupEventID = INVALID_EVENT_ID
+ if (mNumDays > 1) {
+ // This is the Week view.
+ // With touch, we always switch to Day/Agenda View
+ // With track ball, if we selected a free slot, then create an event.
+ // If we selected a specific event, switch to EventInfo view.
+ if (trackBallSelection) {
+ if (selectedEvent != null) {
+ if (mIsAccessibilityEnabled) {
+ mAccessibilityMgr?.interrupt()
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
+ mScrolling = false
+ return super.onKeyUp(keyCode, event)
+ }
+
+ @Override
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ return super.onKeyDown(keyCode, event)
+ }
+
+ @Override
+ override fun onHoverEvent(event: MotionEvent?): Boolean {
+ return true
+ }
+
+ private val isTouchExplorationEnabled: Boolean
+ private get() = mIsAccessibilityEnabled && mAccessibilityMgr!!.isTouchExplorationEnabled()
+
+ private fun sendAccessibilityEventAsNeeded(speakEvents: Boolean) {
+ if (!mIsAccessibilityEnabled) {
+ return
+ }
+ val dayChanged = mLastSelectionDayForAccessibility != mSelectionDayForAccessibility
+ val hourChanged = mLastSelectionHourForAccessibility != mSelectionHourForAccessibility
+ if (dayChanged || hourChanged || mLastSelectedEventForAccessibility !==
+ mSelectedEventForAccessibility) {
+ mLastSelectionDayForAccessibility = mSelectionDayForAccessibility
+ mLastSelectionHourForAccessibility = mSelectionHourForAccessibility
+ mLastSelectedEventForAccessibility = mSelectedEventForAccessibility
+ val b = StringBuilder()
+
+ // Announce only the changes i.e. day or hour or both
+ if (dayChanged) {
+ b.append(selectedTimeForAccessibility.format("%A "))
+ }
+ if (hourChanged) {
+ b.append(selectedTimeForAccessibility.format(if (mIs24HourFormat) "%k" else "%l%p"))
+ }
+ if (dayChanged || hourChanged) {
+ b.append(PERIOD_SPACE)
+ }
+ if (speakEvents) {
+ if (mEventCountTemplate == null) {
+ mEventCountTemplate = mContext?.getString(R.string.template_announce_item_index)
+ }
+
+ // Read out the relevant event(s)
+ val numEvents: Int = mSelectedEvents.size
+ if (numEvents > 0) {
+ if (mSelectedEventForAccessibility == null) {
+ // Read out all the events
+ var i = 1
+ for (calEvent in mSelectedEvents) {
+ if (numEvents > 1) {
+ // Read out x of numEvents if there are more than one event
+ mStringBuilder.setLength(0)
+ b.append(mFormatter.format(mEventCountTemplate, i++, numEvents))
+ b.append(" ")
+ }
+ appendEventAccessibilityString(b, calEvent)
+ }
+ } else {
+ if (numEvents > 1) {
+ // Read out x of numEvents if there are more than one event
+ mStringBuilder.setLength(0)
+ b.append(
+ mFormatter.format(
+ mEventCountTemplate, mSelectedEvents
+ .indexOf(mSelectedEventForAccessibility) + 1, numEvents
+ )
+ )
+ b.append(" ")
+ }
+ appendEventAccessibilityString(b, mSelectedEventForAccessibility)
+ }
+ }
+ }
+ if (dayChanged || hourChanged || speakEvents) {
+ val event: AccessibilityEvent = AccessibilityEvent
+ .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ val msg: CharSequence = b.toString()
+ event.getText().add(msg)
+ event.setAddedCount(msg.length)
+ sendAccessibilityEventUnchecked(event)
+ }
+ }
+ }
+
+ /**
+ * @param b
+ * @param calEvent
+ */
+ private fun appendEventAccessibilityString(b: StringBuilder, calEvent: Event?) {
+ b.append(calEvent!!.titleAndLocation)
+ b.append(PERIOD_SPACE)
+ val `when`: String?
+ var flags: Int = DateUtils.FORMAT_SHOW_DATE
+ if (calEvent!!.allDay) {
+ flags = flags or (DateUtils.FORMAT_UTC or DateUtils.FORMAT_SHOW_WEEKDAY)
+ } else {
+ flags = flags or DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(mContext)) {
+ flags = flags or DateUtils.FORMAT_24HOUR
+ }
+ }
+ `when` = Utils.formatDateRange(mContext, calEvent!!.startMillis, calEvent!!.endMillis,
+ flags)
+ b.append(`when`)
+ b.append(PERIOD_SPACE)
+ }
+
+ private inner class GotoBroadcaster(start: Time, end: Time) : Animation.AnimationListener {
+ private val mCounter: Int
+ private val mStart: Time
+ private val mEnd: Time
+ @Override
+ override fun onAnimationEnd(animation: Animation) {
+ var view = mViewSwitcher.getCurrentView() as DayView
+ view.mViewStartX = 0
+ view = mViewSwitcher.getNextView() as DayView
+ view.mViewStartX = 0
+ if (mCounter == sCounter) {
+ mController.sendEvent(
+ this as Object?, EventType.GO_TO, mStart, mEnd, null, -1,
+ ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null
+ )
+ }
+ }
+
+ @Override
+ override fun onAnimationRepeat(animation: Animation) {
+ }
+
+ @Override
+ override fun onAnimationStart(animation: Animation) {
+ }
+
+ init {
+ mCounter = ++sCounter
+ mStart = start
+ mEnd = end
+ }
+ }
+
+ private fun switchViews(forward: Boolean, xOffSet: Float, width: Float, velocity: Float): View {
+ mAnimationDistance = width - xOffSet
+ if (DEBUG) {
+ Log.d(TAG, "switchViews($forward) O:$xOffSet Dist:$mAnimationDistance")
+ }
+ var progress: Float = Math.abs(xOffSet) / width
+ if (progress > 1.0f) {
+ progress = 1.0f
+ }
+ val inFromXValue: Float
+ val inToXValue: Float
+ val outFromXValue: Float
+ val outToXValue: Float
+ if (forward) {
+ inFromXValue = 1.0f - progress
+ inToXValue = 0.0f
+ outFromXValue = -progress
+ outToXValue = -1.0f
+ } else {
+ inFromXValue = progress - 1.0f
+ inToXValue = 0.0f
+ outFromXValue = progress
+ outToXValue = 1.0f
+ }
+ val start = Time(mBaseDate!!.timezone)
+ start.set(mController.time as Long)
+ if (forward) {
+ start.monthDay += mNumDays
+ } else {
+ start.monthDay -= mNumDays
+ }
+ mController.time = start.normalize(true)
+ var newSelected: Time? = start
+ if (mNumDays == 7) {
+ newSelected = Time(start)
+ adjustToBeginningOfWeek(start)
+ }
+ val end = Time(start)
+ end.monthDay += mNumDays - 1
+
+ // We have to allocate these animation objects each time we switch views
+ // because that is the only way to set the animation parameters.
+ val inAnimation = TranslateAnimation(
+ Animation.RELATIVE_TO_SELF, inFromXValue,
+ Animation.RELATIVE_TO_SELF, inToXValue,
+ Animation.ABSOLUTE, 0.0f,
+ Animation.ABSOLUTE, 0.0f
+ )
+ val outAnimation = TranslateAnimation(
+ Animation.RELATIVE_TO_SELF, outFromXValue,
+ Animation.RELATIVE_TO_SELF, outToXValue,
+ Animation.ABSOLUTE, 0.0f,
+ Animation.ABSOLUTE, 0.0f
+ )
+ val duration = calculateDuration(width - Math.abs(xOffSet), width, velocity)
+ inAnimation.setDuration(duration)
+ inAnimation.setInterpolator(mHScrollInterpolator)
+ outAnimation.setInterpolator(mHScrollInterpolator)
+ outAnimation.setDuration(duration)
+ outAnimation.setAnimationListener(GotoBroadcaster(start, end))
+ mViewSwitcher.setInAnimation(inAnimation)
+ mViewSwitcher.setOutAnimation(outAnimation)
+ var view = mViewSwitcher.getCurrentView() as DayView
+ view.cleanup()
+ mViewSwitcher.showNext()
+ view = mViewSwitcher.getCurrentView() as DayView
+ view.setSelected(newSelected, true, false)
+ view.requestFocus()
+ view.reloadEvents()
+ view.updateTitle()
+ view.restartCurrentTimeUpdates()
+ return view
+ }
+
+ // This is called after scrolling stops to move the selected hour
+ // to the visible part of the screen.
+ private fun resetSelectedHour() {
+ if (mSelectionHour < mFirstHour + 1) {
+ setSelectedHour(mFirstHour + 1)
+ setSelectedEvent(null)
+ mSelectedEvents.clear()
+ mComputeSelectedEvents = true
+ } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
+ setSelectedHour(mFirstHour + mNumHours - 3)
+ setSelectedEvent(null)
+ mSelectedEvents.clear()
+ mComputeSelectedEvents = true
+ }
+ }
+
+ private fun initFirstHour() {
+ mFirstHour = mSelectionHour - mNumHours / 5
+ if (mFirstHour < 0) {
+ mFirstHour = 0
+ } else if (mFirstHour + mNumHours > 24) {
+ mFirstHour = 24 - mNumHours
+ }
+ }
+
+ /**
+ * Recomputes the first full hour that is visible on screen after the
+ * screen is scrolled.
+ */
+ private fun computeFirstHour() {
+ // Compute the first full hour that is visible on screen
+ mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP)
+ mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY
+ }
+
+ private fun adjustHourSelection() {
+ if (mSelectionHour < 0) {
+ setSelectedHour(0)
+ if (mMaxAlldayEvents > 0) {
+ mPrevSelectedEvent = null
+ mSelectionAllday = true
+ }
+ }
+ if (mSelectionHour > 23) {
+ setSelectedHour(23)
+ }
+
+ // If the selected hour is at least 2 time slots from the top and
+ // bottom of the screen, then don't scroll the view.
+ if (mSelectionHour < mFirstHour + 1) {
+ // If there are all-days events for the selected day but there
+ // are no more normal events earlier in the day, then jump to
+ // the all-day event area.
+ // Exception 1: allow the user to scroll to 8am with the trackball
+ // before jumping to the all-day event area.
+ // Exception 2: if 12am is on screen, then allow the user to select
+ // 12am before going up to the all-day event area.
+ val daynum = mSelectionDay - mFirstJulianDay
+ if (daynum < mEarliestStartHour!!.size && daynum >= 0 && mMaxAlldayEvents > 0 &&
+ mEarliestStartHour!![daynum] > mSelectionHour &&
+ mFirstHour > 0 && mFirstHour < 8) {
+ mPrevSelectedEvent = null
+ mSelectionAllday = true
+ setSelectedHour(mFirstHour + 1)
+ return
+ }
+ if (mFirstHour > 0) {
+ mFirstHour -= 1
+ mViewStartY -= mCellHeight + HOUR_GAP
+ if (mViewStartY < 0) {
+ mViewStartY = 0
+ }
+ return
+ }
+ }
+ if (mSelectionHour > mFirstHour + mNumHours - 3) {
+ if (mFirstHour < 24 - mNumHours) {
+ mFirstHour += 1
+ mViewStartY += mCellHeight + HOUR_GAP
+ if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY
+ }
+ return
+ } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
+ mViewStartY = mMaxViewStartY
+ }
+ }
+ }
+
+ fun clearCachedEvents() {
+ mLastReloadMillis = 0
+ }
+
+ private val mCancelCallback: Runnable = object : Runnable {
+ override fun run() {
+ clearCachedEvents()
+ }
+ }
+
+ /* package */
+ fun reloadEvents() {
+ // Protect against this being called before this view has been
+ // initialized.
+// if (mContext == null) {
+// return;
+// }
+
+ // Make sure our time zones are up to date
+ mTZUpdater.run()
+ setSelectedEvent(null)
+ mPrevSelectedEvent = null
+ mSelectedEvents.clear()
+
+ // The start date is the beginning of the week at 12am
+ val weekStart = Time(Utils.getTimeZone(mContext, mTZUpdater))
+ weekStart.set(mBaseDate)
+ weekStart.hour = 0
+ weekStart.minute = 0
+ weekStart.second = 0
+ val millis: Long = weekStart.normalize(true /* ignore isDst */)
+
+ // Avoid reloading events unnecessarily.
+ if (millis == mLastReloadMillis) {
+ return
+ }
+ mLastReloadMillis = millis
+
+ // load events in the background
+ // mContext.startProgressSpinner();
+ val events: ArrayList<Event> = ArrayList<Event>()
+ mEventLoader.loadEventsInBackground(mNumDays, events as ArrayList<Event?>, mFirstJulianDay,
+ object : Runnable {
+ override fun run() {
+ val fadeinEvents = mFirstJulianDay != mLoadedFirstJulianDay
+ mEvents = events
+ mLoadedFirstJulianDay = mFirstJulianDay
+ if (mAllDayEvents == null) {
+ mAllDayEvents = ArrayList<Event>()
+ } else {
+ mAllDayEvents?.clear()
+ }
+
+ // Create a shorter array for all day events
+ for (e in events) {
+ if (e.drawAsAllday()) {
+ mAllDayEvents?.add(e)
+ }
+ }
+
+ // New events, new layouts
+ if (mLayouts == null || mLayouts!!.size < events.size) {
+ mLayouts = arrayOfNulls<StaticLayout>(events.size)
+ } else {
+ Arrays.fill(mLayouts, null)
+ }
+ if (mAllDayLayouts == null || mAllDayLayouts!!.size < mAllDayEvents!!.size) {
+ mAllDayLayouts = arrayOfNulls<StaticLayout>(events.size)
+ } else {
+ Arrays.fill(mAllDayLayouts, null)
+ }
+ computeEventRelations()
+ mRemeasure = true
+ mComputeSelectedEvents = true
+ recalc()
+
+ // Start animation to cross fade the events
+ if (fadeinEvents) {
+ if (mEventsCrossFadeAnimation == null) {
+ mEventsCrossFadeAnimation =
+ ObjectAnimator.ofInt(this@DayView, "EventsAlpha", 0, 255)
+ mEventsCrossFadeAnimation?.setDuration(EVENTS_CROSS_FADE_DURATION.toLong())
+ }
+ mEventsCrossFadeAnimation?.start()
+ } else {
+ invalidate()
+ }
+ }
+ }, mCancelCallback)
+ }
+
+ var eventsAlpha: Int
+ get() = mEventsAlpha
+ set(alpha) {
+ mEventsAlpha = alpha
+ invalidate()
+ }
+
+ fun stopEventsAnimation() {
+ if (mEventsCrossFadeAnimation != null) {
+ mEventsCrossFadeAnimation?.cancel()
+ }
+ mEventsAlpha = 255
+ }
+
+ private fun computeEventRelations() {
+ // Compute the layout relation between each event before measuring cell
+ // width, as the cell width should be adjusted along with the relation.
+ //
+ // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
+ // We should mark them as "overwapped". Though they are not overwapped logically, but
+ // minimum cell height implicitly expands the cell height of A and it should look like
+ // (1:00pm - 1:15pm) after the cell height adjustment.
+
+ // Compute the space needed for the all-day events, if any.
+ // Make a pass over all the events, and keep track of the maximum
+ // number of all-day events in any one day. Also, keep track of
+ // the earliest event in each day.
+ var maxAllDayEvents = 0
+ val events: ArrayList<Event> = mEvents
+ val len: Int = events.size
+ // Num of all-day-events on each day.
+ val eventsCount = IntArray(mLastJulianDay - mFirstJulianDay + 1)
+ Arrays.fill(eventsCount, 0)
+ for (ii in 0 until len) {
+ val event: Event = events.get(ii)
+ if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
+ continue
+ }
+ if (event.drawAsAllday()) {
+ // Count all the events being drawn as allDay events
+ val firstDay: Int = Math.max(event.startDay, mFirstJulianDay)
+ val lastDay: Int = Math.min(event.endDay, mLastJulianDay)
+ for (day in firstDay..lastDay) {
+ val count = ++eventsCount[day - mFirstJulianDay]
+ if (maxAllDayEvents < count) {
+ maxAllDayEvents = count
+ }
+ }
+ var daynum: Int = event.startDay - mFirstJulianDay
+ var durationDays: Int = event.endDay - event.startDay + 1
+ if (daynum < 0) {
+ durationDays += daynum
+ daynum = 0
+ }
+ if (daynum + durationDays > mNumDays) {
+ durationDays = mNumDays - daynum
+ }
+ var day = daynum
+ while (durationDays > 0) {
+ mHasAllDayEvent!![day] = true
+ day++
+ durationDays--
+ }
+ } else {
+ var daynum: Int = event.startDay - mFirstJulianDay
+ var hour: Int = event.startTime / 60
+ if (daynum >= 0 && hour < mEarliestStartHour!![daynum]) {
+ mEarliestStartHour!![daynum] = hour
+ }
+
+ // Also check the end hour in case the event spans more than
+ // one day.
+ daynum = event.endDay - mFirstJulianDay
+ hour = event.endTime / 60
+ if (daynum < mNumDays && hour < mEarliestStartHour!![daynum]) {
+ mEarliestStartHour!![daynum] = hour
+ }
+ }
+ }
+ mMaxAlldayEvents = maxAllDayEvents
+ initAllDayHeights()
+ }
+
+ @Override
+ protected override fun onDraw(canvas: Canvas) {
+ if (mRemeasure) {
+ remeasure(getWidth(), getHeight())
+ mRemeasure = false
+ }
+ canvas.save()
+ val yTranslate = (-mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight).toFloat()
+ // offset canvas by the current drag and header position
+ canvas.translate(-mViewStartX.toFloat(), yTranslate)
+ // clip to everything below the allDay area
+ val dest: Rect = mDestRect
+ dest.top = (mFirstCell - yTranslate).toInt()
+ dest.bottom = (mViewHeight - yTranslate).toInt()
+ dest.left = 0
+ dest.right = mViewWidth
+ canvas.save()
+ canvas.clipRect(dest)
+ // Draw the movable part of the view
+ doDraw(canvas)
+ // restore to having no clip
+ canvas.restore()
+ if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+ val xTranslate: Float
+ xTranslate = if (mViewStartX > 0) {
+ mViewWidth.toFloat()
+ } else {
+ -mViewWidth.toFloat()
+ }
+ // Move the canvas around to prep it for the next view
+ // specifically, shift it by a screen and undo the
+ // yTranslation which will be redone in the nextView's onDraw().
+ canvas.translate(xTranslate, -yTranslate)
+ val nextView = mViewSwitcher.getNextView() as DayView
+
+ // Prevent infinite recursive calls to onDraw().
+ nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE
+ nextView.onDraw(canvas)
+ // Move it back for this view
+ canvas.translate(-xTranslate, 0f)
+ } else {
+ // If we drew another view we already translated it back
+ // If we didn't draw another view we should be at the edge of the
+ // screen
+ canvas.translate(mViewStartX.toFloat(), -yTranslate)
+ }
+
+ // Draw the fixed areas (that don't scroll) directly to the canvas.
+ drawAfterScroll(canvas)
+ if (mComputeSelectedEvents && mUpdateToast) {
+ mUpdateToast = false
+ }
+ mComputeSelectedEvents = false
+
+ // Draw overscroll glow
+ if (!mEdgeEffectTop.isFinished()) {
+ if (DAY_HEADER_HEIGHT != 0) {
+ canvas.translate(0f, DAY_HEADER_HEIGHT.toFloat())
+ }
+ if (mEdgeEffectTop.draw(canvas)) {
+ invalidate()
+ }
+ if (DAY_HEADER_HEIGHT != 0) {
+ canvas.translate(0f, -DAY_HEADER_HEIGHT.toFloat())
+ }
+ }
+ if (!mEdgeEffectBottom.isFinished()) {
+ canvas.rotate(180f, mViewWidth.toFloat() / 2f, mViewHeight.toFloat() / 2f)
+ if (mEdgeEffectBottom.draw(canvas)) {
+ invalidate()
+ }
+ }
+ canvas.restore()
+ }
+
+ private fun drawAfterScroll(canvas: Canvas) {
+ val p: Paint = mPaint
+ val r: Rect = mRect
+ drawAllDayHighlights(r, canvas, p)
+ if (mMaxAlldayEvents != 0) {
+ drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p)
+ drawUpperLeftCorner(r, canvas, p)
+ }
+ drawScrollLine(r, canvas, p)
+ drawDayHeaderLoop(r, canvas, p)
+
+ // Draw the AM and PM indicators if we're in 12 hour mode
+ if (!mIs24HourFormat) {
+ drawAmPm(canvas, p)
+ }
+ }
+
+ // This isn't really the upper-left corner. It's the square area just
+ // below the upper-left corner, above the hours and to the left of the
+ // all-day area.
+ private fun drawUpperLeftCorner(r: Rect, canvas: Canvas, p: Paint) {
+ setupHourTextPaint(p)
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+ // Draw the allDay expand/collapse icon
+ if (mUseExpandIcon) {
+ mExpandAlldayDrawable.setBounds(mExpandAllDayRect)
+ mExpandAlldayDrawable.draw(canvas)
+ } else {
+ mCollapseAlldayDrawable.setBounds(mExpandAllDayRect)
+ mCollapseAlldayDrawable.draw(canvas)
+ }
+ }
+ }
+
+ private fun drawScrollLine(r: Rect, canvas: Canvas, p: Paint) {
+ val right = computeDayLeftPosition(mNumDays)
+ val y = mFirstCell - 1
+ p.setAntiAlias(false)
+ p.setStyle(Style.FILL)
+ p.setColor(mCalendarGridLineInnerHorizontalColor)
+ p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+ canvas.drawLine(GRID_LINE_LEFT_MARGIN, y.toFloat(), right.toFloat(), y.toFloat(), p)
+ p.setAntiAlias(true)
+ }
+
+ // Computes the x position for the left side of the given day (base 0)
+ private fun computeDayLeftPosition(day: Int): Int {
+ val effectiveWidth = mViewWidth - mHoursWidth
+ return day * effectiveWidth / mNumDays + mHoursWidth
+ }
+
+ private fun drawAllDayHighlights(r: Rect, canvas: Canvas, p: Paint) {
+ if (mFutureBgColor != 0) {
+ // First, color the labels area light gray
+ r.top = 0
+ r.bottom = DAY_HEADER_HEIGHT
+ r.left = 0
+ r.right = mViewWidth
+ p.setColor(mBgColor)
+ p.setStyle(Style.FILL)
+ canvas.drawRect(r, p)
+ // and the area that says All day
+ r.top = DAY_HEADER_HEIGHT
+ r.bottom = mFirstCell - 1
+ r.left = 0
+ r.right = mHoursWidth
+ canvas.drawRect(r, p)
+ var startIndex = -1
+ val todayIndex = mTodayJulianDay - mFirstJulianDay
+ if (todayIndex < 0) {
+ // Future
+ startIndex = 0
+ } else if (todayIndex >= 1 && todayIndex + 1 < mNumDays) {
+ // Multiday - tomorrow is visible.
+ startIndex = todayIndex + 1
+ }
+ if (startIndex >= 0) {
+ // Draw the future highlight
+ r.top = 0
+ r.bottom = mFirstCell - 1
+ r.left = computeDayLeftPosition(startIndex) + 1
+ r.right = computeDayLeftPosition(mNumDays)
+ p.setColor(mFutureBgColor)
+ p.setStyle(Style.FILL)
+ canvas.drawRect(r, p)
+ }
+ }
+ }
+
+ private fun drawDayHeaderLoop(r: Rect, canvas: Canvas, p: Paint) {
+ // Draw the horizontal day background banner
+ // p.setColor(mCalendarDateBannerBackground);
+ // r.top = 0;
+ // r.bottom = DAY_HEADER_HEIGHT;
+ // r.left = 0;
+ // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
+ // canvas.drawRect(r, p);
+ //
+ // Fill the extra space on the right side with the default background
+ // r.left = r.right;
+ // r.right = mViewWidth;
+ // p.setColor(mCalendarGridAreaBackground);
+ // canvas.drawRect(r, p);
+ if (mNumDays == 1 && ONE_DAY_HEADER_HEIGHT == 0) {
+ return
+ }
+ p.setTypeface(mBold)
+ p.setTextAlign(Paint.Align.RIGHT)
+ var cell = mFirstJulianDay
+ val dayNames: Array<String?>?
+ dayNames = if (mDateStrWidth < mCellWidth) {
+ mDayStrs
+ } else {
+ mDayStrs2Letter
+ }
+ p.setAntiAlias(true)
+ var day = 0
+ while (day < mNumDays) {
+ var dayOfWeek = day + mFirstVisibleDayOfWeek
+ if (dayOfWeek >= 14) {
+ dayOfWeek -= 14
+ }
+ var color = mCalendarDateBannerTextColor
+ if (mNumDays == 1) {
+ if (dayOfWeek == Time.SATURDAY) {
+ color = mWeek_saturdayColor
+ } else if (dayOfWeek == Time.SUNDAY) {
+ color = mWeek_sundayColor
+ }
+ } else {
+ val column = day % 7
+ if (Utils.isSaturday(column, mFirstDayOfWeek)) {
+ color = mWeek_saturdayColor
+ } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
+ color = mWeek_sundayColor
+ }
+ }
+ p.setColor(color)
+ drawDayHeader(dayNames!![dayOfWeek], day, cell, canvas, p)
+ day++
+ cell++
+ }
+ p.setTypeface(null)
+ }
+
+ private fun drawAmPm(canvas: Canvas, p: Paint) {
+ p.setColor(mCalendarAmPmLabel)
+ p.setTextSize(AMPM_TEXT_SIZE)
+ p.setTypeface(mBold)
+ p.setAntiAlias(true)
+ p.setTextAlign(Paint.Align.RIGHT)
+ var text = mAmString
+ if (mFirstHour >= 12) {
+ text = mPmString
+ }
+ var y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP
+ canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+ if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
+ // Also draw the "PM"
+ text = mPmString
+ y =
+ mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP) +
+ 2 * mHoursTextHeight + HOUR_GAP
+ canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+ }
+ }
+
+ private fun drawCurrentTimeLine(
+ r: Rect,
+ day: Int,
+ top: Int,
+ canvas: Canvas,
+ p: Paint
+ ) {
+ r.left = computeDayLeftPosition(day) - CURRENT_TIME_LINE_SIDE_BUFFER + 1
+ r.right = computeDayLeftPosition(day + 1) + CURRENT_TIME_LINE_SIDE_BUFFER + 1
+ r.top = top - CURRENT_TIME_LINE_TOP_OFFSET
+ r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight()
+ mCurrentTimeLine.setBounds(r)
+ mCurrentTimeLine.draw(canvas)
+ if (mAnimateToday) {
+ mCurrentTimeAnimateLine.setBounds(r)
+ mCurrentTimeAnimateLine.setAlpha(mAnimateTodayAlpha)
+ mCurrentTimeAnimateLine.draw(canvas)
+ }
+ }
+
+ private fun doDraw(canvas: Canvas) {
+ val p: Paint = mPaint
+ val r: Rect = mRect
+ if (mFutureBgColor != 0) {
+ drawBgColors(r, canvas, p)
+ }
+ drawGridBackground(r, canvas, p)
+ drawHours(r, canvas, p)
+
+ // Draw each day
+ var cell = mFirstJulianDay
+ p.setAntiAlias(false)
+ val alpha: Int = p.getAlpha()
+ p.setAlpha(mEventsAlpha)
+ var day = 0
+ while (day < mNumDays) {
+
+ // TODO Wow, this needs cleanup. drawEvents loop through all the
+ // events on every call.
+ drawEvents(cell, day, HOUR_GAP, canvas, p)
+ // If this is today
+ if (cell == mTodayJulianDay) {
+ val lineY: Int =
+ mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+ mCellHeight / 60 + 1
+
+ // And the current time shows up somewhere on the screen
+ if (lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
+ drawCurrentTimeLine(r, day, lineY, canvas, p)
+ }
+ }
+ day++
+ cell++
+ }
+ p.setAntiAlias(true)
+ p.setAlpha(alpha)
+ }
+
+ private fun drawHours(r: Rect, canvas: Canvas, p: Paint) {
+ setupHourTextPaint(p)
+ var y = HOUR_GAP + mHoursTextHeight + HOURS_TOP_MARGIN
+ for (i in 0..23) {
+ val time = mHourStrs!![i]
+ canvas.drawText(time, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+ y += mCellHeight + HOUR_GAP
+ }
+ }
+
+ private fun setupHourTextPaint(p: Paint) {
+ p.setColor(mCalendarHourLabelColor)
+ p.setTextSize(HOURS_TEXT_SIZE)
+ p.setTypeface(Typeface.DEFAULT)
+ p.setTextAlign(Paint.Align.RIGHT)
+ p.setAntiAlias(true)
+ }
+
+ private fun drawDayHeader(dayStr: String?, day: Int, cell: Int, canvas: Canvas, p: Paint) {
+ var dateNum = mFirstVisibleDate + day
+ var x: Int
+ if (dateNum > mMonthLength) {
+ dateNum -= mMonthLength
+ }
+ p.setAntiAlias(true)
+ val todayIndex = mTodayJulianDay - mFirstJulianDay
+ // Draw day of the month
+ val dateNumStr: String = dateNum.toString()
+ if (mNumDays > 1) {
+ val y = (DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN).toFloat()
+
+ // Draw day of the month
+ x = computeDayLeftPosition(day + 1) - DAY_HEADER_RIGHT_MARGIN
+ p.setTextAlign(Align.RIGHT)
+ p.setTextSize(DATE_HEADER_FONT_SIZE)
+ p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
+ canvas.drawText(dateNumStr as String, x.toFloat(), y, p)
+
+ // Draw day of the week
+ x -= (p.measureText(" $dateNumStr")).toInt()
+ p.setTextSize(DAY_HEADER_FONT_SIZE)
+ p.setTypeface(Typeface.DEFAULT)
+ canvas.drawText(dayStr as String, x.toFloat(), y, p)
+ } else {
+ val y = (ONE_DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN).toFloat()
+ p.setTextAlign(Align.LEFT)
+
+ // Draw day of the week
+ x = computeDayLeftPosition(day) + DAY_HEADER_ONE_DAY_LEFT_MARGIN
+ p.setTextSize(DAY_HEADER_FONT_SIZE)
+ p.setTypeface(Typeface.DEFAULT)
+ canvas.drawText(dayStr as String, x.toFloat(), y, p)
+
+ // Draw day of the month
+ x += (p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN).toInt()
+ p.setTextSize(DATE_HEADER_FONT_SIZE)
+ p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
+ canvas.drawText(dateNumStr, x.toFloat(), y, p)
+ }
+ }
+
+ private fun drawGridBackground(r: Rect, canvas: Canvas, p: Paint) {
+ val savedStyle: Style = p.getStyle()
+ val stopX = computeDayLeftPosition(mNumDays).toFloat()
+ var y = 0f
+ val deltaY = (mCellHeight + HOUR_GAP).toFloat()
+ var linesIndex = 0
+ val startY = 0f
+ val stopY = (HOUR_GAP + 24 * (mCellHeight + HOUR_GAP)).toFloat()
+ var x = mHoursWidth.toFloat()
+
+ // Draw the inner horizontal grid lines
+ p.setColor(mCalendarGridLineInnerHorizontalColor)
+ p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+ p.setAntiAlias(false)
+ y = 0f
+ linesIndex = 0
+ for (hour in 0..24) {
+ mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN
+ mLines[linesIndex++] = y
+ mLines[linesIndex++] = stopX
+ mLines[linesIndex++] = y
+ y += deltaY
+ }
+ if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
+ canvas.drawLines(mLines, 0, linesIndex, p)
+ linesIndex = 0
+ p.setColor(mCalendarGridLineInnerVerticalColor)
+ }
+
+ // Draw the inner vertical grid lines
+ for (day in 0..mNumDays) {
+ x = computeDayLeftPosition(day).toFloat()
+ mLines[linesIndex++] = x
+ mLines[linesIndex++] = startY
+ mLines[linesIndex++] = x
+ mLines[linesIndex++] = stopY
+ }
+ canvas.drawLines(mLines, 0, linesIndex, p)
+
+ // Restore the saved style.
+ p.setStyle(savedStyle)
+ p.setAntiAlias(true)
+ }
+
+ /**
+ * @param r
+ * @param canvas
+ * @param p
+ */
+ private fun drawBgColors(r: Rect, canvas: Canvas, p: Paint) {
+ val todayIndex = mTodayJulianDay - mFirstJulianDay
+ // Draw the hours background color
+ r.top = mDestRect.top
+ r.bottom = mDestRect.bottom
+ r.left = 0
+ r.right = mHoursWidth
+ p.setColor(mBgColor)
+ p.setStyle(Style.FILL)
+ p.setAntiAlias(false)
+ canvas.drawRect(r, p)
+
+ // Draw background for grid area
+ if (mNumDays == 1 && todayIndex == 0) {
+ // Draw a white background for the time later than current time
+ var lineY: Int =
+ mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+ mCellHeight / 60 + 1
+ if (lineY < mViewStartY + mViewHeight) {
+ lineY = Math.max(lineY, mViewStartY)
+ r.left = mHoursWidth
+ r.right = mViewWidth
+ r.top = lineY
+ r.bottom = mViewStartY + mViewHeight
+ p.setColor(mFutureBgColor)
+ canvas.drawRect(r, p)
+ }
+ } else if (todayIndex >= 0 && todayIndex < mNumDays) {
+ // Draw today with a white background for the time later than current time
+ var lineY: Int =
+ mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+ mCellHeight / 60 + 1
+ if (lineY < mViewStartY + mViewHeight) {
+ lineY = Math.max(lineY, mViewStartY)
+ r.left = computeDayLeftPosition(todayIndex) + 1
+ r.right = computeDayLeftPosition(todayIndex + 1)
+ r.top = lineY
+ r.bottom = mViewStartY + mViewHeight
+ p.setColor(mFutureBgColor)
+ canvas.drawRect(r, p)
+ }
+
+ // Paint Tomorrow and later days with future color
+ if (todayIndex + 1 < mNumDays) {
+ r.left = computeDayLeftPosition(todayIndex + 1) + 1
+ r.right = computeDayLeftPosition(mNumDays)
+ r.top = mDestRect.top
+ r.bottom = mDestRect.bottom
+ p.setColor(mFutureBgColor)
+ canvas.drawRect(r, p)
+ }
+ } else if (todayIndex < 0) {
+ // Future
+ r.left = computeDayLeftPosition(0) + 1
+ r.right = computeDayLeftPosition(mNumDays)
+ r.top = mDestRect.top
+ r.bottom = mDestRect.bottom
+ p.setColor(mFutureBgColor)
+ canvas.drawRect(r, p)
+ }
+ p.setAntiAlias(true)
+ }
+
+ private fun computeMaxStringWidth(currentMax: Int, strings: Array<String?>, p: Paint): Int {
+ var maxWidthF = 0.0f
+ val len = strings.size
+ for (i in 0 until len) {
+ val width: Float = p.measureText(strings[i])
+ maxWidthF = Math.max(width, maxWidthF)
+ }
+ var maxWidth = (maxWidthF + 0.5).toInt()
+ if (maxWidth < currentMax) {
+ maxWidth = currentMax
+ }
+ return maxWidth
+ }
+
+ private fun saveSelectionPosition(left: Float, top: Float, right: Float, bottom: Float) {
+ mPrevBox.left = left.toInt()
+ mPrevBox.right = right.toInt()
+ mPrevBox.top = top.toInt()
+ mPrevBox.bottom = bottom.toInt()
+ }
+
+ private fun setupTextRect(r: Rect) {
+ if (r.bottom <= r.top || r.right <= r.left) {
+ r.bottom = r.top
+ r.right = r.left
+ return
+ }
+ if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
+ r.top += EVENT_TEXT_TOP_MARGIN
+ r.bottom -= EVENT_TEXT_BOTTOM_MARGIN
+ }
+ if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
+ r.left += EVENT_TEXT_LEFT_MARGIN
+ r.right -= EVENT_TEXT_RIGHT_MARGIN
+ }
+ }
+
+ private fun setupAllDayTextRect(r: Rect) {
+ if (r.bottom <= r.top || r.right <= r.left) {
+ r.bottom = r.top
+ r.right = r.left
+ return
+ }
+ if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
+ r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN
+ r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN
+ }
+ if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
+ r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+ r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
+ }
+ }
+
+ /**
+ * Return the layout for a numbered event. Create it if not already existing
+ */
+ private fun getEventLayout(
+ layouts: Array<StaticLayout?>?,
+ i: Int,
+ event: Event,
+ paint: Paint,
+ r: Rect
+ ): StaticLayout? {
+ if (i < 0 || i >= layouts!!.size) {
+ return null
+ }
+ var layout: StaticLayout? = layouts!![i]
+ // Check if we have already initialized the StaticLayout and that
+ // the width hasn't changed (due to vertical resizing which causes
+ // re-layout of events at min height)
+ if (layout == null || r.width() !== layout.getWidth()) {
+ val bob = SpannableStringBuilder()
+ if (event.title != null) {
+ // MAX - 1 since we add a space
+ bob.append(drawTextSanitizer(event.title.toString(),
+ MAX_EVENT_TEXT_LEN - 1))
+ bob.setSpan(StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length, 0)
+ bob.append(' ')
+ }
+ if (event.location != null) {
+ bob.append(
+ drawTextSanitizer(
+ event.location.toString(),
+ MAX_EVENT_TEXT_LEN - bob.length
+ )
+ )
+ }
+ when (event.selfAttendeeStatus) {
+ Attendees.ATTENDEE_STATUS_INVITED -> paint.setColor(event.color)
+ Attendees.ATTENDEE_STATUS_DECLINED -> {
+ paint.setColor(mEventTextColor)
+ paint.setAlpha(Utils.DECLINED_EVENT_TEXT_ALPHA)
+ }
+ Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+ Attendees.ATTENDEE_STATUS_TENTATIVE -> paint.setColor(
+ mEventTextColor
+ )
+ else -> paint.setColor(mEventTextColor)
+ }
+
+ // Leave a one pixel boundary on the left and right of the rectangle for the event
+ layout = StaticLayout(
+ bob, 0, bob.length, TextPaint(paint), r.width(),
+ Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width()
+ )
+ layouts[i] = layout
+ }
+ layout.getPaint().setAlpha(mEventsAlpha)
+ return layout
+ }
+
+ private fun drawAllDayEvents(firstDay: Int, numDays: Int, canvas: Canvas, p: Paint) {
+ p.setTextSize(NORMAL_FONT_SIZE)
+ p.setTextAlign(Paint.Align.LEFT)
+ val eventTextPaint: Paint = mEventTextPaint
+ val startY = DAY_HEADER_HEIGHT.toFloat()
+ val stopY = startY + mAlldayHeight + ALLDAY_TOP_MARGIN
+ var x = 0f
+ var linesIndex = 0
+
+ // Draw the inner vertical grid lines
+ p.setColor(mCalendarGridLineInnerVerticalColor)
+ x = mHoursWidth.toFloat()
+ p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+ // Line bounding the top of the all day area
+ mLines!![linesIndex++] = GRID_LINE_LEFT_MARGIN
+ mLines!![linesIndex++] = startY
+ mLines!![linesIndex++] = computeDayLeftPosition(mNumDays).toFloat()
+ mLines!![linesIndex++] = startY
+ for (day in 0..mNumDays) {
+ x = computeDayLeftPosition(day).toFloat()
+ mLines!![linesIndex++] = x
+ mLines!![linesIndex++] = startY
+ mLines!![linesIndex++] = x
+ mLines!![linesIndex++] = stopY
+ }
+ p.setAntiAlias(false)
+ canvas.drawLines(mLines, 0, linesIndex, p)
+ p.setStyle(Style.FILL)
+ val y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+ val lastDay = firstDay + numDays - 1
+ val events: ArrayList<Event>? = mAllDayEvents
+ val numEvents: Int = events!!.size
+ // Whether or not we should draw the more events text
+ var hasMoreEvents = false
+ // size of the allDay area
+ val drawHeight = mAlldayHeight.toFloat()
+ // max number of events being drawn in one day of the allday area
+ var numRectangles = mMaxAlldayEvents.toFloat()
+ // Where to cut off drawn allday events
+ var allDayEventClip = DAY_HEADER_HEIGHT + mAlldayHeight + ALLDAY_TOP_MARGIN
+ // The number of events that weren't drawn in each day
+ mSkippedAlldayEvents = IntArray(numDays)
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount &&
+ !mShowAllAllDayEvents && mAnimateDayHeight == 0) {
+ // We draw one fewer event than will fit so that more events text
+ // can be drawn
+ numRectangles = (mMaxUnexpandedAlldayEventCount - 1).toFloat()
+ // We also clip the events above the more events text
+ allDayEventClip -= MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+ hasMoreEvents = true
+ } else if (mAnimateDayHeight != 0) {
+ // clip at the end of the animating space
+ allDayEventClip = DAY_HEADER_HEIGHT + mAnimateDayHeight + ALLDAY_TOP_MARGIN
+ }
+ var alpha: Int = eventTextPaint.getAlpha()
+ eventTextPaint.setAlpha(mEventsAlpha)
+ for (i in 0 until numEvents) {
+ val event: Event = events!!.get(i)
+ var startDay: Int = event.startDay
+ var endDay: Int = event.endDay
+ if (startDay > lastDay || endDay < firstDay) {
+ continue
+ }
+ if (startDay < firstDay) {
+ startDay = firstDay
+ }
+ if (endDay > lastDay) {
+ endDay = lastDay
+ }
+ val startIndex = startDay - firstDay
+ val endIndex = endDay - firstDay
+ var height =
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount)
+ mAnimateDayEventHeight.toFloat() else drawHeight / numRectangles
+
+ // Prevent a single event from getting too big
+ if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
+ height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
+ }
+
+ // Leave a one-pixel space between the vertical day lines and the
+ // event rectangle.
+ event.left = computeDayLeftPosition(startIndex).toFloat()
+ event.right = computeDayLeftPosition(endIndex + 1).toFloat() - DAY_GAP
+ event.top = y + height * event.getColumn()
+ event.bottom = event.top + height - ALL_DAY_EVENT_RECT_BOTTOM_MARGIN
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+ // check if we should skip this event. We skip if it starts
+ // after the clip bound or ends after the skip bound and we're
+ // not animating.
+ if (event.top >= allDayEventClip) {
+ incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
+ continue
+ } else if (event.bottom > allDayEventClip) {
+ if (hasMoreEvents) {
+ incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
+ continue
+ }
+ event.bottom = allDayEventClip.toFloat()
+ }
+ }
+ val r: Rect = drawEventRect(
+ event, canvas, p, eventTextPaint, event.top.toInt(),
+ event.bottom.toInt()
+ )
+ setupAllDayTextRect(r)
+ val layout: StaticLayout? = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r)
+ drawEventText(layout, r, canvas, r.top, r.bottom, true)
+
+ // Check if this all-day event intersects the selected day
+ if (mSelectionAllday && mComputeSelectedEvents) {
+ if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
+ mSelectedEvents.add(event)
+ }
+ }
+ }
+ eventTextPaint.setAlpha(alpha)
+ if (mMoreAlldayEventsTextAlpha != 0 && mSkippedAlldayEvents != null) {
+ // If the more allday text should be visible, draw it.
+ alpha = p.getAlpha()
+ p.setAlpha(mEventsAlpha)
+ p.setColor(mMoreAlldayEventsTextAlpha shl 24 and mMoreEventsTextColor)
+ for (i in mSkippedAlldayEvents!!.indices) {
+ if (mSkippedAlldayEvents!![i] > 0) {
+ drawMoreAlldayEvents(canvas, mSkippedAlldayEvents!![i], i, p)
+ }
+ }
+ p.setAlpha(alpha)
+ }
+ if (mSelectionAllday) {
+ // Compute the neighbors for the list of all-day events that
+ // intersect the selected day.
+ computeAllDayNeighbors()
+
+ // Set the selection position to zero so that when we move down
+ // to the normal event area, we will highlight the topmost event.
+ saveSelectionPosition(0f, 0f, 0f, 0f)
+ }
+ }
+
+ // Helper method for counting the number of allday events skipped on each day
+ private fun incrementSkipCount(counts: IntArray?, startIndex: Int, endIndex: Int) {
+ if (counts == null || startIndex < 0 || endIndex > counts.size) {
+ return
+ }
+ for (i in startIndex..endIndex) {
+ counts[i]++
+ }
+ }
+
+ // Draws the "box +n" text for hidden allday events
+ protected fun drawMoreAlldayEvents(canvas: Canvas, remainingEvents: Int, day: Int, p: Paint) {
+ var x = computeDayLeftPosition(day) + EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+ var y = (mAlldayHeight - .5f * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - (.5f *
+ EVENT_SQUARE_WIDTH) + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN).toInt()
+ val r: Rect = mRect
+ r.top = y
+ r.left = x
+ r.bottom = y + EVENT_SQUARE_WIDTH
+ r.right = x + EVENT_SQUARE_WIDTH
+ p.setColor(mMoreEventsTextColor)
+ p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
+ p.setStyle(Style.STROKE)
+ p.setAntiAlias(false)
+ canvas.drawRect(r, p)
+ p.setAntiAlias(true)
+ p.setStyle(Style.FILL)
+ p.setTextSize(EVENT_TEXT_FONT_SIZE)
+ val text: String =
+ mResources.getQuantityString(R.plurals.month_more_events, remainingEvents)
+ y += EVENT_SQUARE_WIDTH
+ x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING
+ canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(), p)
+ }
+
+ private fun computeAllDayNeighbors() {
+ val len: Int = mSelectedEvents.size
+ if (len == 0 || mSelectedEvent != null) {
+ return
+ }
+
+ // First, clear all the links
+ for (ii in 0 until len) {
+ val ev: Event = mSelectedEvents.get(ii)
+ ev.nextUp = null
+ ev.nextDown = null
+ ev.nextLeft = null
+ ev.nextRight = null
+ }
+
+ // For each event in the selected event list "mSelectedEvents", find
+ // its neighbors in the up and down directions. This could be done
+ // more efficiently by sorting on the Event.getColumn() field, but
+ // the list is expected to be very small.
+
+ // Find the event in the same row as the previously selected all-day
+ // event, if any.
+ var startPosition = -1
+ if (mPrevSelectedEvent != null && mPrevSelectedEvent!!.drawAsAllday()) {
+ startPosition = mPrevSelectedEvent?.getColumn() as Int
+ }
+ var maxPosition = -1
+ var startEvent: Event? = null
+ var maxPositionEvent: Event? = null
+ for (ii in 0 until len) {
+ val ev: Event = mSelectedEvents.get(ii)
+ val position: Int = ev.getColumn()
+ if (position == startPosition) {
+ startEvent = ev
+ } else if (position > maxPosition) {
+ maxPositionEvent = ev
+ maxPosition = position
+ }
+ for (jj in 0 until len) {
+ if (jj == ii) {
+ continue
+ }
+ val neighbor: Event = mSelectedEvents.get(jj)
+ val neighborPosition: Int = neighbor.getColumn()
+ if (neighborPosition == position - 1) {
+ ev.nextUp = neighbor
+ } else if (neighborPosition == position + 1) {
+ ev.nextDown = neighbor
+ }
+ }
+ }
+ if (startEvent != null) {
+ setSelectedEvent(startEvent)
+ } else {
+ setSelectedEvent(maxPositionEvent)
+ }
+ }
+
+ private fun drawEvents(date: Int, dayIndex: Int, top: Int, canvas: Canvas, p: Paint) {
+ val eventTextPaint: Paint = mEventTextPaint
+ val left = computeDayLeftPosition(dayIndex) + 1
+ val cellWidth = computeDayLeftPosition(dayIndex + 1) - left + 1
+ val cellHeight = mCellHeight
+
+ // Use the selected hour as the selection region
+ val selectionArea: Rect = mSelectionRect
+ selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP)
+ selectionArea.bottom = selectionArea.top + cellHeight
+ selectionArea.left = left
+ selectionArea.right = selectionArea.left + cellWidth
+ val events: ArrayList<Event> = mEvents
+ val numEvents: Int = events.size
+ val geometry: EventGeometry = mEventGeometry
+ val viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight
+ val alpha: Int = eventTextPaint.getAlpha()
+ eventTextPaint.setAlpha(mEventsAlpha)
+ for (i in 0 until numEvents) {
+ val event: Event = events.get(i)
+ if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+ continue
+ }
+
+ // Don't draw it if it is not visible
+ if (event.bottom < mViewStartY || event.top > viewEndY) {
+ continue
+ }
+ if (date == mSelectionDay && !mSelectionAllday && mComputeSelectedEvents &&
+ geometry.eventIntersectsSelection(event, selectionArea)
+ ) {
+ mSelectedEvents.add(event)
+ }
+ val r: Rect = drawEventRect(event, canvas, p, eventTextPaint, mViewStartY, viewEndY)
+ setupTextRect(r)
+
+ // Don't draw text if it is not visible
+ if (r.top > viewEndY || r.bottom < mViewStartY) {
+ continue
+ }
+ val layout: StaticLayout? = getEventLayout(mLayouts, i, event, eventTextPaint, r)
+ // TODO: not sure why we are 4 pixels off
+ drawEventText(
+ layout,
+ r,
+ canvas,
+ mViewStartY + 4,
+ mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight,
+ false
+ )
+ }
+ eventTextPaint.setAlpha(alpha)
+ }
+
+ private fun drawEventRect(
+ event: Event,
+ canvas: Canvas,
+ p: Paint,
+ eventTextPaint: Paint,
+ visibleTop: Int,
+ visibleBot: Int
+ ): Rect {
+ // Draw the Event Rect
+ val r: Rect = mRect
+ r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN, visibleTop)
+ r.bottom = Math.min(event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN, visibleBot)
+ r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
+ r.right = event.right.toInt()
+ var color: Int = event.color
+ when (event.selfAttendeeStatus) {
+ Attendees.ATTENDEE_STATUS_INVITED -> if (event !== mClickedEvent) {
+ p.setStyle(Style.STROKE)
+ }
+ Attendees.ATTENDEE_STATUS_DECLINED -> {
+ if (event !== mClickedEvent) {
+ color = Utils.getDeclinedColorFromColor(color)
+ }
+ p.setStyle(Style.FILL_AND_STROKE)
+ }
+ Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+ Attendees.ATTENDEE_STATUS_TENTATIVE -> p.setStyle(
+ Style.FILL_AND_STROKE
+ )
+ else -> p.setStyle(Style.FILL_AND_STROKE)
+ }
+ p.setAntiAlias(false)
+ val floorHalfStroke = Math.floor(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
+ val ceilHalfStroke = Math.ceil(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
+ r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN + floorHalfStroke, visibleTop)
+ r.bottom = Math.min(
+ event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN - ceilHalfStroke,
+ visibleBot
+ )
+ r.left += floorHalfStroke
+ r.right -= ceilHalfStroke
+ p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
+ p.setColor(color)
+ val alpha: Int = p.getAlpha()
+ p.setAlpha(mEventsAlpha)
+ canvas.drawRect(r, p)
+ p.setAlpha(alpha)
+ p.setStyle(Style.FILL)
+
+ // Setup rect for drawEventText which follows
+ r.top = event.top.toInt() + EVENT_RECT_TOP_MARGIN
+ r.bottom = event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN
+ r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
+ r.right = event.right.toInt() - EVENT_RECT_RIGHT_MARGIN
+ return r
+ }
+
+ private val drawTextSanitizerFilter: Pattern = Pattern.compile("[\t\n],")
+
+ // Sanitize a string before passing it to drawText or else we get little
+ // squares. For newlines and tabs before a comma, delete the character.
+ // Otherwise, just replace them with a space.
+ private fun drawTextSanitizer(string: String, maxEventTextLen: Int): String {
+ var string = string
+ val m: Matcher = drawTextSanitizerFilter.matcher(string)
+ string = m.replaceAll(",")
+ var len: Int = string.length
+ if (maxEventTextLen <= 0) {
+ string = ""
+ len = 0
+ } else if (len > maxEventTextLen) {
+ string = string.substring(0, maxEventTextLen)
+ len = maxEventTextLen
+ }
+ return string.replace('\n', ' ')
+ }
+
+ private fun drawEventText(
+ eventLayout: StaticLayout?,
+ rect: Rect,
+ canvas: Canvas,
+ top: Int,
+ bottom: Int,
+ center: Boolean
+ ) {
+ // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
+ val width: Int = rect.right - rect.left
+ val height: Int = rect.bottom - rect.top
+
+ // If the rectangle is too small for text, then return
+ if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
+ return
+ }
+ var totalLineHeight = 0
+ val lineCount: Int = eventLayout.getLineCount()
+ for (i in 0 until lineCount) {
+ val lineBottom: Int = eventLayout.getLineBottom(i)
+ totalLineHeight = if (lineBottom <= height) {
+ lineBottom
+ } else {
+ break
+ }
+ }
+
+ // + 2 is small workaround when the font is slightly bigger than the rect. This will
+ // still allow the text to be shown without overflowing into the other all day rects.
+ if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight + 2 < top) {
+ return
+ }
+
+ // Use a StaticLayout to format the string.
+ canvas.save()
+ // canvas.translate(rect.left, rect.top + (rect.bottom - rect.top / 2));
+ val padding = if (center) (rect.bottom - rect.top - totalLineHeight) / 2 else 0
+ canvas.translate(rect.left.toFloat(), rect.top.toFloat() + padding)
+ rect.left = 0
+ rect.right = width
+ rect.top = 0
+ rect.bottom = totalLineHeight
+
+ // There's a bug somewhere. If this rect is outside of a previous
+ // cliprect, this becomes a no-op. What happens is that the text draw
+ // past the event rect. The current fix is to not draw the staticLayout
+ // at all if it is completely out of bound.
+ canvas.clipRect(rect)
+ eventLayout.draw(canvas)
+ canvas.restore()
+ }
+
+ // The following routines are called from the parent activity when certain
+ // touch events occur.
+ private fun doDown(ev: MotionEvent) {
+ mTouchMode = TOUCH_MODE_DOWN
+ mViewStartX = 0
+ mOnFlingCalled = false
+ mHandler?.removeCallbacks(mContinueScroll)
+ val x = ev.getX().toInt()
+ val y = ev.getY().toInt()
+
+ // Save selection information: we use setSelectionFromPosition to find the selected event
+ // in order to show the "clicked" color. But since it is also setting the selected info
+ // for new events, we need to restore the old info after calling the function.
+ val oldSelectedEvent: Event? = mSelectedEvent
+ val oldSelectionDay = mSelectionDay
+ val oldSelectionHour = mSelectionHour
+ if (setSelectionFromPosition(x, y, false)) {
+ // If a time was selected (a blue selection box is visible) and the click location
+ // is in the selected time, do not show a click on an event to prevent a situation
+ // of both a selection and an event are clicked when they overlap.
+ val pressedSelected = (mSelectionMode != SELECTION_HIDDEN &&
+ oldSelectionDay == mSelectionDay && oldSelectionHour == mSelectionHour)
+ if (!pressedSelected && mSelectedEvent != null) {
+ mSavedClickedEvent = mSelectedEvent
+ mDownTouchTime = System.currentTimeMillis()
+ postDelayed(mSetClick, mOnDownDelay.toLong())
+ } else {
+ eventClickCleanup()
+ }
+ }
+ mSelectedEvent = oldSelectedEvent
+ mSelectionDay = oldSelectionDay
+ mSelectionHour = oldSelectionHour
+ invalidate()
+ }
+
+ // Kicks off all the animations when the expand allday area is tapped
+ private fun doExpandAllDayClick() {
+ mShowAllAllDayEvents = !mShowAllAllDayEvents
+ ObjectAnimator.setFrameDelay(0)
+
+ // Determine the starting height
+ if (mAnimateDayHeight == 0) {
+ mAnimateDayHeight =
+ if (mShowAllAllDayEvents) mAlldayHeight - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+ else mAlldayHeight
+ }
+ // Cancel current animations
+ mCancellingAnimations = true
+ if (mAlldayAnimator != null) {
+ mAlldayAnimator?.cancel()
+ }
+ if (mAlldayEventAnimator != null) {
+ mAlldayEventAnimator?.cancel()
+ }
+ if (mMoreAlldayEventsAnimator != null) {
+ mMoreAlldayEventsAnimator?.cancel()
+ }
+ mCancellingAnimations = false
+ // get new animators
+ mAlldayAnimator = allDayAnimator
+ mAlldayEventAnimator = allDayEventAnimator
+ mMoreAlldayEventsAnimator = ObjectAnimator.ofInt(
+ this,
+ "moreAllDayEventsTextAlpha",
+ if (mShowAllAllDayEvents) MORE_EVENTS_MAX_ALPHA else 0,
+ if (mShowAllAllDayEvents) 0 else MORE_EVENTS_MAX_ALPHA
+ )
+
+ // Set up delays and start the animators
+ mAlldayAnimator?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION
+ else 0)
+ mAlldayAnimator?.start()
+ mMoreAlldayEventsAnimator?.setStartDelay(if (mShowAllAllDayEvents) 0
+ else ANIMATION_DURATION)
+ mMoreAlldayEventsAnimator?.setDuration(ANIMATION_SECONDARY_DURATION)
+ mMoreAlldayEventsAnimator?.start()
+ if (mAlldayEventAnimator != null) {
+ // This is the only animator that can return null, so check it
+ mAlldayEventAnimator
+ ?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION else 0)
+ mAlldayEventAnimator?.start()
+ }
+ }
+
+ /**
+ * Figures out the initial heights for allDay events and space when
+ * a view is being set up.
+ */
+ fun initAllDayHeights() {
+ if (mMaxAlldayEvents <= mMaxUnexpandedAlldayEventCount) {
+ return
+ }
+ if (mShowAllAllDayEvents) {
+ var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+ maxADHeight = Math.min(
+ maxADHeight,
+ (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+ )
+ mAnimateDayEventHeight = maxADHeight / mMaxAlldayEvents
+ } else {
+ mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+ }
+ } // First calculate the absolute max height
+ // Now expand to fit but not beyond the absolute max
+ // calculate the height of individual events in order to fit
+ // if there's nothing to animate just return
+
+ // Set up the animator with the calculated values
+ // Sets up an animator for changing the height of allday events
+ private val allDayEventAnimator: ObjectAnimator?
+ private get() {
+ // First calculate the absolute max height
+ var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+ // Now expand to fit but not beyond the absolute max
+ maxADHeight = Math.min(
+ maxADHeight,
+ (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+ )
+ // calculate the height of individual events in order to fit
+ val fitHeight = maxADHeight / mMaxAlldayEvents
+ val currentHeight = mAnimateDayEventHeight
+ val desiredHeight =
+ if (mShowAllAllDayEvents) fitHeight else MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+ // if there's nothing to animate just return
+ if (currentHeight == desiredHeight) {
+ return null
+ }
+
+ // Set up the animator with the calculated values
+ val animator: ObjectAnimator = ObjectAnimator.ofInt(
+ this, "animateDayEventHeight",
+ currentHeight, desiredHeight
+ )
+ animator.setDuration(ANIMATION_DURATION)
+ return animator
+ }
+
+ // Set up the animator with the calculated values
+ // Sets up an animator for changing the height of the allday area
+ private val allDayAnimator: ObjectAnimator
+ private get() {
+ // Calculate the absolute max height
+ var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+ // Find the desired height but don't exceed abs max
+ maxADHeight = Math.min(
+ maxADHeight,
+ (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+ )
+ // calculate the current and desired heights
+ val currentHeight = if (mAnimateDayHeight != 0) mAnimateDayHeight else mAlldayHeight
+ val desiredHeight =
+ if (mShowAllAllDayEvents) maxADHeight else (MAX_UNEXPANDED_ALLDAY_HEIGHT -
+ MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - 1).toInt()
+
+ // Set up the animator with the calculated values
+ val animator: ObjectAnimator = ObjectAnimator.ofInt(
+ this, "animateDayHeight",
+ currentHeight, desiredHeight
+ )
+ animator.setDuration(ANIMATION_DURATION)
+ animator.addListener(object : AnimatorListenerAdapter() {
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ if (!mCancellingAnimations) {
+ // when finished, set this to 0 to signify not animating
+ mAnimateDayHeight = 0
+ mUseExpandIcon = !mShowAllAllDayEvents
+ }
+ mRemeasure = true
+ invalidate()
+ }
+ })
+ return animator
+ }
+
+ // setter for the 'box +n' alpha text used by the animator
+ fun setMoreAllDayEventsTextAlpha(alpha: Int) {
+ mMoreAlldayEventsTextAlpha = alpha
+ invalidate()
+ }
+
+ // setter for the height of the allday area used by the animator
+ fun setAnimateDayHeight(height: Int) {
+ mAnimateDayHeight = height
+ mRemeasure = true
+ invalidate()
+ }
+
+ // setter for the height of allday events used by the animator
+ fun setAnimateDayEventHeight(height: Int) {
+ mAnimateDayEventHeight = height
+ mRemeasure = true
+ invalidate()
+ }
+
+ private fun doSingleTapUp(ev: MotionEvent) {
+ if (!mHandleActionUp || mScrolling) {
+ return
+ }
+ val x = ev.getX().toInt()
+ val y = ev.getY().toInt()
+ val selectedDay = mSelectionDay
+ val selectedHour = mSelectionHour
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+ // check if the tap was in the allday expansion area
+ val bottom = mFirstCell
+ if (x < mHoursWidth && y > DAY_HEADER_HEIGHT && y < DAY_HEADER_HEIGHT + mAlldayHeight ||
+ !mShowAllAllDayEvents && mAnimateDayHeight == 0 && y < bottom && y >= bottom -
+ MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT
+ ) {
+ doExpandAllDayClick()
+ return
+ }
+ }
+ val validPosition = setSelectionFromPosition(x, y, false)
+ if (!validPosition) {
+ if (y < DAY_HEADER_HEIGHT) {
+ val selectedTime = Time(mBaseDate)
+ selectedTime.setJulianDay(mSelectionDay)
+ selectedTime.hour = mSelectionHour
+ selectedTime.normalize(true /* ignore isDst */)
+ mController.sendEvent(
+ this as? Object, EventType.GO_TO, null, null, selectedTime, -1,
+ ViewType.DAY, CalendarController.EXTRA_GOTO_DATE, null, null
+ )
+ }
+ return
+ }
+ val hasSelection = mSelectionMode != SELECTION_HIDDEN
+ val pressedSelected = ((hasSelection || mTouchExplorationEnabled) &&
+ selectedDay == mSelectionDay && selectedHour == mSelectionHour)
+ if (mSelectedEvent != null) {
+ // If the tap is on an event, launch the "View event" view
+ if (mIsAccessibilityEnabled) {
+ mAccessibilityMgr?.interrupt()
+ }
+ mSelectionMode = SELECTION_HIDDEN
+ var yLocation = ((mSelectedEvent!!.top + mSelectedEvent!!.bottom) / 2) as Int
+ // Y location is affected by the position of the event in the scrolling
+ // view (mViewStartY) and the presence of all day events (mFirstCell)
+ if (!mSelectedEvent!!.allDay) {
+ yLocation += mFirstCell - mViewStartY
+ }
+ mClickedYLocation = yLocation
+ val clearDelay: Long = CLICK_DISPLAY_DURATION + mOnDownDelay -
+ (System.currentTimeMillis() - mDownTouchTime)
+ if (clearDelay > 0) {
+ this.postDelayed(mClearClick, clearDelay)
+ } else {
+ this.post(mClearClick)
+ }
+ }
+ invalidate()
+ }
+
+ private fun doLongPress(ev: MotionEvent) {
+ eventClickCleanup()
+ if (mScrolling) {
+ return
+ }
+
+ // Scale gesture in progress
+ if (mStartingSpanY != 0f) {
+ return
+ }
+ val x = ev.getX().toInt()
+ val y = ev.getY().toInt()
+ val validPosition = setSelectionFromPosition(x, y, false)
+ if (!validPosition) {
+ // return if the touch wasn't on an area of concern
+ return
+ }
+ invalidate()
+ performLongClick()
+ }
+
+ private fun doScroll(e1: MotionEvent, e2: MotionEvent, deltaX: Float, deltaY: Float) {
+ cancelAnimation()
+ if (mStartingScroll) {
+ mInitialScrollX = 0f
+ mInitialScrollY = 0f
+ mStartingScroll = false
+ }
+ mInitialScrollX += deltaX
+ mInitialScrollY += deltaY
+ val distanceX = mInitialScrollX.toInt()
+ val distanceY = mInitialScrollY.toInt()
+ val focusY = getAverageY(e2)
+ if (mRecalCenterHour) {
+ // Calculate the hour that correspond to the average of the Y touch points
+ mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
+ (mCellHeight + DAY_GAP))
+ mRecalCenterHour = false
+ }
+
+ // If we haven't figured out the predominant scroll direction yet,
+ // then do it now.
+ if (mTouchMode == TOUCH_MODE_DOWN) {
+ val absDistanceX: Int = Math.abs(distanceX)
+ val absDistanceY: Int = Math.abs(distanceY)
+ mScrollStartY = mViewStartY
+ mPreviousDirection = 0
+ if (absDistanceX > absDistanceY) {
+ val slopFactor = if (mScaleGestureDetector.isInProgress()) 20 else 2
+ if (absDistanceX > mScaledPagingTouchSlop * slopFactor) {
+ mTouchMode = TOUCH_MODE_HSCROLL
+ mViewStartX = distanceX
+ initNextView(-mViewStartX)
+ }
+ } else {
+ mTouchMode = TOUCH_MODE_VSCROLL
+ }
+ } else if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+ // We are already scrolling horizontally, so check if we
+ // changed the direction of scrolling so that the other week
+ // is now visible.
+ mViewStartX = distanceX
+ if (distanceX != 0) {
+ val direction = if (distanceX > 0) 1 else -1
+ if (direction != mPreviousDirection) {
+ // The user has switched the direction of scrolling
+ // so re-init the next view
+ initNextView(-mViewStartX)
+ mPreviousDirection = direction
+ }
+ }
+ }
+ if (mTouchMode and TOUCH_MODE_VSCROLL != 0) {
+ // Calculate the top of the visible region in the calendar grid.
+ // Increasing/decrease this will scroll the calendar grid up/down.
+ mViewStartY = ((mGestureCenterHour * (mCellHeight + DAY_GAP) -
+ focusY) + DAY_HEADER_HEIGHT + mAlldayHeight).toInt()
+
+ // If dragging while already at the end, do a glow
+ val pulledToY = (mScrollStartY + deltaY).toInt()
+ if (pulledToY < 0) {
+ mEdgeEffectTop.onPull(deltaY / mViewHeight)
+ if (!mEdgeEffectBottom.isFinished()) {
+ mEdgeEffectBottom.onRelease()
+ }
+ } else if (pulledToY > mMaxViewStartY) {
+ mEdgeEffectBottom.onPull(deltaY / mViewHeight)
+ if (!mEdgeEffectTop.isFinished()) {
+ mEdgeEffectTop.onRelease()
+ }
+ }
+ if (mViewStartY < 0) {
+ mViewStartY = 0
+ mRecalCenterHour = true
+ } else if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY
+ mRecalCenterHour = true
+ }
+ if (mRecalCenterHour) {
+ // Calculate the hour that correspond to the average of the Y touch points
+ mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
+ (mCellHeight + DAY_GAP))
+ mRecalCenterHour = false
+ }
+ computeFirstHour()
+ }
+ mScrolling = true
+ mSelectionMode = SELECTION_HIDDEN
+ invalidate()
+ }
+
+ private fun getAverageY(me: MotionEvent): Float {
+ val count: Int = me.getPointerCount()
+ var focusY = 0f
+ for (i in 0 until count) {
+ focusY += me.getY(i)
+ }
+ focusY /= count.toFloat()
+ return focusY
+ }
+
+ private fun cancelAnimation() {
+ val `in`: Animation? = mViewSwitcher?.getInAnimation()
+ if (`in` != null) {
+ // cancel() doesn't terminate cleanly.
+ `in`?.scaleCurrentDuration(0f)
+ }
+ val out: Animation? = mViewSwitcher?.getOutAnimation()
+ if (out != null) {
+ // cancel() doesn't terminate cleanly.
+ out?.scaleCurrentDuration(0f)
+ }
+ }
+
+ private fun doFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float) {
+ cancelAnimation()
+ mSelectionMode = SELECTION_HIDDEN
+ eventClickCleanup()
+ mOnFlingCalled = true
+ if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+ // Horizontal fling.
+ // initNextView(deltaX);
+ mTouchMode = TOUCH_MODE_INITIAL_STATE
+ if (DEBUG) Log.d(TAG, "doFling: velocityX $velocityX")
+ val deltaX = e2.getX().toInt() - e1.getX().toInt()
+ switchViews(deltaX < 0, mViewStartX.toFloat(), mViewWidth.toFloat(), velocityX)
+ mViewStartX = 0
+ return
+ }
+ if (mTouchMode and TOUCH_MODE_VSCROLL == 0) {
+ if (DEBUG) Log.d(TAG, "doFling: no fling")
+ return
+ }
+
+ // Vertical fling.
+ mTouchMode = TOUCH_MODE_INITIAL_STATE
+ mViewStartX = 0
+ if (DEBUG) {
+ Log.d(TAG, "doFling: mViewStartY$mViewStartY velocityY $velocityY")
+ }
+
+ // Continue scrolling vertically
+ mScrolling = true
+ mScroller.fling(
+ 0 /* startX */, mViewStartY /* startY */, 0 /* velocityX */,
+ (-velocityY).toInt(), 0 /* minX */, 0 /* maxX */, 0 /* minY */,
+ mMaxViewStartY /* maxY */, OVERFLING_DISTANCE, OVERFLING_DISTANCE
+ )
+
+ // When flinging down, show a glow when it hits the end only if it
+ // wasn't started at the top
+ if (velocityY > 0 && mViewStartY != 0) {
+ mCallEdgeEffectOnAbsorb = true
+ } else if (velocityY < 0 && mViewStartY != mMaxViewStartY) {
+ mCallEdgeEffectOnAbsorb = true
+ }
+ mHandler?.post(mContinueScroll)
+ }
+
+ private fun initNextView(deltaX: Int): Boolean {
+ // Change the view to the previous day or week
+ val view = mViewSwitcher.getNextView() as DayView
+ val date: Time? = view.mBaseDate
+ date?.set(mBaseDate)
+ val switchForward: Boolean
+ if (deltaX > 0) {
+ date!!.monthDay -= mNumDays
+ view.setSelectedDay(mSelectionDay - mNumDays)
+ switchForward = false
+ } else {
+ date!!.monthDay += mNumDays
+ view.setSelectedDay(mSelectionDay + mNumDays)
+ switchForward = true
+ }
+ date?.normalize(true /* ignore isDst */)
+ initView(view)
+ view.layout(getLeft(), getTop(), getRight(), getBottom())
+ view.reloadEvents()
+ return switchForward
+ }
+
+ // ScaleGestureDetector.OnScaleGestureListener
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ mHandleActionUp = false
+ val gestureCenterInPixels: Float = detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight
+ mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP)
+ mStartingSpanY = Math.max(MIN_Y_SPAN.toFloat(),
+ Math.abs(detector.getCurrentSpanY().toFloat()))
+ mCellHeightBeforeScaleGesture = mCellHeight
+ if (DEBUG_SCALING) {
+ val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
+ Log.d(
+ TAG, "onScaleBegin: mGestureCenterHour:" + mGestureCenterHour +
+ "\tViewStartHour: " + ViewStartHour + "\tmViewStartY:" + mViewStartY +
+ "\tmCellHeight:" + mCellHeight + " SpanY:" + detector.getCurrentSpanY()
+ )
+ }
+ return true
+ }
+
+ // ScaleGestureDetector.OnScaleGestureListener
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val spanY: Float = Math.max(MIN_Y_SPAN.toFloat(),
+ Math.abs(detector.getCurrentSpanY().toFloat()))
+ mCellHeight = (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY).toInt()
+ if (mCellHeight < mMinCellHeight) {
+ // If mStartingSpanY is too small, even a small increase in the
+ // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
+ mStartingSpanY = spanY
+ mCellHeight = mMinCellHeight
+ mCellHeightBeforeScaleGesture = mMinCellHeight
+ } else if (mCellHeight > MAX_CELL_HEIGHT) {
+ mStartingSpanY = spanY
+ mCellHeight = MAX_CELL_HEIGHT
+ mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT
+ }
+ val gestureCenterInPixels = detector.getFocusY().toInt() - DAY_HEADER_HEIGHT - mAlldayHeight
+ mViewStartY = (mGestureCenterHour * (mCellHeight + DAY_GAP)).toInt() - gestureCenterInPixels
+ mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
+ if (DEBUG_SCALING) {
+ val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
+ Log.d(
+ TAG, "onScale: mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: " +
+ ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:" +
+ mCellHeight + " SpanY:" + detector.getCurrentSpanY()
+ )
+ }
+ if (mViewStartY < 0) {
+ mViewStartY = 0
+ mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
+ (mCellHeight + DAY_GAP).toFloat())
+ } else if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY
+ mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
+ (mCellHeight + DAY_GAP).toFloat())
+ }
+ computeFirstHour()
+ mRemeasure = true
+ invalidate()
+ return true
+ }
+
+ // ScaleGestureDetector.OnScaleGestureListener
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ mScrollStartY = mViewStartY
+ mInitialScrollY = 0f
+ mInitialScrollX = 0f
+ mStartingSpanY = 0f
+ }
+
+ @Override
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ val action: Int = ev.getAction()
+ if (DEBUG) Log.e(TAG, "" + action + " ev.getPointerCount() = " + ev.getPointerCount())
+ if (ev.getActionMasked() === MotionEvent.ACTION_DOWN ||
+ ev.getActionMasked() === MotionEvent.ACTION_UP ||
+ ev.getActionMasked() === MotionEvent.ACTION_POINTER_UP ||
+ ev.getActionMasked() === MotionEvent.ACTION_POINTER_DOWN
+ ) {
+ mRecalCenterHour = true
+ }
+ if (mTouchMode and TOUCH_MODE_HSCROLL == 0) {
+ mScaleGestureDetector.onTouchEvent(ev)
+ }
+ return when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ mStartingScroll = true
+ if (DEBUG) {
+ Log.e(
+ TAG,
+ "ACTION_DOWN ev.getDownTime = " + ev.getDownTime().toString() + " Cnt=" +
+ ev.getPointerCount()
+ )
+ }
+ val bottom =
+ mAlldayHeight + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+ mTouchStartedInAlldayArea = if (ev.getY() < bottom) {
+ true
+ } else {
+ false
+ }
+ mHandleActionUp = true
+ mGestureDetector.onTouchEvent(ev)
+ true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (DEBUG) Log.e(
+ TAG,
+ "ACTION_MOVE Cnt=" + ev.getPointerCount() + this@DayView
+ )
+ mGestureDetector.onTouchEvent(ev)
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (DEBUG) Log.e(
+ TAG,
+ "ACTION_UP Cnt=" + ev.getPointerCount() + mHandleActionUp
+ )
+ mEdgeEffectTop.onRelease()
+ mEdgeEffectBottom.onRelease()
+ mStartingScroll = false
+ mGestureDetector.onTouchEvent(ev)
+ if (!mHandleActionUp) {
+ mHandleActionUp = true
+ mViewStartX = 0
+ invalidate()
+ return true
+ }
+ if (mOnFlingCalled) {
+ return true
+ }
+
+ // If we were scrolling, then reset the selected hour so that it
+ // is visible.
+ if (mScrolling) {
+ mScrolling = false
+ resetSelectedHour()
+ invalidate()
+ }
+ if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+ mTouchMode = TOUCH_MODE_INITIAL_STATE
+ if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
+ // The user has gone beyond the threshold so switch views
+ if (DEBUG) Log.d(
+ TAG,
+ "- horizontal scroll: switch views"
+ )
+ switchViews(
+ mViewStartX > 0,
+ mViewStartX.toFloat(),
+ mViewWidth.toFloat(),
+ 0f
+ )
+ mViewStartX = 0
+ return true
+ } else {
+ // Not beyond the threshold so invalidate which will cause
+ // the view to snap back. Also call recalc() to ensure
+ // that we have the correct starting date and title.
+ if (DEBUG) Log.d(
+ TAG,
+ "- horizontal scroll: snap back"
+ )
+ recalc()
+ invalidate()
+ mViewStartX = 0
+ }
+ }
+ true
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ if (DEBUG) Log.e(
+ TAG,
+ "ACTION_CANCEL"
+ )
+ mGestureDetector.onTouchEvent(ev)
+ mScrolling = false
+ resetSelectedHour()
+ true
+ }
+ else -> {
+ if (DEBUG) Log.e(
+ TAG,
+ "Not MotionEvent " + ev.toString()
+ )
+ if (mGestureDetector.onTouchEvent(ev)) {
+ true
+ } else super.onTouchEvent(ev)
+ }
+ }
+ }
+
+ override fun onCreateContextMenu(menu: ContextMenu, view: View?, menuInfo: ContextMenuInfo?) {
+ var item: MenuItem
+
+ // If the trackball is held down, then the context menu pops up and
+ // we never get onKeyUp() for the long-press. So check for it here
+ // and change the selection to the long-press state.
+ if (mSelectionMode != SELECTION_LONGPRESS) {
+ invalidate()
+ }
+ val startMillis = selectedTimeInMillis
+ val flags: Int = (DateUtils.FORMAT_SHOW_TIME
+ or DateUtils.FORMAT_CAP_NOON_MIDNIGHT
+ or DateUtils.FORMAT_SHOW_WEEKDAY)
+ val title: String? = Utils.formatDateRange(mContext, startMillis, startMillis, flags)
+ menu.setHeaderTitle(title)
+ mPopup?.dismiss()
+ }
+
+ /**
+ * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
+ * If the touch position is not within the displayed grid, then this
+ * method returns false.
+ *
+ * @param x the x position of the touch
+ * @param y the y position of the touch
+ * @param keepOldSelection - do not change the selection info (used for invoking accessibility
+ * messages)
+ * @return true if the touch position is valid
+ */
+ private fun setSelectionFromPosition(x: Int, y: Int, keepOldSelection: Boolean): Boolean {
+ var x = x
+ var savedEvent: Event? = null
+ var savedDay = 0
+ var savedHour = 0
+ var savedAllDay = false
+ if (keepOldSelection) {
+ // Store selection info and restore it at the end. This way, we can invoke the
+ // right accessibility message without affecting the selection.
+ savedEvent = mSelectedEvent
+ savedDay = mSelectionDay
+ savedHour = mSelectionHour
+ savedAllDay = mSelectionAllday
+ }
+ if (x < mHoursWidth) {
+ x = mHoursWidth
+ }
+ var day = (x - mHoursWidth) / (mCellWidth + DAY_GAP)
+ if (day >= mNumDays) {
+ day = mNumDays - 1
+ }
+ day += mFirstJulianDay
+ setSelectedDay(day)
+ if (y < DAY_HEADER_HEIGHT) {
+ sendAccessibilityEventAsNeeded(false)
+ return false
+ }
+ setSelectedHour(mFirstHour) /* First fully visible hour */
+ mSelectionAllday = if (y < mFirstCell) {
+ true
+ } else {
+ // y is now offset from top of the scrollable region
+ val adjustedY = y - mFirstCell
+ if (adjustedY < mFirstHourOffset) {
+ setSelectedHour(mSelectionHour - 1) /* In the partially visible hour */
+ } else {
+ setSelectedHour(
+ mSelectionHour +
+ (adjustedY - mFirstHourOffset) / (mCellHeight + HOUR_GAP)
+ )
+ }
+ false
+ }
+ findSelectedEvent(x, y)
+ sendAccessibilityEventAsNeeded(true)
+
+ // Restore old values
+ if (keepOldSelection) {
+ mSelectedEvent = savedEvent
+ mSelectionDay = savedDay
+ mSelectionHour = savedHour
+ mSelectionAllday = savedAllDay
+ }
+ return true
+ }
+
+ private fun findSelectedEvent(x: Int, y: Int) {
+ var y = y
+ val date = mSelectionDay
+ val cellWidth = mCellWidth
+ var events: ArrayList<Event>? = mEvents
+ var numEvents: Int = events!!.size
+ val left = computeDayLeftPosition(mSelectionDay - mFirstJulianDay)
+ val top = 0
+ setSelectedEvent(null)
+ mSelectedEvents.clear()
+ if (mSelectionAllday) {
+ var yDistance: Float
+ var minYdistance = 10000.0f // any large number
+ var closestEvent: Event? = null
+ val drawHeight = mAlldayHeight.toFloat()
+ val yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+ var maxUnexpandedColumn = mMaxUnexpandedAlldayEventCount
+ if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+ // Leave a gap for the 'box +n' text
+ maxUnexpandedColumn--
+ }
+ events = mAllDayEvents
+ numEvents = events!!.size
+ for (i in 0 until numEvents) {
+ val event: Event? = events?.get(i)
+ if (!event!!.drawAsAllday() ||
+ !mShowAllAllDayEvents && event!!.getColumn() >= maxUnexpandedColumn
+ ) {
+ // Don't check non-allday events or events that aren't shown
+ continue
+ }
+ if (event!!.startDay <= mSelectionDay && event!!.endDay >= mSelectionDay) {
+ val numRectangles =
+ if (mShowAllAllDayEvents) mMaxAlldayEvents.toFloat()
+ else mMaxUnexpandedAlldayEventCount.toFloat()
+ var height = drawHeight / numRectangles
+ if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
+ height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
+ }
+ val eventTop: Float = yOffset + height * event?.getColumn()
+ val eventBottom = eventTop + height
+ if (eventTop < y && eventBottom > y) {
+ // If the touch is inside the event rectangle, then
+ // add the event.
+ mSelectedEvents.add(event)
+ closestEvent = event
+ break
+ } else {
+ // Find the closest event
+ yDistance = if (eventTop >= y) {
+ eventTop - y
+ } else {
+ y - eventBottom
+ }
+ if (yDistance < minYdistance) {
+ minYdistance = yDistance
+ closestEvent = event
+ }
+ }
+ }
+ }
+ setSelectedEvent(closestEvent)
+ return
+ }
+
+ // Adjust y for the scrollable bitmap
+ y += mViewStartY - mFirstCell
+
+ // Use a region around (x,y) for the selection region
+ val region: Rect = mRect
+ region.left = x - 10
+ region.right = x + 10
+ region.top = y - 10
+ region.bottom = y + 10
+ val geometry: EventGeometry = mEventGeometry
+ for (i in 0 until numEvents) {
+ val event: Event? = events?.get(i)
+ // Compute the event rectangle.
+ if (!geometry.computeEventRect(date, left, top, cellWidth, event as Event)) {
+ continue
+ }
+
+ // If the event intersects the selection region, then add it to
+ // mSelectedEvents.
+ if (geometry.eventIntersectsSelection(event as Event, region)) {
+ mSelectedEvents.add(event as Event)
+ }
+ }
+
+ // If there are any events in the selected region, then assign the
+ // closest one to mSelectedEvent.
+ if (mSelectedEvents.size > 0) {
+ val len: Int = mSelectedEvents.size
+ var closestEvent: Event? = null
+ var minDist = (mViewWidth + mViewHeight).toFloat() // some large distance
+ for (index in 0 until len) {
+ val ev: Event? = mSelectedEvents?.get(index)
+ val dist: Float = geometry.pointToEvent(x.toFloat(), y.toFloat(), ev as Event)
+ if (dist < minDist) {
+ minDist = dist
+ closestEvent = ev
+ }
+ }
+ setSelectedEvent(closestEvent)
+
+ // Keep the selected hour and day consistent with the selected
+ // event. They could be different if we touched on an empty hour
+ // slot very close to an event in the previous hour slot. In
+ // that case we will select the nearby event.
+ val startDay: Int = mSelectedEvent!!.startDay
+ val endDay: Int = mSelectedEvent!!.endDay
+ if (mSelectionDay < startDay) {
+ setSelectedDay(startDay)
+ } else if (mSelectionDay > endDay) {
+ setSelectedDay(endDay)
+ }
+ val startHour: Int = mSelectedEvent!!.startTime / 60
+ val endHour: Int
+ endHour = if (mSelectedEvent!!.startTime < mSelectedEvent!!.endTime) {
+ (mSelectedEvent!!.endTime - 1) / 60
+ } else {
+ mSelectedEvent!!.endTime / 60
+ }
+ if (mSelectionHour < startHour && mSelectionDay == startDay) {
+ setSelectedHour(startHour)
+ } else if (mSelectionHour > endHour && mSelectionDay == endDay) {
+ setSelectedHour(endHour)
+ }
+ }
+ }
+
+ // Encapsulates the code to continue the scrolling after the
+ // finger is lifted. Instead of stopping the scroll immediately,
+ // the scroll continues to "free spin" and gradually slows down.
+ private inner class ContinueScroll : Runnable {
+ override fun run() {
+ mScrolling = mScrolling && mScroller.computeScrollOffset()
+ if (!mScrolling || mPaused) {
+ resetSelectedHour()
+ invalidate()
+ return
+ }
+ mViewStartY = mScroller.getCurrY()
+ if (mCallEdgeEffectOnAbsorb) {
+ if (mViewStartY < 0) {
+ mEdgeEffectTop.onAbsorb(mLastVelocity.toInt())
+ mCallEdgeEffectOnAbsorb = false
+ } else if (mViewStartY > mMaxViewStartY) {
+ mEdgeEffectBottom.onAbsorb(mLastVelocity.toInt())
+ mCallEdgeEffectOnAbsorb = false
+ }
+ mLastVelocity = mScroller.getCurrVelocity()
+ }
+ if (mScrollStartY == 0 || mScrollStartY == mMaxViewStartY) {
+ // Allow overscroll/springback only on a fling,
+ // not a pull/fling from the end
+ if (mViewStartY < 0) {
+ mViewStartY = 0
+ } else if (mViewStartY > mMaxViewStartY) {
+ mViewStartY = mMaxViewStartY
+ }
+ }
+ computeFirstHour()
+ mHandler?.post(this)
+ invalidate()
+ }
+ }
+
+ /**
+ * Cleanup the pop-up and timers.
+ */
+ fun cleanup() {
+ // Protect against null-pointer exceptions
+ if (mPopup != null) {
+ mPopup?.dismiss()
+ }
+ mPaused = true
+ mLastPopupEventID = INVALID_EVENT_ID
+ if (mHandler != null) {
+ mHandler?.removeCallbacks(mDismissPopup)
+ mHandler?.removeCallbacks(mUpdateCurrentTime)
+ }
+ Utils.setSharedPreference(
+ mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
+ mCellHeight
+ )
+ // Clear all click animations
+ eventClickCleanup()
+ // Turn off redraw
+ mRemeasure = false
+ // Turn off scrolling to make sure the view is in the correct state if we fling back to it
+ mScrolling = false
+ }
+
+ private fun eventClickCleanup() {
+ this.removeCallbacks(mClearClick)
+ this.removeCallbacks(mSetClick)
+ mClickedEvent = null
+ mSavedClickedEvent = null
+ }
+
+ private fun setSelectedEvent(e: Event?) {
+ mSelectedEvent = e
+ mSelectedEventForAccessibility = e
+ }
+
+ private fun setSelectedHour(h: Int) {
+ mSelectionHour = h
+ mSelectionHourForAccessibility = h
+ }
+
+ private fun setSelectedDay(d: Int) {
+ mSelectionDay = d
+ mSelectionDayForAccessibility = d
+ }
+
+ /**
+ * Restart the update timer
+ */
+ fun restartCurrentTimeUpdates() {
+ mPaused = false
+ if (mHandler != null) {
+ mHandler?.removeCallbacks(mUpdateCurrentTime)
+ mHandler?.post(mUpdateCurrentTime)
+ }
+ }
+
+ @Override
+ protected override fun onDetachedFromWindow() {
+ cleanup()
+ super.onDetachedFromWindow()
+ }
+
+ internal inner class DismissPopup : Runnable {
+ override fun run() {
+ // Protect against null-pointer exceptions
+ if (mPopup != null) {
+ mPopup?.dismiss()
+ }
+ }
+ }
+
+ internal inner class UpdateCurrentTime : Runnable {
+ override fun run() {
+ val currentTime: Long = System.currentTimeMillis()
+ mCurrentTime?.set(currentTime)
+ // % causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
+ if (!mPaused) {
+ mHandler?.postDelayed(
+ mUpdateCurrentTime, UPDATE_CURRENT_TIME_DELAY -
+ currentTime % UPDATE_CURRENT_TIME_DELAY
+ )
+ }
+ mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
+ invalidate()
+ }
+ }
+
+ internal inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+ @Override
+ override fun onSingleTapUp(ev: MotionEvent): Boolean {
+ if (DEBUG) Log.e(TAG, "GestureDetector.onSingleTapUp")
+ doSingleTapUp(ev)
+ return true
+ }
+
+ @Override
+ override fun onLongPress(ev: MotionEvent) {
+ if (DEBUG) Log.e(TAG, "GestureDetector.onLongPress")
+ doLongPress(ev)
+ }
+
+ @Override
+ override fun onScroll(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ var distanceY = distanceY
+ if (DEBUG) Log.e(TAG, "GestureDetector.onScroll")
+ eventClickCleanup()
+ if (mTouchStartedInAlldayArea) {
+ if (Math.abs(distanceX) < Math.abs(distanceY)) {
+ // Make sure that click feedback is gone when you scroll from the
+ // all day area
+ invalidate()
+ return false
+ }
+ // don't scroll vertically if this started in the allday area
+ distanceY = 0f
+ }
+ doScroll(e1, e2, distanceX, distanceY)
+ return true
+ }
+
+ @Override
+ override fun onFling(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ var velocityY = velocityY
+ if (DEBUG) Log.e(TAG, "GestureDetector.onFling")
+ if (mTouchStartedInAlldayArea) {
+ if (Math.abs(velocityX) < Math.abs(velocityY)) {
+ return false
+ }
+ // don't fling vertically if this started in the allday area
+ velocityY = 0f
+ }
+ doFling(e1, e2, velocityX, velocityY)
+ return true
+ }
+
+ @Override
+ override fun onDown(ev: MotionEvent): Boolean {
+ if (DEBUG) Log.e(TAG, "GestureDetector.onDown")
+ doDown(ev)
+ return true
+ }
+ }
+
+ @Override
+ override fun onLongClick(v: View?): Boolean {
+ return true
+ }
+
+ private inner class ScrollInterpolator : Interpolator {
+ override fun getInterpolation(t: Float): Float {
+ var t = t
+ t -= 1.0f
+ t = t * t * t * t * t + 1
+ if ((1 - t) * mAnimationDistance < 1) {
+ cancelAnimation()
+ }
+ return t
+ }
+ }
+
+ private fun calculateDuration(delta: Float, width: Float, velocity: Float): Long {
+ /*
+ * Here we compute a "distance" that will be used in the computation of
+ * the overall snap duration. This is a function of the actual distance
+ * that needs to be traveled; we keep this value close to half screen
+ * size in order to reduce the variance in snap duration as a function
+ * of the distance the page needs to travel.
+ */
+ var velocity = velocity
+ val halfScreenSize = width / 2
+ val distanceRatio = delta / width
+ val distanceInfluenceForSnapDuration = distanceInfluenceForSnapDuration(distanceRatio)
+ val distance = halfScreenSize + halfScreenSize * distanceInfluenceForSnapDuration
+ velocity = Math.abs(velocity)
+ velocity = Math.max(MINIMUM_SNAP_VELOCITY.toFloat(), velocity)
+
+ /*
+ * we want the page's snap velocity to approximately match the velocity
+ * at which the user flings, so we scale the duration by a value near to
+ * the derivative of the scroll interpolator at zero, ie. 5. We use 6 to
+ * make it a little slower.
+ */
+ val duration: Long = 6L * Math.round(1000 * Math.abs(distance / velocity))
+ if (DEBUG) {
+ Log.e(
+ TAG, "halfScreenSize:" + halfScreenSize + " delta:" + delta + " distanceRatio:" +
+ distanceRatio + " distance:" + distance + " velocity:" + velocity +
+ " duration:" + duration + " distanceInfluenceForSnapDuration:" +
+ distanceInfluenceForSnapDuration
+ )
+ }
+ return duration
+ }
+
+ /*
+ * We want the duration of the page snap animation to be influenced by the
+ * distance that the screen has to travel, however, we don't want this
+ * duration to be effected in a purely linear fashion. Instead, we use this
+ * method to moderate the effect that the distance of travel has on the
+ * overall snap duration.
+ */
+ private fun distanceInfluenceForSnapDuration(f: Float): Float {
+ var f = f
+ f -= 0.5f // center the values about 0.
+ f *= (0.3f * Math.PI / 2.0f).toFloat()
+ return Math.sin(f.toDouble()).toFloat()
+ }
+
+ companion object {
+ private const val TAG = "DayView"
+ private const val DEBUG = false
+ private const val DEBUG_SCALING = false
+ private const val PERIOD_SPACE = ". "
+ private var mScale = 0f // Used for supporting different screen densities
+ private const val INVALID_EVENT_ID: Long = -1 // This is used for remembering a null event
+
+ // Duration of the allday expansion
+ private const val ANIMATION_DURATION: Long = 400
+
+ // duration of the more allday event text fade
+ private const val ANIMATION_SECONDARY_DURATION: Long = 200
+
+ // duration of the scroll to go to a specified time
+ private const val GOTO_SCROLL_DURATION = 200
+
+ // duration for events' cross-fade animation
+ private const val EVENTS_CROSS_FADE_DURATION = 400
+
+ // duration to show the event clicked
+ private const val CLICK_DISPLAY_DURATION = 50
+ private const val MENU_DAY = 3
+ private const val MENU_EVENT_VIEW = 5
+ private const val MENU_EVENT_CREATE = 6
+ private const val MENU_EVENT_EDIT = 7
+ private const val MENU_EVENT_DELETE = 8
+ private var DEFAULT_CELL_HEIGHT = 64
+ private var MAX_CELL_HEIGHT = 150
+ private var MIN_Y_SPAN = 100
+ private val CALENDARS_PROJECTION = arrayOf<String>(
+ Calendars._ID, // 0
+ Calendars.CALENDAR_ACCESS_LEVEL, // 1
+ Calendars.OWNER_ACCOUNT
+ )
+ private const val CALENDARS_INDEX_ACCESS_LEVEL = 1
+ private const val CALENDARS_INDEX_OWNER_ACCOUNT = 2
+ private val CALENDARS_WHERE: String = Calendars._ID.toString() + "=%d"
+ private const val FROM_NONE = 0
+ private const val FROM_ABOVE = 1
+ private const val FROM_BELOW = 2
+ private const val FROM_LEFT = 4
+ private const val FROM_RIGHT = 8
+ private const val ACCESS_LEVEL_NONE = 0
+ private const val ACCESS_LEVEL_DELETE = 1
+ private const val ACCESS_LEVEL_EDIT = 2
+ private var mHorizontalSnapBackThreshold = 128
+
+ // Update the current time line every five minutes if the window is left open that long
+ private const val UPDATE_CURRENT_TIME_DELAY = 300000
+ private var mOnDownDelay = 0
+ protected var mStringBuilder: StringBuilder = StringBuilder(50)
+
+ // TODO recreate formatter when locale changes
+ protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault())
+
+ // The number of milliseconds to show the popup window
+ private const val POPUP_DISMISS_DELAY = 3000
+ private var GRID_LINE_LEFT_MARGIN = 0f
+ private const val GRID_LINE_INNER_WIDTH = 1f
+ private const val DAY_GAP = 1
+ private const val HOUR_GAP = 1
+
+ // This is the standard height of an allday event with no restrictions
+ private var SINGLE_ALLDAY_HEIGHT = 34
+
+ /**
+ * This is the minimum desired height of a allday event.
+ * When unexpanded, allday events will use this height.
+ * When expanded allDay events will attempt to grow to fit all
+ * events at this height.
+ */
+ private var MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = 28.0f // in pixels
+
+ /**
+ * This is how big the unexpanded allday height is allowed to be.
+ * It will get adjusted based on screen size
+ */
+ private var MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
+
+ /**
+ * This is the minimum size reserved for displaying regular events.
+ * The expanded allDay region can't expand into this.
+ */
+ private const val MIN_HOURS_HEIGHT = 180
+ private var ALLDAY_TOP_MARGIN = 1
+
+ // The largest a single allDay event will become.
+ private var MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34
+ private var HOURS_TOP_MARGIN = 2
+ private var HOURS_LEFT_MARGIN = 2
+ private var HOURS_RIGHT_MARGIN = 4
+ private var HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
+ private var NEW_EVENT_MARGIN = 4
+ private var NEW_EVENT_WIDTH = 2
+ private var NEW_EVENT_MAX_LENGTH = 16
+ private var CURRENT_TIME_LINE_SIDE_BUFFER = 4
+ private var CURRENT_TIME_LINE_TOP_OFFSET = 2
+
+ /* package */
+ const val MINUTES_PER_HOUR = 60
+
+ /* package */
+ const val MINUTES_PER_DAY = MINUTES_PER_HOUR * 24
+
+ /* package */
+ const val MILLIS_PER_MINUTE = 60 * 1000
+
+ /* package */
+ const val MILLIS_PER_HOUR = 3600 * 1000
+
+ /* package */
+ const val MILLIS_PER_DAY = MILLIS_PER_HOUR * 24
+
+ // More events text will transition between invisible and this alpha
+ private const val MORE_EVENTS_MAX_ALPHA = 0x4C
+ private var DAY_HEADER_ONE_DAY_LEFT_MARGIN = 0
+ private var DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5
+ private var DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6
+ private var DAY_HEADER_RIGHT_MARGIN = 4
+ private var DAY_HEADER_BOTTOM_MARGIN = 3
+ private var DAY_HEADER_FONT_SIZE = 14f
+ private var DATE_HEADER_FONT_SIZE = 32f
+ private var NORMAL_FONT_SIZE = 12f
+ private var EVENT_TEXT_FONT_SIZE = 12f
+ private var HOURS_TEXT_SIZE = 12f
+ private var AMPM_TEXT_SIZE = 9f
+ private var MIN_HOURS_WIDTH = 96
+ private var MIN_CELL_WIDTH_FOR_TEXT = 20
+ private const val MAX_EVENT_TEXT_LEN = 500
+
+ // smallest height to draw an event with
+ private var MIN_EVENT_HEIGHT = 24.0f // in pixels
+ private var CALENDAR_COLOR_SQUARE_SIZE = 10
+ private var EVENT_RECT_TOP_MARGIN = 1
+ private var EVENT_RECT_BOTTOM_MARGIN = 0
+ private var EVENT_RECT_LEFT_MARGIN = 1
+ private var EVENT_RECT_RIGHT_MARGIN = 0
+ private var EVENT_RECT_STROKE_WIDTH = 2
+ private var EVENT_TEXT_TOP_MARGIN = 2
+ private var EVENT_TEXT_BOTTOM_MARGIN = 2
+ private var EVENT_TEXT_LEFT_MARGIN = 6
+ private var EVENT_TEXT_RIGHT_MARGIN = 6
+ private var ALL_DAY_EVENT_RECT_BOTTOM_MARGIN = 1
+ private var EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
+ private var EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN
+ private var EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+ private var EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN
+
+ // margins and sizing for the expand allday icon
+ private var EXPAND_ALL_DAY_BOTTOM_MARGIN = 10
+
+ // sizing for "box +n" in allDay events
+ private var EVENT_SQUARE_WIDTH = 10
+ private var EVENT_LINE_PADDING = 4
+ private var NEW_EVENT_HINT_FONT_SIZE = 12
+ private var mEventTextColor = 0
+ private var mMoreEventsTextColor = 0
+ private var mWeek_saturdayColor = 0
+ private var mWeek_sundayColor = 0
+ private var mCalendarDateBannerTextColor = 0
+ private var mCalendarAmPmLabel = 0
+ private var mCalendarGridAreaSelected = 0
+ private var mCalendarGridLineInnerHorizontalColor = 0
+ private var mCalendarGridLineInnerVerticalColor = 0
+ private var mFutureBgColor = 0
+ private var mFutureBgColorRes = 0
+ private var mBgColor = 0
+ private var mNewEventHintColor = 0
+ private var mCalendarHourLabelColor = 0
+ private var mMoreAlldayEventsTextAlpha = MORE_EVENTS_MAX_ALPHA
+ private var mCellHeight = 0 // shared among all DayViews
+ private var mMinCellHeight = 32
+ private var mScaledPagingTouchSlop = 0
+
+ /**
+ * Whether to use the expand or collapse icon.
+ */
+ private var mUseExpandIcon = true
+
+ /**
+ * The height of the day names/numbers
+ */
+ private var DAY_HEADER_HEIGHT = 45
+
+ /**
+ * The height of the day names/numbers for multi-day views
+ */
+ private var MULTI_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
+
+ /**
+ * The height of the day names/numbers when viewing a single day
+ */
+ private var ONE_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
+
+ /**
+ * Whether or not to expand the allDay area to fill the screen
+ */
+ private var mShowAllAllDayEvents = false
+ private var sCounter = 0
+
+ /**
+ * The initial state of the touch mode when we enter this view.
+ */
+ private const val TOUCH_MODE_INITIAL_STATE = 0
+
+ /**
+ * Indicates we just received the touch event and we are waiting to see if
+ * it is a tap or a scroll gesture.
+ */
+ private const val TOUCH_MODE_DOWN = 1
+
+ /**
+ * Indicates the touch gesture is a vertical scroll
+ */
+ private const val TOUCH_MODE_VSCROLL = 0x20
+
+ /**
+ * Indicates the touch gesture is a horizontal scroll
+ */
+ private const val TOUCH_MODE_HSCROLL = 0x40
+
+ /**
+ * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+ */
+ private const val SELECTION_HIDDEN = 0
+ private const val SELECTION_PRESSED = 1 // D-pad down but not up yet
+ private const val SELECTION_SELECTED = 2
+ private const val SELECTION_LONGPRESS = 3
+
+ // The rest of this file was borrowed from Launcher2 - PagedView.java
+ private const val MINIMUM_SNAP_VELOCITY = 2200
+ }
+
+ init {
+ mContext = context
+ initAccessibilityVariables()
+ mResources = context!!.getResources()
+ mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint)
+ mNumDays = numDays
+ DATE_HEADER_FONT_SIZE =
+ mResources.getDimension(R.dimen.date_header_text_size).toInt().toFloat()
+ DAY_HEADER_FONT_SIZE =
+ mResources.getDimension(R.dimen.day_label_text_size).toInt().toFloat()
+ ONE_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.one_day_header_height).toInt()
+ DAY_HEADER_BOTTOM_MARGIN = mResources.getDimension(R.dimen.day_header_bottom_margin).toInt()
+ EXPAND_ALL_DAY_BOTTOM_MARGIN =
+ mResources.getDimension(R.dimen.all_day_bottom_margin).toInt()
+ HOURS_TEXT_SIZE = mResources.getDimension(R.dimen.hours_text_size).toInt().toFloat()
+ AMPM_TEXT_SIZE = mResources.getDimension(R.dimen.ampm_text_size).toInt().toFloat()
+ MIN_HOURS_WIDTH = mResources.getDimension(R.dimen.min_hours_width).toInt()
+ HOURS_LEFT_MARGIN = mResources.getDimension(R.dimen.hours_left_margin).toInt()
+ HOURS_RIGHT_MARGIN = mResources.getDimension(R.dimen.hours_right_margin).toInt()
+ MULTI_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.day_header_height).toInt()
+ val eventTextSizeId: Int
+ eventTextSizeId = if (mNumDays == 1) {
+ R.dimen.day_view_event_text_size
+ } else {
+ R.dimen.week_view_event_text_size
+ }
+ EVENT_TEXT_FONT_SIZE = mResources.getDimension(eventTextSizeId).toFloat()
+ NEW_EVENT_HINT_FONT_SIZE = mResources.getDimension(R.dimen.new_event_hint_text_size).toInt()
+ MIN_EVENT_HEIGHT = mResources.getDimension(R.dimen.event_min_height)
+ MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = MIN_EVENT_HEIGHT
+ EVENT_TEXT_TOP_MARGIN = mResources.getDimension(R.dimen.event_text_vertical_margin).toInt()
+ EVENT_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
+ EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
+ EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
+ EVENT_TEXT_LEFT_MARGIN = mResources
+ .getDimension(R.dimen.event_text_horizontal_margin).toInt()
+ EVENT_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+ EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+ EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+ if (mScale == 0f) {
+ mScale = mResources.getDisplayMetrics().density
+ if (mScale != 1f) {
+ SINGLE_ALLDAY_HEIGHT *= mScale.toInt()
+ ALLDAY_TOP_MARGIN *= mScale.toInt()
+ MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale.toInt()
+ NORMAL_FONT_SIZE *= mScale
+ GRID_LINE_LEFT_MARGIN *= mScale
+ HOURS_TOP_MARGIN *= mScale.toInt()
+ MIN_CELL_WIDTH_FOR_TEXT *= mScale.toInt()
+ MAX_UNEXPANDED_ALLDAY_HEIGHT *= mScale.toInt()
+ mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+ CURRENT_TIME_LINE_SIDE_BUFFER *= mScale.toInt()
+ CURRENT_TIME_LINE_TOP_OFFSET *= mScale.toInt()
+ MIN_Y_SPAN *= mScale.toInt()
+ MAX_CELL_HEIGHT *= mScale.toInt()
+ DEFAULT_CELL_HEIGHT *= mScale.toInt()
+ DAY_HEADER_HEIGHT *= mScale.toInt()
+ DAY_HEADER_RIGHT_MARGIN *= mScale.toInt()
+ DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale.toInt()
+ DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale.toInt()
+ DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale.toInt()
+ CALENDAR_COLOR_SQUARE_SIZE *= mScale.toInt()
+ EVENT_RECT_TOP_MARGIN *= mScale.toInt()
+ EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
+ ALL_DAY_EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
+ EVENT_RECT_LEFT_MARGIN *= mScale.toInt()
+ EVENT_RECT_RIGHT_MARGIN *= mScale.toInt()
+ EVENT_RECT_STROKE_WIDTH *= mScale.toInt()
+ EVENT_SQUARE_WIDTH *= mScale.toInt()
+ EVENT_LINE_PADDING *= mScale.toInt()
+ NEW_EVENT_MARGIN *= mScale.toInt()
+ NEW_EVENT_WIDTH *= mScale.toInt()
+ NEW_EVENT_MAX_LENGTH *= mScale.toInt()
+ }
+ }
+ HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
+ DAY_HEADER_HEIGHT = if (mNumDays == 1) ONE_DAY_HEADER_HEIGHT else MULTI_DAY_HEADER_HEIGHT
+ mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_indicator_holo_light)
+ mCurrentTimeAnimateLine = mResources
+ .getDrawable(R.drawable.timeline_indicator_activated_holo_light)
+ mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light)
+ mExpandAlldayDrawable = mResources.getDrawable(R.drawable.ic_expand_holo_light)
+ mCollapseAlldayDrawable = mResources.getDrawable(R.drawable.ic_collapse_holo_light)
+ mNewEventHintColor = mResources.getColor(R.color.new_event_hint_text_color)
+ mAcceptedOrTentativeEventBoxDrawable = mResources
+ .getDrawable(R.drawable.panel_month_event_holo_light)
+ mEventLoader = eventLoader as EventLoader
+ mEventGeometry = EventGeometry()
+ mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT)
+ mEventGeometry.setHourGap(HOUR_GAP.toFloat())
+ mEventGeometry.setCellMargin(DAY_GAP)
+ mLastPopupEventID = INVALID_EVENT_ID
+ mController = controller as CalendarController
+ mViewSwitcher = viewSwitcher as ViewSwitcher
+ mGestureDetector = GestureDetector(context, CalendarGestureListener())
+ mScaleGestureDetector = ScaleGestureDetector(getContext(), this)
+ if (mCellHeight == 0) {
+ mCellHeight = Utils.getSharedPreference(
+ mContext,
+ GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT
+ )
+ }
+ mScroller = OverScroller(context)
+ mHScrollInterpolator = ScrollInterpolator()
+ mEdgeEffectTop = EdgeEffect(context)
+ mEdgeEffectBottom = EdgeEffect(context)
+ val vc: ViewConfiguration = ViewConfiguration.get(context)
+ mScaledPagingTouchSlop = vc.getScaledPagingTouchSlop()
+ mOnDownDelay = ViewConfiguration.getTapTimeout()
+ OVERFLING_DISTANCE = vc.getScaledOverflingDistance()
+ init(context as Context)
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/Event.java b/src/com/android/calendar/Event.java
deleted file mode 100644
index 095e43e7..00000000
--- a/src/com/android/calendar/Event.java
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Debug;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Instances;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicInteger;
-
-// TODO: should Event be Parcelable so it can be passed via Intents?
-public class Event implements Cloneable {
-
- private static final String TAG = "CalEvent";
- private static final boolean PROFILE = false;
-
- /**
- * The sort order is:
- * 1) events with an earlier start (begin for normal events, startday for allday)
- * 2) events with a later end (end for normal events, endday for allday)
- * 3) the title (unnecessary, but nice)
- *
- * The start and end day is sorted first so that all day events are
- * sorted correctly with respect to events that are >24 hours (and
- * therefore show up in the allday area).
- */
- private static final String SORT_EVENTS_BY =
- "begin ASC, end DESC, title ASC";
- private static final String SORT_ALLDAY_BY =
- "startDay ASC, endDay DESC, title ASC";
- private static final String DISPLAY_AS_ALLDAY = "dispAllday";
-
- private static final String EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0";
- private static final String ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1";
-
- // The projection to use when querying instances to build a list of events
- public static final String[] EVENT_PROJECTION = new String[] {
- Instances.TITLE, // 0
- Instances.EVENT_LOCATION, // 1
- Instances.ALL_DAY, // 2
- Instances.DISPLAY_COLOR, // 3 If SDK < 16, set to Instances.CALENDAR_COLOR.
- Instances.EVENT_TIMEZONE, // 4
- Instances.EVENT_ID, // 5
- Instances.BEGIN, // 6
- Instances.END, // 7
- Instances._ID, // 8
- Instances.START_DAY, // 9
- Instances.END_DAY, // 10
- Instances.START_MINUTE, // 11
- Instances.END_MINUTE, // 12
- Instances.HAS_ALARM, // 13
- Instances.RRULE, // 14
- Instances.RDATE, // 15
- Instances.SELF_ATTENDEE_STATUS, // 16
- Events.ORGANIZER, // 17
- Events.GUESTS_CAN_MODIFY, // 18
- Instances.ALL_DAY + "=1 OR (" + Instances.END + "-" + Instances.BEGIN + ")>="
- + DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY, // 19
- };
-
- // The indices for the projection array above.
- private static final int PROJECTION_TITLE_INDEX = 0;
- private static final int PROJECTION_LOCATION_INDEX = 1;
- private static final int PROJECTION_ALL_DAY_INDEX = 2;
- private static final int PROJECTION_COLOR_INDEX = 3;
- private static final int PROJECTION_TIMEZONE_INDEX = 4;
- private static final int PROJECTION_EVENT_ID_INDEX = 5;
- private static final int PROJECTION_BEGIN_INDEX = 6;
- private static final int PROJECTION_END_INDEX = 7;
- private static final int PROJECTION_START_DAY_INDEX = 9;
- private static final int PROJECTION_END_DAY_INDEX = 10;
- private static final int PROJECTION_START_MINUTE_INDEX = 11;
- private static final int PROJECTION_END_MINUTE_INDEX = 12;
- private static final int PROJECTION_HAS_ALARM_INDEX = 13;
- private static final int PROJECTION_RRULE_INDEX = 14;
- private static final int PROJECTION_RDATE_INDEX = 15;
- private static final int PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16;
- private static final int PROJECTION_ORGANIZER_INDEX = 17;
- private static final int PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX = 18;
- private static final int PROJECTION_DISPLAY_AS_ALLDAY = 19;
-
- static {
- if (!Utils.isJellybeanOrLater()) {
- EVENT_PROJECTION[PROJECTION_COLOR_INDEX] = Instances.CALENDAR_COLOR;
- }
- }
-
- private static String mNoTitleString;
- private static int mNoColorColor;
-
- public long id;
- public int color;
- public CharSequence title;
- public CharSequence location;
- public boolean allDay;
- public String organizer;
- public boolean guestsCanModify;
-
- public int startDay; // start Julian day
- public int endDay; // end Julian day
- public int startTime; // Start and end time are in minutes since midnight
- public int endTime;
-
- public long startMillis; // UTC milliseconds since the epoch
- public long endMillis; // UTC milliseconds since the epoch
- private int mColumn;
- private int mMaxColumns;
-
- public boolean hasAlarm;
- public boolean isRepeating;
-
- public int selfAttendeeStatus;
-
- // The coordinates of the event rectangle drawn on the screen.
- public float left;
- public float right;
- public float top;
- public float bottom;
-
- // These 4 fields are used for navigating among events within the selected
- // hour in the Day and Week view.
- public Event nextRight;
- public Event nextLeft;
- public Event nextUp;
- public Event nextDown;
-
- @Override
- public final Object clone() throws CloneNotSupportedException {
- super.clone();
- Event e = new Event();
-
- e.title = title;
- e.color = color;
- e.location = location;
- e.allDay = allDay;
- e.startDay = startDay;
- e.endDay = endDay;
- e.startTime = startTime;
- e.endTime = endTime;
- e.startMillis = startMillis;
- e.endMillis = endMillis;
- e.hasAlarm = hasAlarm;
- e.isRepeating = isRepeating;
- e.selfAttendeeStatus = selfAttendeeStatus;
- e.organizer = organizer;
- e.guestsCanModify = guestsCanModify;
-
- return e;
- }
-
- public final void copyTo(Event dest) {
- dest.id = id;
- dest.title = title;
- dest.color = color;
- dest.location = location;
- dest.allDay = allDay;
- dest.startDay = startDay;
- dest.endDay = endDay;
- dest.startTime = startTime;
- dest.endTime = endTime;
- dest.startMillis = startMillis;
- dest.endMillis = endMillis;
- dest.hasAlarm = hasAlarm;
- dest.isRepeating = isRepeating;
- dest.selfAttendeeStatus = selfAttendeeStatus;
- dest.organizer = organizer;
- dest.guestsCanModify = guestsCanModify;
- }
-
- public static final Event newInstance() {
- Event e = new Event();
-
- e.id = 0;
- e.title = null;
- e.color = 0;
- e.location = null;
- e.allDay = false;
- e.startDay = 0;
- e.endDay = 0;
- e.startTime = 0;
- e.endTime = 0;
- e.startMillis = 0;
- e.endMillis = 0;
- e.hasAlarm = false;
- e.isRepeating = false;
- e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
-
- return e;
- }
-
- /**
- * Loads <i>days</i> days worth of instances starting at <i>startDay</i>.
- */
- public static void loadEvents(Context context, ArrayList<Event> events, int startDay, int days,
- int requestId, AtomicInteger sequenceNumber) {
-
- if (PROFILE) {
- Debug.startMethodTracing("loadEvents");
- }
-
- Cursor cEvents = null;
- Cursor cAllday = null;
-
- events.clear();
- try {
- int endDay = startDay + days - 1;
-
- // We use the byDay instances query to get a list of all events for
- // the days we're interested in.
- // The sort order is: events with an earlier start time occur
- // first and if the start times are the same, then events with
- // a later end time occur first. The later end time is ordered
- // first so that long rectangles in the calendar views appear on
- // the left side. If the start and end times of two events are
- // the same then we sort alphabetically on the title. This isn't
- // required for correctness, it just adds a nice touch.
-
- // Respect the preference to show/hide declined events
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- boolean hideDeclined = prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED,
- false);
-
- String where = EVENTS_WHERE;
- String whereAllday = ALLDAY_WHERE;
- if (hideDeclined) {
- String hideString = " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
- + Attendees.ATTENDEE_STATUS_DECLINED;
- where += hideString;
- whereAllday += hideString;
- }
-
- cEvents = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
- endDay, where, null, SORT_EVENTS_BY);
- cAllday = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
- endDay, whereAllday, null, SORT_ALLDAY_BY);
-
- // Check if we should return early because there are more recent
- // load requests waiting.
- if (requestId != sequenceNumber.get()) {
- return;
- }
-
- buildEventsFromCursor(events, cEvents, context, startDay, endDay);
- buildEventsFromCursor(events, cAllday, context, startDay, endDay);
-
- } finally {
- if (cEvents != null) {
- cEvents.close();
- }
- if (cAllday != null) {
- cAllday.close();
- }
- if (PROFILE) {
- Debug.stopMethodTracing();
- }
- }
- }
-
- /**
- * Performs a query to return all visible instances in the given range
- * that match the given selection. This is a blocking function and
- * should not be done on the UI thread. This will cause an expansion of
- * recurring events to fill this time range if they are not already
- * expanded and will slow down for larger time ranges with many
- * recurring events.
- *
- * @param cr The ContentResolver to use for the query
- * @param projection The columns to return
- * @param begin The start of the time range to query in UTC millis since
- * epoch
- * @param end The end of the time range to query in UTC millis since
- * epoch
- * @param selection Filter on the query as an SQL WHERE statement
- * @param selectionArgs Args to replace any '?'s in the selection
- * @param orderBy How to order the rows as an SQL ORDER BY statement
- * @return A Cursor of instances matching the selection
- */
- private static final Cursor instancesQuery(ContentResolver cr, String[] projection,
- int startDay, int endDay, String selection, String[] selectionArgs, String orderBy) {
- String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?";
- String[] WHERE_CALENDARS_ARGS = {"1"};
- String DEFAULT_SORT_ORDER = "begin ASC";
-
- Uri.Builder builder = Instances.CONTENT_BY_DAY_URI.buildUpon();
- ContentUris.appendId(builder, startDay);
- ContentUris.appendId(builder, endDay);
- if (TextUtils.isEmpty(selection)) {
- selection = WHERE_CALENDARS_SELECTED;
- selectionArgs = WHERE_CALENDARS_ARGS;
- } else {
- selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
- if (selectionArgs != null && selectionArgs.length > 0) {
- selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1);
- selectionArgs[selectionArgs.length - 1] = WHERE_CALENDARS_ARGS[0];
- } else {
- selectionArgs = WHERE_CALENDARS_ARGS;
- }
- }
- return cr.query(builder.build(), projection, selection, selectionArgs,
- orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * Adds all the events from the cursors to the events list.
- *
- * @param events The list of events
- * @param cEvents Events to add to the list
- * @param context
- * @param startDay
- * @param endDay
- */
- public static void buildEventsFromCursor(
- ArrayList<Event> events, Cursor cEvents, Context context, int startDay, int endDay) {
- if (cEvents == null || events == null) {
- Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!");
- return;
- }
-
- int count = cEvents.getCount();
-
- if (count == 0) {
- return;
- }
-
- Resources res = context.getResources();
- mNoTitleString = res.getString(R.string.no_title_label);
- mNoColorColor = res.getColor(R.color.event_center);
- // Sort events in two passes so we ensure the allday and standard events
- // get sorted in the correct order
- cEvents.moveToPosition(-1);
- while (cEvents.moveToNext()) {
- Event e = generateEventFromCursor(cEvents);
- if (e.startDay > endDay || e.endDay < startDay) {
- continue;
- }
- events.add(e);
- }
- }
-
- /**
- * @param cEvents Cursor pointing at event
- * @return An event created from the cursor
- */
- private static Event generateEventFromCursor(Cursor cEvents) {
- Event e = new Event();
-
- e.id = cEvents.getLong(PROJECTION_EVENT_ID_INDEX);
- e.title = cEvents.getString(PROJECTION_TITLE_INDEX);
- e.location = cEvents.getString(PROJECTION_LOCATION_INDEX);
- e.allDay = cEvents.getInt(PROJECTION_ALL_DAY_INDEX) != 0;
- e.organizer = cEvents.getString(PROJECTION_ORGANIZER_INDEX);
- e.guestsCanModify = cEvents.getInt(PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX) != 0;
-
- if (e.title == null || e.title.length() == 0) {
- e.title = mNoTitleString;
- }
-
- if (!cEvents.isNull(PROJECTION_COLOR_INDEX)) {
- // Read the color from the database
- e.color = Utils.getDisplayColorFromColor(cEvents.getInt(PROJECTION_COLOR_INDEX));
- } else {
- e.color = mNoColorColor;
- }
-
- long eStart = cEvents.getLong(PROJECTION_BEGIN_INDEX);
- long eEnd = cEvents.getLong(PROJECTION_END_INDEX);
-
- e.startMillis = eStart;
- e.startTime = cEvents.getInt(PROJECTION_START_MINUTE_INDEX);
- e.startDay = cEvents.getInt(PROJECTION_START_DAY_INDEX);
-
- e.endMillis = eEnd;
- e.endTime = cEvents.getInt(PROJECTION_END_MINUTE_INDEX);
- e.endDay = cEvents.getInt(PROJECTION_END_DAY_INDEX);
-
- e.hasAlarm = cEvents.getInt(PROJECTION_HAS_ALARM_INDEX) != 0;
-
- // Check if this is a repeating event
- String rrule = cEvents.getString(PROJECTION_RRULE_INDEX);
- String rdate = cEvents.getString(PROJECTION_RDATE_INDEX);
- if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
- e.isRepeating = true;
- } else {
- e.isRepeating = false;
- }
-
- e.selfAttendeeStatus = cEvents.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX);
- return e;
- }
-
- /**
- * Computes a position for each event. Each event is displayed
- * as a non-overlapping rectangle. For normal events, these rectangles
- * are displayed in separate columns in the week view and day view. For
- * all-day events, these rectangles are displayed in separate rows along
- * the top. In both cases, each event is assigned two numbers: N, and
- * Max, that specify that this event is the Nth event of Max number of
- * events that are displayed in a group. The width and position of each
- * rectangle depend on the maximum number of rectangles that occur at
- * the same time.
- *
- * @param eventsList the list of events, sorted into increasing time order
- * @param minimumDurationMillis minimum duration acceptable as cell height of each event
- * rectangle in millisecond. Should be 0 when it is not determined.
- */
- /* package */ static void computePositions(ArrayList<Event> eventsList,
- long minimumDurationMillis) {
- if (eventsList == null) {
- return;
- }
-
- // Compute the column positions separately for the all-day events
- doComputePositions(eventsList, minimumDurationMillis, false);
- doComputePositions(eventsList, minimumDurationMillis, true);
- }
-
- private static void doComputePositions(ArrayList<Event> eventsList,
- long minimumDurationMillis, boolean doAlldayEvents) {
- final ArrayList<Event> activeList = new ArrayList<Event>();
- final ArrayList<Event> groupList = new ArrayList<Event>();
-
- if (minimumDurationMillis < 0) {
- minimumDurationMillis = 0;
- }
-
- long colMask = 0;
- int maxCols = 0;
- for (Event event : eventsList) {
- // Process all-day events separately
- if (event.drawAsAllday() != doAlldayEvents)
- continue;
-
- if (!doAlldayEvents) {
- colMask = removeNonAlldayActiveEvents(
- event, activeList.iterator(), minimumDurationMillis, colMask);
- } else {
- colMask = removeAlldayActiveEvents(event, activeList.iterator(), colMask);
- }
-
- // If the active list is empty, then reset the max columns, clear
- // the column bit mask, and empty the groupList.
- if (activeList.isEmpty()) {
- for (Event ev : groupList) {
- ev.setMaxColumns(maxCols);
- }
- maxCols = 0;
- colMask = 0;
- groupList.clear();
- }
-
- // Find the first empty column. Empty columns are represented by
- // zero bits in the column mask "colMask".
- int col = findFirstZeroBit(colMask);
- if (col == 64)
- col = 63;
- colMask |= (1L << col);
- event.setColumn(col);
- activeList.add(event);
- groupList.add(event);
- int len = activeList.size();
- if (maxCols < len)
- maxCols = len;
- }
- for (Event ev : groupList) {
- ev.setMaxColumns(maxCols);
- }
- }
-
- private static long removeAlldayActiveEvents(Event event, Iterator<Event> iter, long colMask) {
- // Remove the inactive allday events. An event on the active list
- // becomes inactive when the end day is less than the current event's
- // start day.
- while (iter.hasNext()) {
- final Event active = iter.next();
- if (active.endDay < event.startDay) {
- colMask &= ~(1L << active.getColumn());
- iter.remove();
- }
- }
- return colMask;
- }
-
- private static long removeNonAlldayActiveEvents(
- Event event, Iterator<Event> iter, long minDurationMillis, long colMask) {
- long start = event.getStartMillis();
- // Remove the inactive events. An event on the active list
- // becomes inactive when its end time is less than or equal to
- // the current event's start time.
- while (iter.hasNext()) {
- final Event active = iter.next();
-
- final long duration = Math.max(
- active.getEndMillis() - active.getStartMillis(), minDurationMillis);
- if ((active.getStartMillis() + duration) <= start) {
- colMask &= ~(1L << active.getColumn());
- iter.remove();
- }
- }
- return colMask;
- }
-
- public static int findFirstZeroBit(long val) {
- for (int ii = 0; ii < 64; ++ii) {
- if ((val & (1L << ii)) == 0)
- return ii;
- }
- return 64;
- }
-
- public final void dump() {
- Log.e("Cal", "+-----------------------------------------+");
- Log.e("Cal", "+ id = " + id);
- Log.e("Cal", "+ color = " + color);
- Log.e("Cal", "+ title = " + title);
- Log.e("Cal", "+ location = " + location);
- Log.e("Cal", "+ allDay = " + allDay);
- Log.e("Cal", "+ startDay = " + startDay);
- Log.e("Cal", "+ endDay = " + endDay);
- Log.e("Cal", "+ startTime = " + startTime);
- Log.e("Cal", "+ endTime = " + endTime);
- Log.e("Cal", "+ organizer = " + organizer);
- Log.e("Cal", "+ guestwrt = " + guestsCanModify);
- }
-
- public final boolean intersects(int julianDay, int startMinute,
- int endMinute) {
- if (endDay < julianDay) {
- return false;
- }
-
- if (startDay > julianDay) {
- return false;
- }
-
- if (endDay == julianDay) {
- if (endTime < startMinute) {
- return false;
- }
- // An event that ends at the start minute should not be considered
- // as intersecting the given time span, but don't exclude
- // zero-length (or very short) events.
- if (endTime == startMinute
- && (startTime != endTime || startDay != endDay)) {
- return false;
- }
- }
-
- if (startDay == julianDay && startTime > endMinute) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns the event title and location separated by a comma. If the
- * location is already part of the title (at the end of the title), then
- * just the title is returned.
- *
- * @return the event title and location as a String
- */
- public String getTitleAndLocation() {
- String text = title.toString();
-
- // Append the location to the title, unless the title ends with the
- // location (for example, "meeting in building 42" ends with the
- // location).
- if (location != null) {
- String locationString = location.toString();
- if (!text.endsWith(locationString)) {
- text += ", " + locationString;
- }
- }
- return text;
- }
-
- public void setColumn(int column) {
- mColumn = column;
- }
-
- public int getColumn() {
- return mColumn;
- }
-
- public void setMaxColumns(int maxColumns) {
- mMaxColumns = maxColumns;
- }
-
- public int getMaxColumns() {
- return mMaxColumns;
- }
-
- public void setStartMillis(long startMillis) {
- this.startMillis = startMillis;
- }
-
- public long getStartMillis() {
- return startMillis;
- }
-
- public void setEndMillis(long endMillis) {
- this.endMillis = endMillis;
- }
-
- public long getEndMillis() {
- return endMillis;
- }
-
- public boolean drawAsAllday() {
- // Use >= so we'll pick up Exchange allday events
- return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS;
- }
-}
diff --git a/src/com/android/calendar/Event.kt b/src/com/android/calendar/Event.kt
new file mode 100644
index 00000000..c21a0a0e
--- /dev/null
+++ b/src/com/android/calendar/Event.kt
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.res.Resources
+import android.database.Cursor
+import android.net.Uri
+import android.os.Debug
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Events
+import android.provider.CalendarContract.Instances
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.util.Log
+
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Iterator
+import java.util.concurrent.atomic.AtomicInteger
+
+// TODO: should Event be Parcelable so it can be passed via Intents?
+class Event : Cloneable {
+ companion object {
+ private const val TAG = "CalEvent"
+ private const val PROFILE = false
+
+ /**
+ * The sort order is:
+ * 1) events with an earlier start (begin for normal events, startday for allday)
+ * 2) events with a later end (end for normal events, endday for allday)
+ * 3) the title (unnecessary, but nice)
+ *
+ * The start and end day is sorted first so that all day events are
+ * sorted correctly with respect to events that are >24 hours (and
+ * therefore show up in the allday area).
+ */
+ private const val SORT_EVENTS_BY = "begin ASC, end DESC, title ASC"
+ private const val SORT_ALLDAY_BY = "startDay ASC, endDay DESC, title ASC"
+ private const val DISPLAY_AS_ALLDAY = "dispAllday"
+ private const val EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0"
+ private const val ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1"
+
+ // The projection to use when querying instances to build a list of events
+ @JvmField
+ val EVENT_PROJECTION = arrayOf<String>(
+ Instances.TITLE, // 0
+ Instances.EVENT_LOCATION, // 1
+ Instances.ALL_DAY, // 2
+ Instances.DISPLAY_COLOR, // 3 If SDK < 16, set to Instances.CALENDAR_COLOR.
+ Instances.EVENT_TIMEZONE, // 4
+ Instances.EVENT_ID, // 5
+ Instances.BEGIN, // 6
+ Instances.END, // 7
+ Instances._ID, // 8
+ Instances.START_DAY, // 9
+ Instances.END_DAY, // 10
+ Instances.START_MINUTE, // 11
+ Instances.END_MINUTE, // 12
+ Instances.HAS_ALARM, // 13
+ Instances.RRULE, // 14
+ Instances.RDATE, // 15
+ Instances.SELF_ATTENDEE_STATUS, // 16
+ Events.ORGANIZER, // 17
+ Events.GUESTS_CAN_MODIFY, // 18
+ Instances.ALL_DAY.toString() + "=1 OR (" + Instances.END + "-" +
+ Instances.BEGIN + ")>=" +
+ DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY
+ )
+
+ // The indices for the projection array above.
+ private const val PROJECTION_TITLE_INDEX = 0
+ private const val PROJECTION_LOCATION_INDEX = 1
+ private const val PROJECTION_ALL_DAY_INDEX = 2
+ private const val PROJECTION_COLOR_INDEX = 3
+ private const val PROJECTION_TIMEZONE_INDEX = 4
+ private const val PROJECTION_EVENT_ID_INDEX = 5
+ private const val PROJECTION_BEGIN_INDEX = 6
+ private const val PROJECTION_END_INDEX = 7
+ private const val PROJECTION_START_DAY_INDEX = 9
+ private const val PROJECTION_END_DAY_INDEX = 10
+ private const val PROJECTION_START_MINUTE_INDEX = 11
+ private const val PROJECTION_END_MINUTE_INDEX = 12
+ private const val PROJECTION_HAS_ALARM_INDEX = 13
+ private const val PROJECTION_RRULE_INDEX = 14
+ private const val PROJECTION_RDATE_INDEX = 15
+ private const val PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16
+ private const val PROJECTION_ORGANIZER_INDEX = 17
+ private const val PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX = 18
+ private const val PROJECTION_DISPLAY_AS_ALLDAY = 19
+ private var mNoTitleString: String? = null
+ private var mNoColorColor = 0
+ @JvmStatic fun newInstance(): Event {
+ val e = Event()
+ e.id = 0
+ e.title = null
+ e.color = 0
+ e.location = null
+ e.allDay = false
+ e.startDay = 0
+ e.endDay = 0
+ e.startTime = 0
+ e.endTime = 0
+ e.startMillis = 0
+ e.endMillis = 0
+ e.hasAlarm = false
+ e.isRepeating = false
+ e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE
+ return e
+ }
+
+ /**
+ * Loads *days* days worth of instances starting at *startDay*.
+ */
+ @JvmStatic fun loadEvents(
+ context: Context?,
+ events: ArrayList<Event?>,
+ startDay: Int,
+ days: Int,
+ requestId: Int,
+ sequenceNumber: AtomicInteger?
+ ) {
+ if (PROFILE) {
+ Debug.startMethodTracing("loadEvents")
+ }
+ var cEvents: Cursor? = null
+ var cAllday: Cursor? = null
+ events.clear()
+ try {
+ val endDay = startDay + days - 1
+
+ // We use the byDay instances query to get a list of all events for
+ // the days we're interested in.
+ // The sort order is: events with an earlier start time occur
+ // first and if the start times are the same, then events with
+ // a later end time occur first. The later end time is ordered
+ // first so that long rectangles in the calendar views appear on
+ // the left side. If the start and end times of two events are
+ // the same then we sort alphabetically on the title. This isn't
+ // required for correctness, it just adds a nice touch.
+
+ // Respect the preference to show/hide declined events
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val hideDeclined: Boolean = prefs?.getBoolean(
+ GeneralPreferences.KEY_HIDE_DECLINED,
+ false
+ ) as Boolean
+ var where = EVENTS_WHERE
+ var whereAllday = ALLDAY_WHERE
+ if (hideDeclined) {
+ val hideString = (" AND " + Instances.SELF_ATTENDEE_STATUS.toString() + "!=" +
+ Attendees.ATTENDEE_STATUS_DECLINED)
+ where += hideString
+ whereAllday += hideString
+ }
+ cEvents = instancesQuery(
+ context?.getContentResolver(), EVENT_PROJECTION, startDay,
+ endDay, where, null, SORT_EVENTS_BY
+ )
+ cAllday = instancesQuery(
+ context?.getContentResolver(), EVENT_PROJECTION, startDay,
+ endDay, whereAllday, null, SORT_ALLDAY_BY
+ )
+
+ // Check if we should return early because there are more recent
+ // load requests waiting.
+ if (requestId != sequenceNumber?.get()) {
+ return
+ }
+ buildEventsFromCursor(events, cEvents, context, startDay, endDay)
+ buildEventsFromCursor(events, cAllday, context, startDay, endDay)
+ } finally {
+ if (cEvents != null) {
+ cEvents.close()
+ }
+ if (cAllday != null) {
+ cAllday.close()
+ }
+ if (PROFILE) {
+ Debug.stopMethodTracing()
+ }
+ }
+ }
+
+ /**
+ * Performs a query to return all visible instances in the given range
+ * that match the given selection. This is a blocking function and
+ * should not be done on the UI thread. This will cause an expansion of
+ * recurring events to fill this time range if they are not already
+ * expanded and will slow down for larger time ranges with many
+ * recurring events.
+ *
+ * @param cr The ContentResolver to use for the query
+ * @param projection The columns to return
+ * @param begin The start of the time range to query in UTC millis since
+ * epoch
+ * @param end The end of the time range to query in UTC millis since
+ * epoch
+ * @param selection Filter on the query as an SQL WHERE statement
+ * @param selectionArgs Args to replace any '?'s in the selection
+ * @param orderBy How to order the rows as an SQL ORDER BY statement
+ * @return A Cursor of instances matching the selection
+ */
+ @JvmStatic private fun instancesQuery(
+ cr: ContentResolver?,
+ projection: Array<String>,
+ startDay: Int,
+ endDay: Int,
+ selection: String,
+ selectionArgs: Array<String?>?,
+ orderBy: String?
+ ): Cursor? {
+ var selection = selection
+ var selectionArgs = selectionArgs
+ val WHERE_CALENDARS_SELECTED: String = Calendars.VISIBLE.toString() + "=?"
+ val WHERE_CALENDARS_ARGS = arrayOf<String?>("1")
+ val DEFAULT_SORT_ORDER = "begin ASC"
+ val builder: Uri.Builder = Instances.CONTENT_BY_DAY_URI.buildUpon()
+ ContentUris.appendId(builder, startDay.toLong())
+ ContentUris.appendId(builder, endDay.toLong())
+ if (TextUtils.isEmpty(selection)) {
+ selection = WHERE_CALENDARS_SELECTED
+ selectionArgs = WHERE_CALENDARS_ARGS
+ } else {
+ selection = "($selection) AND $WHERE_CALENDARS_SELECTED"
+ if (selectionArgs != null && selectionArgs.size > 0) {
+ selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.size + 1)
+ selectionArgs[selectionArgs.size - 1] = WHERE_CALENDARS_ARGS[0]
+ } else {
+ selectionArgs = WHERE_CALENDARS_ARGS
+ }
+ }
+ return cr?.query(
+ builder.build(), projection, selection, selectionArgs,
+ orderBy ?: DEFAULT_SORT_ORDER
+ )
+ }
+
+ /**
+ * Adds all the events from the cursors to the events list.
+ *
+ * @param events The list of events
+ * @param cEvents Events to add to the list
+ * @param context
+ * @param startDay
+ * @param endDay
+ */
+ @JvmStatic fun buildEventsFromCursor(
+ events: ArrayList<Event?>?,
+ cEvents: Cursor?,
+ context: Context?,
+ startDay: Int,
+ endDay: Int
+ ) {
+ if (cEvents == null || events == null) {
+ Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!")
+ return
+ }
+ val count: Int = cEvents.getCount()
+ if (count == 0) {
+ return
+ }
+ val res: Resources? = context?.getResources()
+ mNoTitleString = res?.getString(R.string.no_title_label)
+ mNoColorColor = res?.getColor(R.color.event_center) as Int
+ // Sort events in two passes so we ensure the allday and standard events
+ // get sorted in the correct order
+ cEvents.moveToPosition(-1)
+ while (cEvents.moveToNext()) {
+ val e = generateEventFromCursor(cEvents)
+ if (e.startDay > endDay || e.endDay < startDay) {
+ continue
+ }
+ events.add(e)
+ }
+ }
+
+ /**
+ * @param cEvents Cursor pointing at event
+ * @return An event created from the cursor
+ */
+ @JvmStatic private fun generateEventFromCursor(cEvents: Cursor): Event {
+ val e = Event()
+ e.id = cEvents.getLong(PROJECTION_EVENT_ID_INDEX)
+ e.title = cEvents.getString(PROJECTION_TITLE_INDEX)
+ e.location = cEvents.getString(PROJECTION_LOCATION_INDEX)
+ e.allDay = cEvents.getInt(PROJECTION_ALL_DAY_INDEX) !== 0
+ e.organizer = cEvents.getString(PROJECTION_ORGANIZER_INDEX)
+ e.guestsCanModify = cEvents.getInt(PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX) !== 0
+ if (e.title == null || e.title!!.length == 0) {
+ e.title = mNoTitleString
+ }
+ if (!cEvents.isNull(PROJECTION_COLOR_INDEX)) {
+ // Read the color from the database
+ e.color = Utils.getDisplayColorFromColor(cEvents.getInt(PROJECTION_COLOR_INDEX))
+ } else {
+ e.color = mNoColorColor
+ }
+ val eStart: Long = cEvents.getLong(PROJECTION_BEGIN_INDEX)
+ val eEnd: Long = cEvents.getLong(PROJECTION_END_INDEX)
+ e.startMillis = eStart
+ e.startTime = cEvents.getInt(PROJECTION_START_MINUTE_INDEX)
+ e.startDay = cEvents.getInt(PROJECTION_START_DAY_INDEX)
+ e.endMillis = eEnd
+ e.endTime = cEvents.getInt(PROJECTION_END_MINUTE_INDEX)
+ e.endDay = cEvents.getInt(PROJECTION_END_DAY_INDEX)
+ e.hasAlarm = cEvents.getInt(PROJECTION_HAS_ALARM_INDEX) !== 0
+
+ // Check if this is a repeating event
+ val rrule: String = cEvents.getString(PROJECTION_RRULE_INDEX)
+ val rdate: String = cEvents.getString(PROJECTION_RDATE_INDEX)
+ if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
+ e.isRepeating = true
+ } else {
+ e.isRepeating = false
+ }
+ e.selfAttendeeStatus = cEvents.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX)
+ return e
+ }
+
+ /**
+ * Computes a position for each event. Each event is displayed
+ * as a non-overlapping rectangle. For normal events, these rectangles
+ * are displayed in separate columns in the week view and day view. For
+ * all-day events, these rectangles are displayed in separate rows along
+ * the top. In both cases, each event is assigned two numbers: N, and
+ * Max, that specify that this event is the Nth event of Max number of
+ * events that are displayed in a group. The width and position of each
+ * rectangle depend on the maximum number of rectangles that occur at
+ * the same time.
+ *
+ * @param eventsList the list of events, sorted into increasing time order
+ * @param minimumDurationMillis minimum duration acceptable as cell height of each event
+ * rectangle in millisecond. Should be 0 when it is not determined.
+ */
+ /* package */
+ @JvmStatic fun computePositions(
+ eventsList: ArrayList<Event>?,
+ minimumDurationMillis: Long
+ ) {
+ if (eventsList == null) {
+ return
+ }
+
+ // Compute the column positions separately for the all-day events
+ doComputePositions(eventsList, minimumDurationMillis, false)
+ doComputePositions(eventsList, minimumDurationMillis, true)
+ }
+
+ @JvmStatic private fun doComputePositions(
+ eventsList: ArrayList<Event>,
+ minimumDurationMillis: Long,
+ doAlldayEvents: Boolean
+ ) {
+ var minimumDurationMillis = minimumDurationMillis
+ val activeList: ArrayList<Event> = ArrayList<Event>()
+ val groupList: ArrayList<Event> = ArrayList<Event>()
+ if (minimumDurationMillis < 0) {
+ minimumDurationMillis = 0
+ }
+ var colMask: Long = 0
+ var maxCols = 0
+ for (event in eventsList) {
+ // Process all-day events separately
+ if (event.drawAsAllday() != doAlldayEvents) continue
+ colMask = if (!doAlldayEvents) {
+ removeNonAlldayActiveEvents(
+ event, activeList.iterator() as Iterator<Event>,
+ minimumDurationMillis, colMask
+ )
+ } else {
+ removeAlldayActiveEvents(event, activeList.iterator()
+ as Iterator<Event>, colMask)
+ }
+
+ // If the active list is empty, then reset the max columns, clear
+ // the column bit mask, and empty the groupList.
+ if (activeList.isEmpty()) {
+ for (ev in groupList) {
+ ev.maxColumns = maxCols
+ }
+ maxCols = 0
+ colMask = 0
+ groupList.clear()
+ }
+
+ // Find the first empty column. Empty columns are represented by
+ // zero bits in the column mask "colMask".
+ var col = findFirstZeroBit(colMask)
+ if (col == 64) col = 63
+ colMask = colMask or (1L shl col)
+ event.column = col
+ activeList.add(event)
+ groupList.add(event)
+ val len: Int = activeList.size
+ if (maxCols < len) maxCols = len
+ }
+ for (ev in groupList) {
+ ev.maxColumns = maxCols
+ }
+ }
+
+ @JvmStatic private fun removeAlldayActiveEvents(
+ event: Event,
+ iter: Iterator<Event>,
+ colMask: Long
+ ): Long {
+ // Remove the inactive allday events. An event on the active list
+ // becomes inactive when the end day is less than the current event's
+ // start day.
+ var colMask = colMask
+ while (iter.hasNext()) {
+ val active = iter.next()
+ if (active.endDay < event.startDay) {
+ colMask = colMask and (1L shl active.column).inv()
+ iter.remove()
+ }
+ }
+ return colMask
+ }
+
+ @JvmStatic private fun removeNonAlldayActiveEvents(
+ event: Event,
+ iter: Iterator<Event>,
+ minDurationMillis: Long,
+ colMask: Long
+ ): Long {
+ var colMask = colMask
+ val start = event.getStartMillis()
+ // Remove the inactive events. An event on the active list
+ // becomes inactive when its end time is less than or equal to
+ // the current event's start time.
+ while (iter.hasNext()) {
+ val active = iter.next()
+ val duration: Long = Math.max(
+ active.getEndMillis() - active.getStartMillis(), minDurationMillis
+ )
+ if (active.getStartMillis() + duration <= start) {
+ colMask = colMask and (1L shl active.column).inv()
+ iter.remove()
+ }
+ }
+ return colMask
+ }
+
+ @JvmStatic fun findFirstZeroBit(`val`: Long): Int {
+ for (ii in 0..63) {
+ if (`val` and (1L shl ii) == 0L) return ii
+ }
+ return 64
+ }
+
+ init {
+ if (!Utils.isJellybeanOrLater()) {
+ EVENT_PROJECTION[PROJECTION_COLOR_INDEX] = Instances.CALENDAR_COLOR
+ }
+ }
+ }
+
+ @JvmField var id: Long = 0
+ @JvmField var color = 0
+ @JvmField var title: CharSequence? = null
+ @JvmField var location: CharSequence? = null
+ @JvmField var allDay = false
+ @JvmField var organizer: String? = null
+ @JvmField var guestsCanModify = false
+ @JvmField var startDay = 0 // start Julian day
+ @JvmField var endDay = 0 // end Julian day
+ @JvmField var startTime = 0 // Start and end time are in minutes since midnight
+ @JvmField var endTime = 0
+ @JvmField var startMillis = 0L // UTC milliseconds since the epoch
+ @JvmField var endMillis = 0L // UTC milliseconds since the epoch
+ @JvmField var column = 0
+ @JvmField var maxColumns = 0
+ @JvmField var hasAlarm = false
+ @JvmField var isRepeating = false
+ @JvmField var selfAttendeeStatus = 0
+
+ // The coordinates of the event rectangle drawn on the screen.
+ @JvmField var left = 0f
+ @JvmField var right = 0f
+ @JvmField var top = 0f
+ @JvmField var bottom = 0f
+
+ // These 4 fields are used for navigating among events within the selected
+ // hour in the Day and Week view.
+ @JvmField var nextRight: Event? = null
+ @JvmField var nextLeft: Event? = null
+ @JvmField var nextUp: Event? = null
+ @JvmField var nextDown: Event? = null
+ @Override
+ @Throws(CloneNotSupportedException::class)
+ override fun clone(): Object {
+ super.clone()
+ val e = Event()
+ e.title = title
+ e.color = color
+ e.location = location
+ e.allDay = allDay
+ e.startDay = startDay
+ e.endDay = endDay
+ e.startTime = startTime
+ e.endTime = endTime
+ e.startMillis = startMillis
+ e.endMillis = endMillis
+ e.hasAlarm = hasAlarm
+ e.isRepeating = isRepeating
+ e.selfAttendeeStatus = selfAttendeeStatus
+ e.organizer = organizer
+ e.guestsCanModify = guestsCanModify
+ return e as Object
+ }
+
+ fun copyTo(dest: Event) {
+ dest.id = id
+ dest.title = title
+ dest.color = color
+ dest.location = location
+ dest.allDay = allDay
+ dest.startDay = startDay
+ dest.endDay = endDay
+ dest.startTime = startTime
+ dest.endTime = endTime
+ dest.startMillis = startMillis
+ dest.endMillis = endMillis
+ dest.hasAlarm = hasAlarm
+ dest.isRepeating = isRepeating
+ dest.selfAttendeeStatus = selfAttendeeStatus
+ dest.organizer = organizer
+ dest.guestsCanModify = guestsCanModify
+ }
+
+ fun dump() {
+ Log.e("Cal", "+-----------------------------------------+")
+ Log.e("Cal", "+ id = $id")
+ Log.e("Cal", "+ color = $color")
+ Log.e("Cal", "+ title = $title")
+ Log.e("Cal", "+ location = $location")
+ Log.e("Cal", "+ allDay = $allDay")
+ Log.e("Cal", "+ startDay = $startDay")
+ Log.e("Cal", "+ endDay = $endDay")
+ Log.e("Cal", "+ startTime = $startTime")
+ Log.e("Cal", "+ endTime = $endTime")
+ Log.e("Cal", "+ organizer = $organizer")
+ Log.e("Cal", "+ guestwrt = $guestsCanModify")
+ }
+
+ fun intersects(
+ julianDay: Int,
+ startMinute: Int,
+ endMinute: Int
+ ): Boolean {
+ if (endDay < julianDay) {
+ return false
+ }
+ if (startDay > julianDay) {
+ return false
+ }
+ if (endDay == julianDay) {
+ if (endTime < startMinute) {
+ return false
+ }
+ // An event that ends at the start minute should not be considered
+ // as intersecting the given time span, but don't exclude
+ // zero-length (or very short) events.
+ if (endTime == startMinute &&
+ (startTime != endTime || startDay != endDay)) {
+ return false
+ }
+ }
+ return !(startDay == julianDay && startTime > endMinute)
+ }
+
+ /**
+ * Returns the event title and location separated by a comma. If the
+ * location is already part of the title (at the end of the title), then
+ * just the title is returned.
+ *
+ * @return the event title and location as a String
+ */
+ val titleAndLocation: String
+ get() {
+ var text = title.toString()
+
+ // Append the location to the title, unless the title ends with the
+ // location (for example, "meeting in building 42" ends with the
+ // location).
+ if (location != null) {
+ val locationString = location.toString()
+ if (!text.endsWith(locationString)) {
+ text += ", $locationString"
+ }
+ }
+ return text
+ }
+
+ // TODO(damianpatel): this getter will likely not be
+ // needed once DayView.java is converted
+ fun getColumn(): Int {
+ return column
+ }
+
+ fun setStartMillis(startMillis: Long) {
+ this.startMillis = startMillis
+ }
+
+ fun getStartMillis(): Long {
+ return startMillis
+ }
+
+ fun setEndMillis(endMillis: Long) {
+ this.endMillis = endMillis
+ }
+
+ fun getEndMillis(): Long {
+ return endMillis
+ }
+
+ fun drawAsAllday(): Boolean {
+ // Use >= so we'll pick up Exchange allday events
+ return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/EventGeometry.java b/src/com/android/calendar/EventGeometry.java
deleted file mode 100644
index cdecb49c..00000000
--- a/src/com/android/calendar/EventGeometry.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.graphics.Rect;
-
-public class EventGeometry {
- // This is the space from the grid line to the event rectangle.
- private int mCellMargin = 0;
-
- private float mMinuteHeight;
-
- private float mHourGap;
- private float mMinEventHeight;
-
- void setCellMargin(int cellMargin) {
- mCellMargin = cellMargin;
- }
-
- public void setHourGap(float gap) {
- mHourGap = gap;
- }
-
- public void setMinEventHeight(float height) {
- mMinEventHeight = height;
- }
-
- public void setHourHeight(float height) {
- mMinuteHeight = height / 60.0f;
- }
-
- // Computes the rectangle coordinates of the given event on the screen.
- // Returns true if the rectangle is visible on the screen.
- public boolean computeEventRect(int date, int left, int top, int cellWidth, Event event) {
- if (event.drawAsAllday()) {
- return false;
- }
-
- float cellMinuteHeight = mMinuteHeight;
- int startDay = event.startDay;
- int endDay = event.endDay;
-
- if (startDay > date || endDay < date) {
- return false;
- }
-
- int startTime = event.startTime;
- int endTime = event.endTime;
-
- // If the event started on a previous day, then show it starting
- // at the beginning of this day.
- if (startDay < date) {
- startTime = 0;
- }
-
- // If the event ends on a future day, then show it extending to
- // the end of this day.
- if (endDay > date) {
- endTime = DayView.MINUTES_PER_DAY;
- }
-
- int col = event.getColumn();
- int maxCols = event.getMaxColumns();
- int startHour = startTime / 60;
- int endHour = endTime / 60;
-
- // If the end point aligns on a cell boundary then count it as
- // ending in the previous cell so that we don't cross the border
- // between hours.
- if (endHour * 60 == endTime)
- endHour -= 1;
-
- event.top = top;
- event.top += (int) (startTime * cellMinuteHeight);
- event.top += startHour * mHourGap;
-
- event.bottom = top;
- event.bottom += (int) (endTime * cellMinuteHeight);
- event.bottom += endHour * mHourGap - 1;
-
- // Make the rectangle be at least mMinEventHeight pixels high
- if (event.bottom < event.top + mMinEventHeight) {
- event.bottom = event.top + mMinEventHeight;
- }
-
- float colWidth = (float) (cellWidth - (maxCols + 1) * mCellMargin) / (float) maxCols;
- event.left = left + col * (colWidth + mCellMargin);
- event.right = event.left + colWidth;
- return true;
- }
-
- /**
- * Returns true if this event intersects the selection region.
- */
- boolean eventIntersectsSelection(Event event, Rect selection) {
- if (event.left < selection.right && event.right >= selection.left
- && event.top < selection.bottom && event.bottom >= selection.top) {
- return true;
- }
- return false;
- }
-
- /**
- * Computes the distance from the given point to the given event.
- */
- float pointToEvent(float x, float y, Event event) {
- float left = event.left;
- float right = event.right;
- float top = event.top;
- float bottom = event.bottom;
-
- if (x >= left) {
- if (x <= right) {
- if (y >= top) {
- if (y <= bottom) {
- // x,y is inside the event rectangle
- return 0f;
- }
- // x,y is below the event rectangle
- return y - bottom;
- }
- // x,y is above the event rectangle
- return top - y;
- }
-
- // x > right
- float dx = x - right;
- if (y < top) {
- // the upper right corner
- float dy = top - y;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
- if (y > bottom) {
- // the lower right corner
- float dy = y - bottom;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
- // x,y is to the right of the event rectangle
- return dx;
- }
- // x < left
- float dx = left - x;
- if (y < top) {
- // the upper left corner
- float dy = top - y;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
- if (y > bottom) {
- // the lower left corner
- float dy = y - bottom;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
- // x,y is to the left of the event rectangle
- return dx;
- }
-}
diff --git a/src/com/android/calendar/EventGeometry.kt b/src/com/android/calendar/EventGeometry.kt
new file mode 100644
index 00000000..43fc3e77
--- /dev/null
+++ b/src/com/android/calendar/EventGeometry.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.graphics.Rect
+
+class EventGeometry {
+ // This is the space from the grid line to the event rectangle.
+ private var mCellMargin = 0
+ private var mMinuteHeight = 0f
+ private var mHourGap = 0f
+ private var mMinEventHeight = 0f
+ fun setCellMargin(cellMargin: Int) {
+ mCellMargin = cellMargin
+ }
+
+ fun setHourGap(gap: Float) {
+ mHourGap = gap
+ }
+
+ fun setMinEventHeight(height: Float) {
+ mMinEventHeight = height
+ }
+
+ fun setHourHeight(height: Float) {
+ mMinuteHeight = height / 60.0f
+ }
+
+ // Computes the rectangle coordinates of the given event on the screen.
+ // Returns true if the rectangle is visible on the screen.
+ fun computeEventRect(date: Int, left: Int, top: Int, cellWidth: Int, event: Event): Boolean {
+ if (event.drawAsAllday()) {
+ return false
+ }
+ val cellMinuteHeight = mMinuteHeight
+ val startDay: Int = event.startDay
+ val endDay: Int = event.endDay
+ if (startDay > date || endDay < date) {
+ return false
+ }
+ var startTime: Int = event.startTime
+ var endTime: Int = event.endTime
+
+ // If the event started on a previous day, then show it starting
+ // at the beginning of this day.
+ if (startDay < date) {
+ startTime = 0
+ }
+
+ // If the event ends on a future day, then show it extending to
+ // the end of this day.
+ if (endDay > date) {
+ endTime = DayView.MINUTES_PER_DAY
+ }
+ val col: Int = event.column
+ val maxCols: Int = event.maxColumns
+ val startHour = startTime / 60
+ var endHour = endTime / 60
+
+ // If the end point aligns on a cell boundary then count it as
+ // ending in the previous cell so that we don't cross the border
+ // between hours.
+ if (endHour * 60 == endTime) endHour -= 1
+ event.top = top as Float
+ event.top += (startTime * cellMinuteHeight).toInt()
+ event.top += startHour * mHourGap
+ event.bottom = top as Float
+ event.bottom += (endTime * cellMinuteHeight).toInt()
+ event.bottom += endHour * mHourGap - 1
+
+ // Make the rectangle be at least mMinEventHeight pixels high
+ if (event.bottom < event.top + mMinEventHeight) {
+ event.bottom = event.top + mMinEventHeight
+ }
+ val colWidth = (cellWidth - (maxCols + 1) * mCellMargin).toFloat() / maxCols.toFloat()
+ event.left = left + col * (colWidth + mCellMargin)
+ event.right = event.left + colWidth
+ return true
+ }
+
+ /**
+ * Returns true if this event intersects the selection region.
+ */
+ fun eventIntersectsSelection(event: Event, selection: Rect): Boolean {
+ return if (event.left < selection.right && event.right >= selection.left &&
+ event.top < selection.bottom && event.bottom >= selection.top) {
+ true
+ } else false
+ }
+
+ /**
+ * Computes the distance from the given point to the given event.
+ */
+ fun pointToEvent(x: Float, y: Float, event: Event): Float {
+ val left: Float = event.left
+ val right: Float = event.right
+ val top: Float = event.top
+ val bottom: Float = event.bottom
+ if (x >= left) {
+ if (x <= right) {
+ return if (y >= top) {
+ if (y <= bottom) {
+ // x,y is inside the event rectangle
+ 0f
+ } else y - bottom
+ // x,y is below the event rectangle
+ } else top - y
+ // x,y is above the event rectangle
+ }
+
+ // x > right
+ val dx = x - right
+ if (y < top) {
+ // the upper right corner
+ val dy = top - y
+ return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+ }
+ if (y > bottom) {
+ // the lower right corner
+ val dy = y - bottom
+ return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+ }
+ // x,y is to the right of the event rectangle
+ return dx
+ }
+ // x < left
+ val dx = left - x
+ if (y < top) {
+ // the upper left corner
+ val dy = top - y
+ return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+ }
+ if (y > bottom) {
+ // the lower left corner
+ val dy = y - bottom
+ return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+ }
+ // x,y is to the left of the event rectangle
+ return dx
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/EventInfoActivity.java b/src/com/android/calendar/EventInfoActivity.java
deleted file mode 100644
index 626e099d..00000000
--- a/src/com/android/calendar/EventInfoActivity.java
+++ /dev/null
@@ -1,190 +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 static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.util.Log;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class EventInfoActivity extends Activity {
- private static final String TAG = "EventInfoActivity";
- private EventInfoFragment mInfoFragment;
- private long mStartMillis, mEndMillis;
- private long mEventId;
-
- // 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 false;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (selfChange) return;
- if (mInfoFragment != null) {
- mInfoFragment.reloadEvents();
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Get the info needed for the fragment
- Intent intent = getIntent();
- int attendeeResponse = 0;
- mEventId = -1;
- boolean isDialog = false;
-
- if (icicle != null) {
- mEventId = icicle.getLong(EventInfoFragment.BUNDLE_KEY_EVENT_ID);
- mStartMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_START_MILLIS);
- mEndMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_END_MILLIS);
- attendeeResponse = icicle.getInt(EventInfoFragment.BUNDLE_KEY_ATTENDEE_RESPONSE);
- isDialog = icicle.getBoolean(EventInfoFragment.BUNDLE_KEY_IS_DIALOG);
- } 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);
- attendeeResponse = intent.getIntExtra(ATTENDEE_STATUS,
- Attendees.ATTENDEE_STATUS_NONE);
- Uri data = intent.getData();
- if (data != null) {
- try {
- List<String> pathSegments = data.getPathSegments();
- int size = pathSegments.size();
- if (size > 2 && "EventTime".equals(pathSegments.get(2))) {
- // Support non-standard VIEW intent format:
- //dat = content://com.android.calendar/events/[id]/EventTime/[start]/[end]
- mEventId = Long.parseLong(pathSegments.get(1));
- if (size > 4) {
- mStartMillis = Long.parseLong(pathSegments.get(3));
- mEndMillis = Long.parseLong(pathSegments.get(4));
- }
- } else {
- mEventId = Long.parseLong(data.getLastPathSegment());
- }
- } catch (NumberFormatException e) {
- if (mEventId == -1) {
- // do nothing here , deal with it later
- } else if (mStartMillis == 0 || mEndMillis ==0) {
- // Parsing failed on the start or end time , make sure the times were not
- // pulled from the intent's extras and reset them.
- mStartMillis = 0;
- mEndMillis = 0;
- }
- }
- }
- }
-
- if (mEventId == -1) {
- Log.w(TAG, "No event id");
- Toast.makeText(this, R.string.event_not_found, Toast.LENGTH_SHORT).show();
- finish();
- }
-
- // If we do not support showing full screen event info in this configuration,
- // close the activity and show the event in AllInOne.
- Resources res = getResources();
- if (!res.getBoolean(R.bool.agenda_show_event_info_full_screen)
- && !res.getBoolean(R.bool.show_event_info_full_screen)) {
- CalendarController.getInstance(this)
- .launchViewEvent(mEventId, mStartMillis, mEndMillis, attendeeResponse);
- finish();
- return;
- }
-
- setContentView(R.layout.simple_frame_layout);
-
- // Get the fragment if exists
- mInfoFragment = (EventInfoFragment)
- getFragmentManager().findFragmentById(R.id.main_frame);
-
-
- // Remove the application title
- ActionBar bar = getActionBar();
- if (bar != null) {
- bar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
- }
-
- // Create a new fragment if none exists
- if (mInfoFragment == null) {
- FragmentManager fragmentManager = getFragmentManager();
- FragmentTransaction ft = fragmentManager.beginTransaction();
- mInfoFragment = new EventInfoFragment(this, mEventId, mStartMillis, mEndMillis,
- attendeeResponse, isDialog, (isDialog ?
- EventInfoFragment.DIALOG_WINDOW_STYLE :
- EventInfoFragment.FULL_WINDOW_STYLE));
- ft.replace(R.id.main_frame, mInfoFragment);
- ft.commit();
- }
- }
-
- @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);
- }
-
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- getContentResolver().registerContentObserver(CalendarContract.Events.CONTENT_URI,
- true, mObserver);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- getContentResolver().unregisterContentObserver(mObserver);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- }
-}
diff --git a/src/com/android/calendar/EventInfoActivity.kt b/src/com/android/calendar/EventInfoActivity.kt
new file mode 100644
index 00000000..c0a1b9cd
--- /dev/null
+++ b/src/com/android/calendar/EventInfoActivity.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.app.ActionBar
+import android.app.Activity
+import android.app.FragmentManager
+import android.app.FragmentTransaction
+import android.content.Intent
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.provider.CalendarContract
+import android.provider.CalendarContract.Attendees
+import android.util.Log
+import android.widget.Toast
+
+class EventInfoActivity : Activity() {
+ private var mInfoFragment: EventInfoFragment? = null
+ private var mStartMillis: Long = 0
+ private var mEndMillis: Long = 0
+ private var mEventId: Long = 0
+
+ // Create an observer so that we can update the views whenever a
+ // Calendar event changes.
+ private val mObserver: ContentObserver = object : ContentObserver(Handler()) {
+ @Override
+ override fun deliverSelfNotifications(): Boolean {
+ return false
+ }
+
+ @Override
+ override fun onChange(selfChange: Boolean) {
+ if (selfChange) return
+ val temp = mInfoFragment
+ if (temp != null) {
+ mInfoFragment?.reloadEvents()
+ }
+ }
+ }
+
+ @Override
+ protected override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+
+ // Get the info needed for the fragment
+ val intent: Intent = getIntent()
+ var attendeeResponse = 0
+ mEventId = -1
+ var isDialog = false
+ if (icicle != null) {
+ mEventId = icicle.getLong(EventInfoFragment.BUNDLE_KEY_EVENT_ID)
+ mStartMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_START_MILLIS)
+ mEndMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_END_MILLIS)
+ attendeeResponse = icicle.getInt(EventInfoFragment.BUNDLE_KEY_ATTENDEE_RESPONSE)
+ isDialog = icicle.getBoolean(EventInfoFragment.BUNDLE_KEY_IS_DIALOG)
+ } 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)
+ attendeeResponse = intent.getIntExtra(
+ ATTENDEE_STATUS,
+ Attendees.ATTENDEE_STATUS_NONE
+ )
+ val data: Uri? = intent.getData()
+ if (data != null) {
+ try {
+ val pathSegments = data.getPathSegments()
+ val size: Int = pathSegments.size
+ if (size > 2 && "EventTime".equals(pathSegments[2])) {
+ // Support non-standard VIEW intent format:
+ // dat = content://com.android.calendar/events/[id]/EventTime/[start]/[end]
+ mEventId = pathSegments[1].toLong()
+ if (size > 4) {
+ mStartMillis = pathSegments[3].toLong()
+ mEndMillis = pathSegments[4].toLong()
+ }
+ } else {
+ mEventId = data.getLastPathSegment() as Long
+ }
+ } catch (e: NumberFormatException) {
+ if (mEventId == -1L) {
+ // do nothing here , deal with it later
+ } else if (mStartMillis == 0L || mEndMillis == 0L) {
+ // Parsing failed on the start or end time , make sure the times were not
+ // pulled from the intent's extras and reset them.
+ mStartMillis = 0
+ mEndMillis = 0
+ }
+ }
+ }
+ }
+ if (mEventId == -1L) {
+ Log.w(TAG, "No event id")
+ Toast.makeText(this, R.string.event_not_found, Toast.LENGTH_SHORT).show()
+ finish()
+ }
+
+ // If we do not support showing full screen event info in this configuration,
+ // close the activity and show the event in AllInOne.
+ val res: Resources = getResources()
+ if (!res.getBoolean(R.bool.agenda_show_event_info_full_screen) &&
+ !res.getBoolean(R.bool.show_event_info_full_screen)
+ ) {
+ CalendarController.getInstance(this)
+ ?.launchViewEvent(mEventId, mStartMillis, mEndMillis, attendeeResponse)
+ finish()
+ return
+ }
+ setContentView(R.layout.simple_frame_layout)
+
+ // Get the fragment if exists
+ mInfoFragment = getFragmentManager().findFragmentById(R.id.main_frame) as EventInfoFragment
+
+ // Remove the application title
+ val bar: ActionBar? = getActionBar()
+ if (bar != null) {
+ bar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP or ActionBar.DISPLAY_SHOW_HOME)
+ }
+
+ // Create a new fragment if none exists
+ if (mInfoFragment == null) {
+ val fragmentManager: FragmentManager = getFragmentManager()
+ val ft: FragmentTransaction = fragmentManager.beginTransaction()
+ mInfoFragment = EventInfoFragment(
+ this,
+ mEventId,
+ mStartMillis,
+ mEndMillis,
+ attendeeResponse,
+ isDialog,
+ if (isDialog) EventInfoFragment.DIALOG_WINDOW_STYLE
+ else EventInfoFragment.FULL_WINDOW_STYLE
+ )
+ ft.replace(R.id.main_frame, mInfoFragment)
+ ft.commit()
+ }
+ }
+
+ @Override
+ protected override fun 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)
+ }
+
+ @Override
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ }
+
+ @Override
+ protected override fun onResume() {
+ super.onResume()
+ getContentResolver().registerContentObserver(
+ CalendarContract.Events.CONTENT_URI,
+ true, mObserver
+ )
+ }
+
+ @Override
+ protected override fun onPause() {
+ super.onPause()
+ getContentResolver().unregisterContentObserver(mObserver)
+ }
+
+ @Override
+ protected override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ companion object {
+ private const val TAG = "EventInfoActivity"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java
deleted file mode 100644
index 0aa83d02..00000000
--- a/src/com/android/calendar/EventInfoFragment.java
+++ /dev/null
@@ -1,877 +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_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.app.Service;
-import android.content.ActivityNotFoundException;
-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.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-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.provider.CalendarContract.Reminders;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.QuickContact;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.text.method.LinkMovementMethod;
-import android.text.method.MovementMethod;
-import android.text.style.ForegroundColorSpan;
-import android.text.util.Rfc822Token;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.RadioGroup.OnCheckedChangeListener;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.calendar.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.alerts.QuickResponseActivity;
-import com.android.calendarcommon2.DateException;
-import com.android.calendarcommon2.Duration;
-import com.android.calendarcommon2.EventRecurrence;
-import com.android.colorpicker.HsvColorComparator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
- CalendarController.EventHandler, OnClickListener {
-
- public static final boolean DEBUG = false;
-
- public static final String TAG = "EventInfoFragment";
-
- protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
- protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
- protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
- protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
- protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
- protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
- protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color";
- protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init";
- protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color";
- protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key";
- protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init";
- protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color";
- protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init";
- protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
- protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE =
- "key_user_set_attendee_response";
- protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE =
- "key_tentative_user_response";
- protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events";
- protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes";
- protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods";
-
-
- private static final String PERIOD_SPACE = ". ";
-
- private static final String NO_EVENT_COLOR = "";
-
- /**
- * These are the corresponding indices into the array of strings
- * "R.array.change_response_labels" in the resource file.
- */
- static final int UPDATE_SINGLE = 0;
- static final int UPDATE_ALL = 1;
-
- // Style of view
- public static final int FULL_WINDOW_STYLE = 0;
- public static final int DIALOG_WINDOW_STYLE = 1;
-
- private int mWindowStyle = DIALOG_WINDOW_STYLE;
-
- // Query tokens for QueryHandler
- private static final int TOKEN_QUERY_EVENT = 1 << 0;
- private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
- private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
- private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
- private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
- private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5;
- private static final int TOKEN_QUERY_COLORS = 1 << 6;
-
- private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
- | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
- | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS;
-
- private int mCurrentQuery = 0;
-
- private static final String[] EVENT_PROJECTION = new String[] {
- Events._ID, // 0 do not remove; used in DeleteEventHelper
- Events.TITLE, // 1 do not remove; used in DeleteEventHelper
- Events.RRULE, // 2 do not remove; used in DeleteEventHelper
- Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper
- Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper
- Events.DTSTART, // 5 do not remove; used in DeleteEventHelper
- Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper
- Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper
- Events.DESCRIPTION, // 8
- Events.EVENT_LOCATION, // 9
- Calendars.CALENDAR_ACCESS_LEVEL, // 10
- Events.CALENDAR_COLOR, // 11
- Events.EVENT_COLOR, // 12
- Events.HAS_ATTENDEE_DATA, // 13
- Events.ORGANIZER, // 14
- Events.HAS_ALARM, // 15
- Calendars.MAX_REMINDERS, // 16
- Calendars.ALLOWED_REMINDERS, // 17
- Events.CUSTOM_APP_PACKAGE, // 18
- Events.CUSTOM_APP_URI, // 19
- Events.DTEND, // 20
- Events.DURATION, // 21
- Events.ORIGINAL_SYNC_ID // 22 do not remove; used in DeleteEventHelper
- };
- private static final int EVENT_INDEX_ID = 0;
- private static final int EVENT_INDEX_TITLE = 1;
- private static final int EVENT_INDEX_RRULE = 2;
- private static final int EVENT_INDEX_ALL_DAY = 3;
- private static final int EVENT_INDEX_CALENDAR_ID = 4;
- private static final int EVENT_INDEX_DTSTART = 5;
- private static final int EVENT_INDEX_SYNC_ID = 6;
- private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
- private static final int EVENT_INDEX_DESCRIPTION = 8;
- private static final int EVENT_INDEX_EVENT_LOCATION = 9;
- private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
- private static final int EVENT_INDEX_CALENDAR_COLOR = 11;
- private static final int EVENT_INDEX_EVENT_COLOR = 12;
- private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
- private static final int EVENT_INDEX_ORGANIZER = 14;
- private static final int EVENT_INDEX_HAS_ALARM = 15;
- private static final int EVENT_INDEX_MAX_REMINDERS = 16;
- private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17;
- private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18;
- private static final int EVENT_INDEX_CUSTOM_APP_URI = 19;
- private static final int EVENT_INDEX_DTEND = 20;
- private static final int EVENT_INDEX_DURATION = 21;
-
- static {
- if (!Utils.isJellybeanOrLater()) {
- EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // nonessential value
- EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // nonessential value
- }
- }
-
- static final String[] CALENDARS_PROJECTION = new String[] {
- Calendars._ID, // 0
- Calendars.CALENDAR_DISPLAY_NAME, // 1
- Calendars.OWNER_ACCOUNT, // 2
- Calendars.CAN_ORGANIZER_RESPOND, // 3
- Calendars.ACCOUNT_NAME, // 4
- Calendars.ACCOUNT_TYPE // 5
- };
- static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
- static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
- static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
- static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
- static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5;
-
- static final String CALENDARS_WHERE = Calendars._ID + "=?";
- static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
- static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?";
-
- public static final int COLORS_INDEX_COLOR = 1;
- public static final int COLORS_INDEX_COLOR_KEY = 2;
-
- private View mView;
-
- private Uri mUri;
- private long mEventId;
- private Cursor mEventCursor;
- private Cursor mCalendarsCursor;
-
- private static float mScale = 0; // Used for supporting different screen densities
-
- private static int mCustomAppIconSize = 32;
-
- private long mStartMillis;
- private long mEndMillis;
- private boolean mAllDay;
-
- private boolean mOwnerCanRespond;
- private String mSyncAccountName;
- private String mCalendarOwnerAccount;
- private boolean mIsBusyFreeCalendar;
-
- private int mOriginalAttendeeResponse;
- private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
- private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
- private int mWhichEvents = -1;
- // 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 mHasAlarm;
- // 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 Menu mMenu = null;
- private View mHeadlines;
- private ScrollView mScrollView;
- private View mLoadingMsgView;
- private View mErrorMsgView;
- private ObjectAnimator mAnimateAlpha;
- private long mLoadingMsgStartTime;
-
- private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
- private int mOriginalColor = -1;
- private boolean mOriginalColorInitialized = false;
- private int mCalendarColor = -1;
- private boolean mCalendarColorInitialized = false;
- private int mCurrentColor = -1;
- private boolean mCurrentColorInitialized = false;
- private int mCurrentColorKey = -1;
-
- private static final int FADE_IN_TIME = 300; // in milliseconds
- private static final int LOADING_MSG_DELAY = 600; // in milliseconds
- private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
- private boolean mNoCrossFade = false; // Used to prevent repeated cross-fade
- private RadioGroup mResponseRadioGroup;
-
- ArrayList<String> mToEmails = new ArrayList<String>();
- ArrayList<String> mCcEmails = new ArrayList<String>();
-
-
- private final Runnable mTZUpdater = new Runnable() {
- @Override
- public void run() {
- updateEvent(mView);
- }
- };
-
- private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
- @Override
- public void run() {
- // Since this is run after a delay, make sure to only show the message
- // if the event's data is not shown yet.
- if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
- mLoadingMsgStartTime = System.currentTimeMillis();
- mLoadingMsgView.setAlpha(1);
- }
- }
- };
-
- private static int mDialogWidth = 500;
- private static int mDialogHeight = 600;
- private static int DIALOG_TOP_MARGIN = 8;
- private boolean mIsDialog = false;
- private boolean mIsPaused = true;
- private boolean mDismissOnResume = false;
- private int mX = -1;
- private int mY = -1;
- private int mMinTop; // Dialog cannot be above this location
- private boolean mIsTabletConfig;
- private Activity mActivity;
- private Context mContext;
-
- private CalendarController mController;
-
- private void sendAccessibilityEventIfQueryDone(int token) {
- mCurrentQuery |= token;
- if (mCurrentQuery == TOKEN_QUERY_ALL) {
- sendAccessibilityEvent();
- }
- }
-
- public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
- int attendeeResponse, boolean isDialog, int windowStyle) {
-
- Resources r = context.getResources();
- if (mScale == 0) {
- mScale = context.getResources().getDisplayMetrics().density;
- if (mScale != 1) {
- mCustomAppIconSize *= mScale;
- if (isDialog) {
- DIALOG_TOP_MARGIN *= mScale;
- }
- }
- }
- if (isDialog) {
- setDialogSize(r);
- }
- mIsDialog = isDialog;
-
- setStyle(DialogFragment.STYLE_NO_TITLE, 0);
- mUri = uri;
- mStartMillis = startMillis;
- mEndMillis = endMillis;
- mAttendeeResponseFromIntent = attendeeResponse;
- mWindowStyle = windowStyle;
- }
-
- // This is currently required by the fragment manager.
- public EventInfoFragment() {
- }
-
- public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
- int attendeeResponse, boolean isDialog, int windowStyle) {
- this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
- endMillis, attendeeResponse, isDialog, windowStyle);
- mEventId = eventId;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- if (mIsDialog) {
- applyDialogParams();
- }
-
- final Activity activity = getActivity();
- mContext = activity;
- }
-
- private void applyDialogParams() {
- Dialog dialog = getDialog();
- dialog.setCanceledOnTouchOutside(true);
-
- Window window = dialog.getWindow();
- window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
- WindowManager.LayoutParams a = window.getAttributes();
- a.dimAmount = .4f;
-
- a.width = mDialogWidth;
- a.height = mDialogHeight;
-
-
- // On tablets , do smart positioning of dialog
- // On phones , use the whole screen
-
- if (mX != -1 || mY != -1) {
- a.x = mX - mDialogWidth / 2;
- a.y = mY - mDialogHeight / 2;
- if (a.y < mMinTop) {
- a.y = mMinTop + DIALOG_TOP_MARGIN;
- }
- a.gravity = Gravity.LEFT | Gravity.TOP;
- }
- window.setAttributes(a);
- }
-
- public void setDialogParams(int x, int y, int minTop) {
- mX = x;
- mY = y;
- mMinTop = minTop;
- }
-
- // Implements OnCheckedChangeListener
- @Override
- public void onCheckedChanged(RadioGroup group, int checkedId) {
- }
-
- public void onNothingSelected(AdapterView<?> parent) {
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mController.deregisterEventHandler(R.layout.event_info);
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mActivity = activity;
- // Ensure that mIsTabletConfig is set before creating the menu.
- mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
- mController = CalendarController.getInstance(mActivity);
- mController.registerEventHandler(R.layout.event_info, this);
-
- if (!mIsDialog) {
- setHasOptionsMenu(true);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (mWindowStyle == DIALOG_WINDOW_STYLE) {
- mView = inflater.inflate(R.layout.event_info_dialog, container, false);
- } else {
- mView = inflater.inflate(R.layout.event_info, container, false);
- }
- mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
- mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
- mErrorMsgView = mView.findViewById(R.id.event_info_error_msg);
- mTitle = (TextView) mView.findViewById(R.id.title);
- mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
- mWhere = (TextView) mView.findViewById(R.id.where);
- mHeadlines = mView.findViewById(R.id.event_info_headline);
-
- mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
-
- mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
- mAnimateAlpha.setDuration(FADE_IN_TIME);
- mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
- int defLayerType;
-
- @Override
- public void onAnimationStart(Animator animation) {
- // Use hardware layer for better performance during animation
- defLayerType = mScrollView.getLayerType();
- mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- // Ensure that the loading message is gone before showing the
- // event info
- mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
- mLoadingMsgView.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mScrollView.setLayerType(defLayerType, null);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mScrollView.setLayerType(defLayerType, null);
- // Do not cross fade after the first time
- mNoCrossFade = true;
- }
- });
-
- mLoadingMsgView.setAlpha(0);
- mScrollView.setAlpha(0);
- mErrorMsgView.setVisibility(View.INVISIBLE);
- mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
-
- // 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);
- }
-
- return mView;
- }
-
- private void updateTitle() {
- Resources res = getActivity().getResources();
- getActivity().setTitle(res.getString(R.string.event_info_title));
- }
-
- /**
- * Initializes the event cursor, which is expected to point to the first
- * (and only) result from a query.
- * @return false if the cursor is empty, true otherwise
- */
- private boolean initEventCursor() {
- if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
- return false;
- }
- mEventCursor.moveToFirst();
- mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
- String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
- // mHasAlarm will be true if it was saved in the event already.
- mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : false;
- return true;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // Show color/edit/delete buttons only in non-dialog configuration
- if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
- inflater.inflate(R.menu.event_info_title_bar, menu);
- mMenu = menu;
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
- // If we're a dialog we don't want to handle menu buttons
- if (mIsDialog) {
- return false;
- }
- // Handles option menu selections:
- // Home button - close event info activity and start the main calendar
- // one
- // Edit button - start the event edit activity and close the info
- // activity
- // Delete button - start a delete query that calls a runnable that close
- // the info activity
-
- final int itemId = item.getItemId();
- if (itemId == android.R.id.home) {
- Utils.returnToCalendarHome(mContext);
- mActivity.finish();
- return true;
- } else if (itemId == R.id.info_action_edit) {
- mActivity.finish();
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- @Override
- public void onDestroy() {
- if (mEventCursor != null) {
- mEventCursor.close();
- }
- if (mCalendarsCursor != null) {
- mCalendarsCursor.close();
- }
- super.onDestroy();
- }
-
- /**
- * 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.
- *
- * @param eventId The recurring event.
- * @param status The new value for selfAttendeeStatus.
- */
- private void createExceptionResponse(long eventId, int status) {
- ContentValues values = new ContentValues();
- values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
- values.put(Events.SELF_ATTENDEE_STATUS, status);
- values.put(Events.STATUS, Events.STATUS_CONFIRMED);
-
- ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
- Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
- String.valueOf(eventId));
- ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
- }
-
- public static int getResponseFromButtonId(int buttonId) {
- return Attendees.ATTENDEE_STATUS_NONE;
- }
-
- public static int findButtonIdForResponse(int response) {
- return -1;
- }
-
- private void displayEventNotFound() {
- mErrorMsgView.setVisibility(View.VISIBLE);
- mScrollView.setVisibility(View.GONE);
- mLoadingMsgView.setVisibility(View.GONE);
- }
-
- private void updateEvent(View view) {
- if (mEventCursor == null || view == null) {
- return;
- }
-
- Context context = view.getContext();
- if (context == null) {
- return;
- }
-
- String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
- if (eventName == null || eventName.length() == 0) {
- eventName = getActivity().getString(R.string.no_title_label);
- }
-
- // 3rd parties might not have specified the start/end time when firing the
- // Events.CONTENT_URI intent. Update these with values read from the db.
- if (mStartMillis == 0 && mEndMillis == 0) {
- mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
- mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
- if (mEndMillis == 0) {
- String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
- if (!TextUtils.isEmpty(duration)) {
- try {
- Duration d = new Duration();
- d.parse(duration);
- long endMillis = mStartMillis + d.getMillis();
- if (endMillis >= mStartMillis) {
- mEndMillis = endMillis;
- } else {
- Log.d(TAG, "Invalid duration string: " + duration);
- }
- } catch (DateException e) {
- Log.d(TAG, "Error parsing duration string " + duration, e);
- }
- }
- if (mEndMillis == 0) {
- mEndMillis = mStartMillis;
- }
- }
- }
-
- mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
- String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
- String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
- String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
- String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
-
- mHeadlines.setBackgroundColor(mCurrentColor);
-
- // What
- if (eventName != null) {
- setTextCommon(view, R.id.title, eventName);
- }
-
- // When
- // Set the date and repeats (if any)
- String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
-
- Resources resources = context.getResources();
- String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
- System.currentTimeMillis(), localTimezone, mAllDay, context);
-
- String displayedTimezone = null;
- if (!mAllDay) {
- displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
- eventTimezone);
- }
- // Display the datetime. Make the timezone (if any) transparent.
- if (displayedTimezone == null) {
- setTextCommon(view, R.id.when_datetime, displayedDatetime);
- } else {
- int timezoneIndex = displayedDatetime.length();
- displayedDatetime += " " + displayedTimezone;
- SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
- ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
- resources.getColor(R.color.event_info_headline_transparent_color));
- sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- setTextCommon(view, R.id.when_datetime, sb);
- }
-
- view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
-
- // Organizer view is setup in the updateCalendar method
-
-
- // Where
- if (location == null || location.trim().length() == 0) {
- setVisibilityCommon(view, R.id.where, View.GONE);
- } else {
- final TextView textView = mWhere;
- if (textView != null) {
- textView.setText(location.trim());
- }
- }
-
- // Launch Custom App
- if (Utils.isJellybeanOrLater()) {
- updateCustomAppButton();
- }
- }
-
- private void updateCustomAppButton() {
- setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
- return;
- }
-
- private void sendAccessibilityEvent() {
- AccessibilityManager am =
- (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
- if (!am.isEnabled()) {
- return;
- }
-
- AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- event.setClassName(EventInfoFragment.class.getName());
- event.setPackageName(getActivity().getPackageName());
- List<CharSequence> text = event.getText();
-
- if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
- int id = mResponseRadioGroup.getCheckedRadioButtonId();
- if (id != View.NO_ID) {
- text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
- text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
- .getText() + PERIOD_SPACE));
- }
- }
-
- am.sendAccessibilityEvent(event);
- }
-
- private void updateCalendar(View view) {
-
- mCalendarOwnerAccount = "";
- if (mCalendarsCursor != null && mEventCursor != null) {
- mCalendarsCursor.moveToFirst();
- String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
- mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
- mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
- mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
-
- setVisibilityCommon(view, R.id.organizer_container, View.GONE);
- mIsBusyFreeCalendar =
- mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
-
- if (!mIsBusyFreeCalendar) {
-
- View b = mView.findViewById(R.id.edit);
- b.setEnabled(true);
- b.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // For dialogs, just close the fragment
- // For full screen, close activity on phone, leave it for tablet
- if (mIsDialog) {
- EventInfoFragment.this.dismiss();
- }
- else if (!mIsTabletConfig){
- getActivity().finish();
- }
- }
- });
- }
- View button;
- if ((!mIsDialog && !mIsTabletConfig ||
- mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
- mActivity.invalidateOptionsMenu();
- }
- } else {
- setVisibilityCommon(view, R.id.calendar, View.GONE);
- sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
- }
- }
-
- private void setTextCommon(View view, int id, CharSequence text) {
- TextView textView = (TextView) view.findViewById(id);
- if (textView == null)
- return;
- textView.setText(text);
- }
-
- private void setVisibilityCommon(View view, int id, int visibility) {
- View v = view.findViewById(id);
- if (v != null) {
- v.setVisibility(visibility);
- }
- return;
- }
-
- @Override
- public void onPause() {
- mIsPaused = true;
- super.onPause();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsDialog) {
- setDialogSize(getActivity().getResources());
- applyDialogParams();
- }
- mIsPaused = false;
- if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
- int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
- mResponseRadioGroup.check(buttonId);
- }
- }
-
- @Override
- public void eventsChanged() {
- }
-
- @Override
- public long getSupportedEventTypes() {
- return EventType.EVENTS_CHANGED;
- }
-
- @Override
- public void handleEvent(EventInfo event) {
- reloadEvents();
- }
-
- public void reloadEvents() {
- }
-
- @Override
- public void onClick(View view) {
- }
-
- public long getEventId() {
- return mEventId;
- }
-
- public long getStartMillis() {
- return mStartMillis;
- }
- public long getEndMillis() {
- return mEndMillis;
- }
- private void setDialogSize(Resources r) {
- mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
- mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
- }
-}
diff --git a/src/com/android/calendar/EventInfoFragment.kt b/src/com/android/calendar/EventInfoFragment.kt
new file mode 100644
index 00000000..fcc27fc8
--- /dev/null
+++ b/src/com/android/calendar/EventInfoFragment.kt
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.Activity
+import android.app.Dialog
+import android.app.DialogFragment
+import android.app.Service
+import android.content.ContentProviderOperation
+import android.content.ContentUris
+import android.content.ContentValues
+import android.content.Context
+import android.content.res.Resources
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Events
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.TextUtils
+import android.text.style.ForegroundColorSpan
+import android.util.Log
+import android.util.SparseIntArray
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.widget.AdapterView
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import android.widget.RadioGroup.OnCheckedChangeListener
+import android.widget.ScrollView
+import android.widget.TextView
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import com.android.calendarcommon2.DateException
+import com.android.calendarcommon2.Duration
+import java.util.ArrayList
+
+class EventInfoFragment : DialogFragment, OnCheckedChangeListener, CalendarController.EventHandler,
+ OnClickListener {
+ private var mWindowStyle = DIALOG_WINDOW_STYLE
+ private var mCurrentQuery = 0
+
+ companion object {
+ const val DEBUG = false
+ const val TAG = "EventInfoFragment"
+ internal const val BUNDLE_KEY_EVENT_ID = "key_event_id"
+ internal const val BUNDLE_KEY_START_MILLIS = "key_start_millis"
+ internal const val BUNDLE_KEY_END_MILLIS = "key_end_millis"
+ internal const val BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog"
+ internal const val BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible"
+ internal const val BUNDLE_KEY_WINDOW_STYLE = "key_window_style"
+ internal const val BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color"
+ internal const val BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init"
+ internal const val BUNDLE_KEY_CURRENT_COLOR = "key_current_color"
+ internal const val BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key"
+ internal const val BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init"
+ internal const val BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color"
+ internal const val BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init"
+ internal const val BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response"
+ internal const val BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE = "key_user_set_attendee_response"
+ internal const val BUNDLE_KEY_TENTATIVE_USER_RESPONSE = "key_tentative_user_response"
+ internal const val BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events"
+ internal const val BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes"
+ internal const val BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods"
+ private const val PERIOD_SPACE = ". "
+ private const val NO_EVENT_COLOR = ""
+
+ /**
+ * These are the corresponding indices into the array of strings
+ * "R.array.change_response_labels" in the resource file.
+ */
+ const val UPDATE_SINGLE = 0
+ const val UPDATE_ALL = 1
+
+ // Style of view
+ const val FULL_WINDOW_STYLE = 0
+ const val DIALOG_WINDOW_STYLE = 1
+
+ // Query tokens for QueryHandler
+ private const val TOKEN_QUERY_EVENT = 1 shl 0
+ private const val TOKEN_QUERY_CALENDARS = 1 shl 1
+ private const val TOKEN_QUERY_ATTENDEES = 1 shl 2
+ private const val TOKEN_QUERY_DUPLICATE_CALENDARS = 1 shl 3
+ private const val TOKEN_QUERY_REMINDERS = 1 shl 4
+ private const val TOKEN_QUERY_VISIBLE_CALENDARS = 1 shl 5
+ private const val TOKEN_QUERY_COLORS = 1 shl 6
+ private const val TOKEN_QUERY_ALL = (TOKEN_QUERY_DUPLICATE_CALENDARS
+ or TOKEN_QUERY_ATTENDEES or TOKEN_QUERY_CALENDARS or TOKEN_QUERY_EVENT
+ or TOKEN_QUERY_REMINDERS or TOKEN_QUERY_VISIBLE_CALENDARS or TOKEN_QUERY_COLORS)
+ private val EVENT_PROJECTION = arrayOf<String>(
+ Events._ID, // 0 do not remove; used in DeleteEventHelper
+ Events.TITLE, // 1 do not remove; used in DeleteEventHelper
+ Events.RRULE, // 2 do not remove; used in DeleteEventHelper
+ Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper
+ Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper
+ Events.DTSTART, // 5 do not remove; used in DeleteEventHelper
+ Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper
+ Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper
+ Events.DESCRIPTION, // 8
+ Events.EVENT_LOCATION, // 9
+ Calendars.CALENDAR_ACCESS_LEVEL, // 10
+ Events.CALENDAR_COLOR, // 11
+ Events.EVENT_COLOR, // 12
+ Events.HAS_ATTENDEE_DATA, // 13
+ Events.ORGANIZER, // 14
+ Events.HAS_ALARM, // 15
+ Calendars.MAX_REMINDERS, // 16
+ Calendars.ALLOWED_REMINDERS, // 17
+ Events.CUSTOM_APP_PACKAGE, // 18
+ Events.CUSTOM_APP_URI, // 19
+ Events.DTEND, // 20
+ Events.DURATION, // 21
+ Events.ORIGINAL_SYNC_ID // 22 do not remove; used in DeleteEventHelper
+ )
+ private const val EVENT_INDEX_ID = 0
+ private const val EVENT_INDEX_TITLE = 1
+ private const val EVENT_INDEX_RRULE = 2
+ private const val EVENT_INDEX_ALL_DAY = 3
+ private const val EVENT_INDEX_CALENDAR_ID = 4
+ private const val EVENT_INDEX_DTSTART = 5
+ private const val EVENT_INDEX_SYNC_ID = 6
+ private const val EVENT_INDEX_EVENT_TIMEZONE = 7
+ private const val EVENT_INDEX_DESCRIPTION = 8
+ private const val EVENT_INDEX_EVENT_LOCATION = 9
+ private const val EVENT_INDEX_ACCESS_LEVEL = 10
+ private const val EVENT_INDEX_CALENDAR_COLOR = 11
+ private const val EVENT_INDEX_EVENT_COLOR = 12
+ private const val EVENT_INDEX_HAS_ATTENDEE_DATA = 13
+ private const val EVENT_INDEX_ORGANIZER = 14
+ private const val EVENT_INDEX_HAS_ALARM = 15
+ private const val EVENT_INDEX_MAX_REMINDERS = 16
+ private const val EVENT_INDEX_ALLOWED_REMINDERS = 17
+ private const val EVENT_INDEX_CUSTOM_APP_PACKAGE = 18
+ private const val EVENT_INDEX_CUSTOM_APP_URI = 19
+ private const val EVENT_INDEX_DTEND = 20
+ private const val EVENT_INDEX_DURATION = 21
+ val CALENDARS_PROJECTION = arrayOf<String>(
+ Calendars._ID, // 0
+ Calendars.CALENDAR_DISPLAY_NAME, // 1
+ Calendars.OWNER_ACCOUNT, // 2
+ Calendars.CAN_ORGANIZER_RESPOND, // 3
+ Calendars.ACCOUNT_NAME, // 4
+ Calendars.ACCOUNT_TYPE // 5
+ )
+ const val CALENDARS_INDEX_DISPLAY_NAME = 1
+ const val CALENDARS_INDEX_OWNER_ACCOUNT = 2
+ const val CALENDARS_INDEX_OWNER_CAN_RESPOND = 3
+ const val CALENDARS_INDEX_ACCOUNT_NAME = 4
+ const val CALENDARS_INDEX_ACCOUNT_TYPE = 5
+ val CALENDARS_WHERE: String = Calendars._ID.toString() + "=?"
+ val CALENDARS_DUPLICATE_NAME_WHERE: String =
+ Calendars.CALENDAR_DISPLAY_NAME.toString() + "=?"
+ val CALENDARS_VISIBLE_WHERE: String = Calendars.VISIBLE.toString() + "=?"
+ const val COLORS_INDEX_COLOR = 1
+ const val COLORS_INDEX_COLOR_KEY = 2
+ private var mScale = 0f // Used for supporting different screen densities
+ private var mCustomAppIconSize = 32
+ private const val FADE_IN_TIME = 300 // in milliseconds
+ private const val LOADING_MSG_DELAY = 600 // in milliseconds
+ private const val LOADING_MSG_MIN_DISPLAY_TIME = 600
+ private var mDialogWidth = 500
+ private var mDialogHeight = 600
+ private var DIALOG_TOP_MARGIN = 8
+ fun getResponseFromButtonId(buttonId: Int): Int {
+ return Attendees.ATTENDEE_STATUS_NONE
+ }
+
+ fun findButtonIdForResponse(response: Int): Int {
+ return -1
+ }
+
+ init {
+ if (!Utils.isJellybeanOrLater()) {
+ EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID // nonessential value
+ EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID // nonessential value
+ }
+ }
+ }
+
+ private var mView: View? = null
+ private var mUri: Uri? = null
+ var eventId: Long = 0
+ private set
+ private val mEventCursor: Cursor? = null
+ private val mCalendarsCursor: Cursor? = null
+ var startMillis: Long = 0
+ private set
+ var endMillis: Long = 0
+ private set
+ private var mAllDay = false
+ private var mOwnerCanRespond = false
+ private var mSyncAccountName: String? = null
+ private var mCalendarOwnerAccount: String? = null
+ private var mIsBusyFreeCalendar = false
+ private val mOriginalAttendeeResponse = 0
+ private var mAttendeeResponseFromIntent: Int = Attendees.ATTENDEE_STATUS_NONE
+ private val mUserSetResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+ private val mWhichEvents = -1
+
+ // 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 val mTentativeUserSetResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+ private var mHasAlarm = false
+
+ // Used to prevent saving changes in event if it is being deleted.
+ private val mEventDeletionStarted = false
+ private var mTitle: TextView? = null
+ private var mWhenDateTime: TextView? = null
+ private var mWhere: TextView? = null
+ private var mMenu: Menu? = null
+ private var mHeadlines: View? = null
+ private var mScrollView: ScrollView? = null
+ private var mLoadingMsgView: View? = null
+ private var mErrorMsgView: View? = null
+ private var mAnimateAlpha: ObjectAnimator? = null
+ private var mLoadingMsgStartTime: Long = 0
+ private val mDisplayColorKeyMap: SparseIntArray = SparseIntArray()
+ private val mOriginalColor = -1
+ private val mOriginalColorInitialized = false
+ private val mCalendarColor = -1
+ private val mCalendarColorInitialized = false
+ private val mCurrentColor = -1
+ private val mCurrentColorInitialized = false
+ private val mCurrentColorKey = -1
+ private var mNoCrossFade = false // Used to prevent repeated cross-fade
+ private var mResponseRadioGroup: RadioGroup? = null
+ var mToEmails: ArrayList<String> = ArrayList<String>()
+ var mCcEmails: ArrayList<String> = ArrayList<String>()
+ private val mTZUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ updateEvent(mView)
+ }
+ }
+ private val mLoadingMsgAlphaUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ // Since this is run after a delay, make sure to only show the message
+ // if the event's data is not shown yet.
+ if (!mAnimateAlpha!!.isRunning() && mScrollView!!.getAlpha() == 0f) {
+ mLoadingMsgStartTime = System.currentTimeMillis()
+ mLoadingMsgView?.setAlpha(1f)
+ }
+ }
+ }
+ private var mIsDialog = false
+ private var mIsPaused = true
+ private val mDismissOnResume = false
+ private var mX = -1
+ private var mY = -1
+ private var mMinTop = 0 // Dialog cannot be above this location
+ private var mIsTabletConfig = false
+ private var mActivity: Activity? = null
+ private var mContext: Context? = null
+ private var mController: CalendarController? = null
+ private fun sendAccessibilityEventIfQueryDone(token: Int) {
+ mCurrentQuery = mCurrentQuery or token
+ if (mCurrentQuery == TOKEN_QUERY_ALL) {
+ sendAccessibilityEvent()
+ }
+ }
+
+ constructor(
+ context: Context,
+ uri: Uri?,
+ startMillis: Long,
+ endMillis: Long,
+ attendeeResponse: Int,
+ isDialog: Boolean,
+ windowStyle: Int
+ ) {
+ val r: Resources = context.getResources()
+ if (mScale == 0f) {
+ mScale = context.getResources().getDisplayMetrics().density
+ if (mScale != 1f) {
+ mCustomAppIconSize *= mScale.toInt()
+ if (isDialog) {
+ DIALOG_TOP_MARGIN *= mScale.toInt()
+ }
+ }
+ }
+ if (isDialog) {
+ setDialogSize(r)
+ }
+ mIsDialog = isDialog
+ setStyle(DialogFragment.STYLE_NO_TITLE, 0)
+ mUri = uri
+ this.startMillis = startMillis
+ this.endMillis = endMillis
+ mAttendeeResponseFromIntent = attendeeResponse
+ mWindowStyle = windowStyle
+ }
+
+ // This is currently required by the fragment manager.
+ constructor() {}
+ constructor(
+ context: Context?,
+ eventId: Long,
+ startMillis: Long,
+ endMillis: Long,
+ attendeeResponse: Int,
+ isDialog: Boolean,
+ windowStyle: Int
+ ) : this(
+ context as Context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
+ endMillis, attendeeResponse, isDialog, windowStyle
+ ) {
+ this.eventId = eventId
+ }
+
+ @Override
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ if (mIsDialog) {
+ applyDialogParams()
+ }
+ val activity: Activity = getActivity()
+ mContext = activity
+ }
+
+ private fun applyDialogParams() {
+ val dialog: Dialog = getDialog()
+ dialog.setCanceledOnTouchOutside(true)
+ val window: Window? = dialog.getWindow()
+ window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ val a: WindowManager.LayoutParams? = window?.getAttributes()
+ a!!.dimAmount = .4f
+ a!!.width = mDialogWidth
+ a!!.height = mDialogHeight
+
+ // On tablets , do smart positioning of dialog
+ // On phones , use the whole screen
+ if (mX != -1 || mY != -1) {
+ a!!.x = mX - mDialogWidth / 2
+ a!!.y = mY - mDialogHeight / 2
+ if (a!!.y < mMinTop) {
+ a!!.y = mMinTop + DIALOG_TOP_MARGIN
+ }
+ a!!.gravity = Gravity.LEFT or Gravity.TOP
+ }
+ window?.setAttributes(a)
+ }
+
+ fun setDialogParams(x: Int, y: Int, minTop: Int) {
+ mX = x
+ mY = y
+ mMinTop = minTop
+ }
+
+ // Implements OnCheckedChangeListener
+ @Override
+ override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
+ }
+
+ fun onNothingSelected(parent: AdapterView<*>?) {}
+ @Override
+ override fun onDetach() {
+ super.onDetach()
+ mController?.deregisterEventHandler(R.layout.event_info)
+ }
+
+ @Override
+ override fun onAttach(activity: Activity?) {
+ super.onAttach(activity)
+ mActivity = activity
+ // Ensure that mIsTabletConfig is set before creating the menu.
+ mIsTabletConfig = Utils.getConfigBool(mActivity as Context, R.bool.tablet_config)
+ mController = CalendarController.getInstance(mActivity)
+ mController?.registerEventHandler(R.layout.event_info, this)
+ if (!mIsDialog) {
+ setHasOptionsMenu(true)
+ }
+ }
+
+ @Override
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ mView = if (mWindowStyle == DIALOG_WINDOW_STYLE) {
+ inflater.inflate(R.layout.event_info_dialog, container, false)
+ } else {
+ inflater.inflate(R.layout.event_info, container, false)
+ }
+ mScrollView = mView?.findViewById(R.id.event_info_scroll_view) as ScrollView
+ mLoadingMsgView = mView?.findViewById(R.id.event_info_loading_msg)
+ mErrorMsgView = mView?.findViewById(R.id.event_info_error_msg)
+ mTitle = mView?.findViewById(R.id.title) as TextView
+ mWhenDateTime = mView?.findViewById(R.id.when_datetime) as TextView
+ mWhere = mView?.findViewById(R.id.where) as TextView
+ mHeadlines = mView?.findViewById(R.id.event_info_headline)
+ mResponseRadioGroup = mView?.findViewById(R.id.response_value) as RadioGroup
+ mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0f, 1f)
+ mAnimateAlpha?.setDuration(FADE_IN_TIME.toLong())
+ mAnimateAlpha?.addListener(object : AnimatorListenerAdapter() {
+ var defLayerType = 0
+ @Override
+ override fun onAnimationStart(animation: Animator) {
+ // Use hardware layer for better performance during animation
+ defLayerType = mScrollView?.getLayerType() as Int
+ mScrollView?.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ // Ensure that the loading message is gone before showing the
+ // event info
+ mLoadingMsgView?.removeCallbacks(mLoadingMsgAlphaUpdater)
+ mLoadingMsgView?.setVisibility(View.GONE)
+ }
+
+ @Override
+ override fun onAnimationCancel(animation: Animator) {
+ mScrollView?.setLayerType(defLayerType, null)
+ }
+
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ mScrollView?.setLayerType(defLayerType, null)
+ // Do not cross fade after the first time
+ mNoCrossFade = true
+ }
+ })
+ mLoadingMsgView?.setAlpha(0f)
+ mScrollView?.setAlpha(0f)
+ mErrorMsgView?.setVisibility(View.INVISIBLE)
+ mLoadingMsgView?.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY.toLong())
+
+ // Hide Edit/Delete buttons if in full screen mode on a phone
+ if (!mIsDialog && !mIsTabletConfig || mWindowStyle == FULL_WINDOW_STYLE) {
+ mView?.findViewById<View>(R.id.event_info_buttons_container)?.setVisibility(View.GONE)
+ }
+ return mView
+ }
+
+ private fun updateTitle() {
+ val res: Resources = getActivity().getResources()
+ getActivity().setTitle(res.getString(R.string.event_info_title))
+ }
+
+ /**
+ * Initializes the event cursor, which is expected to point to the first
+ * (and only) result from a query.
+ * @return false if the cursor is empty, true otherwise
+ */
+ private fun initEventCursor(): Boolean {
+ if (mEventCursor == null || mEventCursor.getCount() === 0) {
+ return false
+ }
+ mEventCursor.moveToFirst()
+ eventId = mEventCursor.getInt(EVENT_INDEX_ID).toLong()
+ val rRule: String = mEventCursor.getString(EVENT_INDEX_RRULE)
+ // mHasAlarm will be true if it was saved in the event already.
+ mHasAlarm = if (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) === 1) true else false
+ return true
+ }
+
+ @Override
+ override fun onSaveInstanceState(outState: Bundle?) {
+ super.onSaveInstanceState(outState)
+ }
+
+ @Override
+ override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ // Show color/edit/delete buttons only in non-dialog configuration
+ if (!mIsDialog && !mIsTabletConfig || mWindowStyle == FULL_WINDOW_STYLE) {
+ inflater.inflate(R.menu.event_info_title_bar, menu)
+ mMenu = menu
+ }
+ }
+
+ @Override
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+
+ // If we're a dialog we don't want to handle menu buttons
+ if (mIsDialog) {
+ return false
+ }
+ // Handles option menu selections:
+ // Home button - close event info activity and start the main calendar
+ // one
+ // Edit button - start the event edit activity and close the info
+ // activity
+ // Delete button - start a delete query that calls a runnable that close
+ // the info activity
+ val itemId: Int = item.getItemId()
+ if (itemId == android.R.id.home) {
+ Utils.returnToCalendarHome(mContext as Context)
+ mActivity?.finish()
+ return true
+ } else if (itemId == R.id.info_action_edit) {
+ mActivity?.finish()
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ @Override
+ override fun onStop() {
+ super.onStop()
+ }
+
+ @Override
+ override fun onDestroy() {
+ if (mEventCursor != null) {
+ mEventCursor.close()
+ }
+ if (mCalendarsCursor != null) {
+ mCalendarsCursor.close()
+ }
+ super.onDestroy()
+ }
+
+ /**
+ * 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.
+ *
+ * @param eventId The recurring event.
+ * @param status The new value for selfAttendeeStatus.
+ */
+ private fun createExceptionResponse(eventId: Long, status: Int) {
+ val values = ContentValues()
+ values.put(Events.ORIGINAL_INSTANCE_TIME, startMillis)
+ values.put(Events.SELF_ATTENDEE_STATUS, status)
+ values.put(Events.STATUS, Events.STATUS_CONFIRMED)
+ val ops: ArrayList<ContentProviderOperation> = ArrayList<ContentProviderOperation>()
+ val exceptionUri: Uri = Uri.withAppendedPath(
+ Events.CONTENT_EXCEPTION_URI,
+ eventId.toString()
+ )
+ ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build())
+ }
+
+ private fun displayEventNotFound() {
+ mErrorMsgView?.setVisibility(View.VISIBLE)
+ mScrollView?.setVisibility(View.GONE)
+ mLoadingMsgView?.setVisibility(View.GONE)
+ }
+
+ private fun updateEvent(view: View?) {
+ if (mEventCursor == null || view == null) {
+ return
+ }
+ val context: Context = view.getContext() ?: return
+ var eventName: String = mEventCursor.getString(EVENT_INDEX_TITLE)
+ if (eventName == null || eventName.length == 0) {
+ eventName = getActivity().getString(R.string.no_title_label)
+ }
+
+ // 3rd parties might not have specified the start/end time when firing the
+ // Events.CONTENT_URI intent. Update these with values read from the db.
+ if (startMillis == 0L && endMillis == 0L) {
+ startMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART)
+ endMillis = mEventCursor.getLong(EVENT_INDEX_DTEND)
+ if (endMillis == 0L) {
+ val duration: String = mEventCursor.getString(EVENT_INDEX_DURATION)
+ if (!TextUtils.isEmpty(duration)) {
+ try {
+ val d = Duration()
+ d.parse(duration)
+ val endMillis: Long = startMillis + d.getMillis()
+ if (endMillis >= startMillis) {
+ this.endMillis = endMillis
+ } else {
+ Log.d(TAG, "Invalid duration string: $duration")
+ }
+ } catch (e: DateException) {
+ Log.d(TAG, "Error parsing duration string $duration", e)
+ }
+ }
+ if (endMillis == 0L) {
+ endMillis = startMillis
+ }
+ }
+ }
+ mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) !== 0
+ val location: String = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION)
+ val description: String = mEventCursor.getString(EVENT_INDEX_DESCRIPTION)
+ val rRule: String = mEventCursor.getString(EVENT_INDEX_RRULE)
+ val eventTimezone: String = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE)
+ mHeadlines?.setBackgroundColor(mCurrentColor)
+
+ // What
+ if (eventName != null) {
+ setTextCommon(view, R.id.title, eventName)
+ }
+
+ // When
+ // Set the date and repeats (if any)
+ val localTimezone: String? = Utils.getTimeZone(mActivity, mTZUpdater)
+ val resources: Resources = context.getResources()
+ var displayedDatetime: String? = Utils.getDisplayedDatetime(
+ startMillis, endMillis,
+ System.currentTimeMillis(), localTimezone as String, mAllDay, context
+ )
+ var displayedTimezone: String? = null
+ if (!mAllDay) {
+ displayedTimezone = Utils.getDisplayedTimezone(
+ startMillis, localTimezone,
+ eventTimezone
+ )
+ }
+ // Display the datetime. Make the timezone (if any) transparent.
+ if (displayedTimezone == null) {
+ setTextCommon(view, R.id.when_datetime, displayedDatetime as CharSequence)
+ } else {
+ val timezoneIndex: Int = displayedDatetime!!.length
+ displayedDatetime += " $displayedTimezone"
+ val sb = SpannableStringBuilder(displayedDatetime)
+ val transparentColorSpan = ForegroundColorSpan(
+ resources.getColor(R.color.event_info_headline_transparent_color)
+ )
+ sb.setSpan(
+ transparentColorSpan, timezoneIndex, displayedDatetime!!.length,
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE
+ )
+ setTextCommon(view, R.id.when_datetime, sb)
+ }
+ view.findViewById<View>(R.id.when_repeat).setVisibility(View.GONE)
+
+ // Organizer view is setup in the updateCalendar method
+
+ // Where
+ if (location == null || location.trim().length == 0) {
+ setVisibilityCommon(view, R.id.where, View.GONE)
+ } else {
+ val textView: TextView? = mWhere
+ if (textView != null) {
+ textView.setText(location.trim())
+ }
+ }
+
+ // Launch Custom App
+ if (Utils.isJellybeanOrLater()) {
+ updateCustomAppButton()
+ }
+ }
+
+ private fun updateCustomAppButton() {
+ setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE)
+ return
+ }
+
+ private fun sendAccessibilityEvent() {
+ val am: AccessibilityManager =
+ getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ if (!am.isEnabled()) {
+ return
+ }
+ val event: AccessibilityEvent =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ event.setClassName(EventInfoFragment::class.java.getName())
+ event.setPackageName(getActivity().getPackageName())
+ var text = event.getText()
+ if (mResponseRadioGroup?.getVisibility() == View.VISIBLE) {
+ val id: Int = mResponseRadioGroup!!.getCheckedRadioButtonId()
+ if (id != View.NO_ID) {
+ text.add((getView()?.findViewById(R.id.response_label) as TextView)?.getText())
+ text.add(
+ (mResponseRadioGroup?.findViewById(id) as RadioButton)
+ .getText().toString() + PERIOD_SPACE
+ )
+ }
+ }
+ am.sendAccessibilityEvent(event)
+ }
+
+ private fun updateCalendar(view: View?) {
+ mCalendarOwnerAccount = ""
+ if (mCalendarsCursor != null && mEventCursor != null) {
+ mCalendarsCursor.moveToFirst()
+ val tempAccount: String = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT)
+ mCalendarOwnerAccount = tempAccount ?: ""
+ mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) !== 0
+ mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME)
+ setVisibilityCommon(view, R.id.organizer_container, View.GONE)
+ mIsBusyFreeCalendar =
+ mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) === Calendars.CAL_ACCESS_FREEBUSY
+ if (!mIsBusyFreeCalendar) {
+ val b: View? = mView?.findViewById(R.id.edit)
+ b?.setEnabled(true)
+ b?.setOnClickListener(object : OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ // For dialogs, just close the fragment
+ // For full screen, close activity on phone, leave it for tablet
+ if (mIsDialog) {
+ this@EventInfoFragment.dismiss()
+ } else if (!mIsTabletConfig) {
+ getActivity().finish()
+ }
+ }
+ })
+ }
+ var button: View
+ if ((!mIsDialog && !mIsTabletConfig ||
+ mWindowStyle == FULL_WINDOW_STYLE) && mMenu != null
+ ) {
+ mActivity?.invalidateOptionsMenu()
+ }
+ } else {
+ setVisibilityCommon(view, R.id.calendar, View.GONE)
+ sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS)
+ }
+ }
+
+ private fun setTextCommon(view: View, id: Int, text: CharSequence) {
+ val textView: TextView = view.findViewById(id) as TextView ?: return
+ textView.setText(text)
+ }
+
+ private fun setVisibilityCommon(view: View?, id: Int, visibility: Int) {
+ val v: View? = view?.findViewById(id)
+ if (v != null) {
+ v.setVisibility(visibility)
+ }
+ return
+ }
+
+ @Override
+ override fun onPause() {
+ mIsPaused = true
+ super.onPause()
+ }
+
+ @Override
+ override fun onResume() {
+ super.onResume()
+ if (mIsDialog) {
+ setDialogSize(getActivity().getResources())
+ applyDialogParams()
+ }
+ mIsPaused = false
+ if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
+ val buttonId = findButtonIdForResponse(mTentativeUserSetResponse)
+ mResponseRadioGroup?.check(buttonId)
+ }
+ }
+
+ @Override
+ override fun eventsChanged() {
+ }
+
+ @get:Override override val supportedEventTypes: Long
+ get() = EventType.EVENTS_CHANGED
+
+ @Override
+ override fun handleEvent(event: EventInfo?) {
+ reloadEvents()
+ }
+
+ fun reloadEvents() {}
+ @Override
+ override fun onClick(view: View?) {
+ }
+
+ private fun setDialogSize(r: Resources) {
+ mDialogWidth = r.getDimension(R.dimen.event_info_dialog_width).toInt()
+ mDialogHeight = r.getDimension(R.dimen.event_info_dialog_height).toInt()
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/EventLoader.java b/src/com/android/calendar/EventLoader.java
deleted file mode 100644
index d34b1c7c..00000000
--- a/src/com/android/calendar/EventLoader.java
+++ /dev/null
@@ -1,286 +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 android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Handler;
-import android.os.Process;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.EventDays;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class EventLoader {
-
- private Context mContext;
- private Handler mHandler = new Handler();
- private AtomicInteger mSequenceNumber = new AtomicInteger();
-
- private LinkedBlockingQueue<LoadRequest> mLoaderQueue;
- private LoaderThread mLoaderThread;
- private ContentResolver mResolver;
-
- private static interface LoadRequest {
- public void processRequest(EventLoader eventLoader);
- public void skipRequest(EventLoader eventLoader);
- }
-
- private static class ShutdownRequest implements LoadRequest {
- public void processRequest(EventLoader eventLoader) {
- }
-
- public void skipRequest(EventLoader eventLoader) {
- }
- }
-
- /**
- *
- * Code for handling requests to get whether days have an event or not
- * and filling in the eventDays array.
- *
- */
- private static class LoadEventDaysRequest implements LoadRequest {
- public int startDay;
- public int numDays;
- public boolean[] eventDays;
- public Runnable uiCallback;
-
- /**
- * The projection used by the EventDays query.
- */
- private static final String[] PROJECTION = {
- CalendarContract.EventDays.STARTDAY, CalendarContract.EventDays.ENDDAY
- };
-
- public LoadEventDaysRequest(int startDay, int numDays, boolean[] eventDays,
- final Runnable uiCallback)
- {
- this.startDay = startDay;
- this.numDays = numDays;
- this.eventDays = eventDays;
- this.uiCallback = uiCallback;
- }
-
- @Override
- public void processRequest(EventLoader eventLoader)
- {
- final Handler handler = eventLoader.mHandler;
- ContentResolver cr = eventLoader.mResolver;
-
- // Clear the event days
- Arrays.fill(eventDays, false);
-
- //query which days have events
- Cursor cursor = EventDays.query(cr, startDay, numDays, PROJECTION);
- try {
- int startDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.STARTDAY);
- int endDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.ENDDAY);
-
- //Set all the days with events to true
- while (cursor.moveToNext()) {
- int firstDay = cursor.getInt(startDayColumnIndex);
- int lastDay = cursor.getInt(endDayColumnIndex);
- //we want the entire range the event occurs, but only within the month
- int firstIndex = Math.max(firstDay - startDay, 0);
- int lastIndex = Math.min(lastDay - startDay, 30);
-
- for(int i = firstIndex; i <= lastIndex; i++) {
- eventDays[i] = true;
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- handler.post(uiCallback);
- }
-
- @Override
- public void skipRequest(EventLoader eventLoader) {
- }
- }
-
- private static class LoadEventsRequest implements LoadRequest {
-
- public int id;
- public int startDay;
- public int numDays;
- public ArrayList<Event> events;
- public Runnable successCallback;
- public Runnable cancelCallback;
-
- public LoadEventsRequest(int id, int startDay, int numDays, ArrayList<Event> events,
- final Runnable successCallback, final Runnable cancelCallback) {
- this.id = id;
- this.startDay = startDay;
- this.numDays = numDays;
- this.events = events;
- this.successCallback = successCallback;
- this.cancelCallback = cancelCallback;
- }
-
- public void processRequest(EventLoader eventLoader) {
- Event.loadEvents(eventLoader.mContext, events, startDay,
- numDays, id, eventLoader.mSequenceNumber);
-
- // Check if we are still the most recent request.
- if (id == eventLoader.mSequenceNumber.get()) {
- eventLoader.mHandler.post(successCallback);
- } else {
- eventLoader.mHandler.post(cancelCallback);
- }
- }
-
- public void skipRequest(EventLoader eventLoader) {
- eventLoader.mHandler.post(cancelCallback);
- }
- }
-
- private static class LoaderThread extends Thread {
- LinkedBlockingQueue<LoadRequest> mQueue;
- EventLoader mEventLoader;
-
- public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) {
- mQueue = queue;
- mEventLoader = eventLoader;
- }
-
- public void shutdown() {
- try {
- mQueue.put(new ShutdownRequest());
- } catch (InterruptedException ex) {
- // The put() method fails with InterruptedException if the
- // queue is full. This should never happen because the queue
- // has no limit.
- Log.e("Cal", "LoaderThread.shutdown() interrupted!");
- }
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- while (true) {
- try {
- // Wait for the next request
- LoadRequest request = mQueue.take();
-
- // If there are a bunch of requests already waiting, then
- // skip all but the most recent request.
- while (!mQueue.isEmpty()) {
- // Let the request know that it was skipped
- request.skipRequest(mEventLoader);
-
- // Skip to the next request
- request = mQueue.take();
- }
-
- if (request instanceof ShutdownRequest) {
- return;
- }
- request.processRequest(mEventLoader);
- } catch (InterruptedException ex) {
- Log.e("Cal", "background LoaderThread interrupted!");
- }
- }
- }
- }
-
- public EventLoader(Context context) {
- mContext = context;
- mLoaderQueue = new LinkedBlockingQueue<LoadRequest>();
- mResolver = context.getContentResolver();
- }
-
- /**
- * Call this from the activity's onResume()
- */
- public void startBackgroundThread() {
- mLoaderThread = new LoaderThread(mLoaderQueue, this);
- mLoaderThread.start();
- }
-
- /**
- * Call this from the activity's onPause()
- */
- public void stopBackgroundThread() {
- mLoaderThread.shutdown();
- }
-
- /**
- * Loads "numDays" days worth of events, starting at start, into events.
- * Posts uiCallback to the {@link Handler} for this view, which will run in the UI thread.
- * Reuses an existing background thread, if events were already being loaded in the background.
- * NOTE: events and uiCallback are not used if an existing background thread gets reused --
- * the ones that were passed in on the call that results in the background thread getting
- * created are used, and the most recent call's worth of data is loaded into events and posted
- * via the uiCallback.
- */
- public void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
- int startDay, final Runnable successCallback, final Runnable cancelCallback) {
-
- // Increment the sequence number for requests. We don't care if the
- // sequence numbers wrap around because we test for equality with the
- // latest one.
- int id = mSequenceNumber.incrementAndGet();
-
- // Send the load request to the background thread
- LoadEventsRequest request = new LoadEventsRequest(id, startDay, numDays,
- events, successCallback, cancelCallback);
-
- try {
- mLoaderQueue.put(request);
- } catch (InterruptedException ex) {
- // The put() method fails with InterruptedException if the
- // queue is full. This should never happen because the queue
- // has no limit.
- Log.e("Cal", "loadEventsInBackground() interrupted!");
- }
- }
-
- /**
- * Sends a request for the days with events to be marked. Loads "numDays"
- * worth of days, starting at start, and fills in eventDays to express which
- * days have events.
- *
- * @param startDay First day to check for events
- * @param numDays Days following the start day to check
- * @param eventDay Whether or not an event exists on that day
- * @param uiCallback What to do when done (log data, redraw screen)
- */
- void loadEventDaysInBackground(int startDay, int numDays, boolean[] eventDays,
- final Runnable uiCallback)
- {
- // Send load request to the background thread
- LoadEventDaysRequest request = new LoadEventDaysRequest(startDay, numDays,
- eventDays, uiCallback);
- try {
- mLoaderQueue.put(request);
- } catch (InterruptedException ex) {
- // The put() method fails with InterruptedException if the
- // queue is full. This should never happen because the queue
- // has no limit.
- Log.e("Cal", "loadEventDaysInBackground() interrupted!");
- }
- }
-}
diff --git a/src/com/android/calendar/EventLoader.kt b/src/com/android/calendar/EventLoader.kt
new file mode 100644
index 00000000..a05e8a2e
--- /dev/null
+++ b/src/com/android/calendar/EventLoader.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.os.Handler
+import android.os.Process
+import android.provider.CalendarContract
+import android.provider.CalendarContract.EventDays
+import android.util.Log
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.atomic.AtomicInteger
+
+class EventLoader(context: Context) {
+ private val mContext: Context
+ private val mHandler: Handler = Handler()
+ private val mSequenceNumber: AtomicInteger? = AtomicInteger()
+ private val mLoaderQueue: LinkedBlockingQueue<LoadRequest>
+ private var mLoaderThread: LoaderThread? = null
+ private val mResolver: ContentResolver
+
+ private interface LoadRequest {
+ fun processRequest(eventLoader: EventLoader?)
+ fun skipRequest(eventLoader: EventLoader?)
+ }
+
+ private class ShutdownRequest : LoadRequest {
+ override fun processRequest(eventLoader: EventLoader?) {}
+ override fun skipRequest(eventLoader: EventLoader?) {}
+ }
+
+ /**
+ *
+ * Code for handling requests to get whether days have an event or not
+ * and filling in the eventDays array.
+ *
+ */
+ private class LoadEventDaysRequest(
+ var startDay: Int,
+ var numDays: Int,
+ var eventDays: BooleanArray,
+ uiCallback: Runnable
+ ) : LoadRequest {
+ var uiCallback: Runnable
+ @Override
+ override fun processRequest(eventLoader: EventLoader?) {
+ val handler: Handler? = eventLoader?.mHandler
+ val cr: ContentResolver? = eventLoader?.mResolver
+
+ // Clear the event days
+ Arrays.fill(eventDays, false)
+
+ // query which days have events
+ val cursor: Cursor = EventDays.query(cr, startDay, numDays, PROJECTION)
+ try {
+ val startDayColumnIndex: Int = cursor.getColumnIndexOrThrow(EventDays.STARTDAY)
+ val endDayColumnIndex: Int = cursor.getColumnIndexOrThrow(EventDays.ENDDAY)
+
+ // Set all the days with events to true
+ while (cursor.moveToNext()) {
+ val firstDay: Int = cursor.getInt(startDayColumnIndex)
+ val lastDay: Int = cursor.getInt(endDayColumnIndex)
+ // we want the entire range the event occurs, but only within the month
+ val firstIndex: Int = Math.max(firstDay - startDay, 0)
+ val lastIndex: Int = Math.min(lastDay - startDay, 30)
+ for (i in firstIndex..lastIndex) {
+ eventDays[i] = true
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close()
+ }
+ }
+ handler?.post(uiCallback)
+ }
+
+ @Override
+ override fun skipRequest(eventLoader: EventLoader?) {
+ }
+
+ companion object {
+ /**
+ * The projection used by the EventDays query.
+ */
+ private val PROJECTION = arrayOf<String>(
+ CalendarContract.EventDays.STARTDAY, CalendarContract.EventDays.ENDDAY
+ )
+ }
+
+ init {
+ this.uiCallback = uiCallback
+ }
+ }
+
+ private class LoadEventsRequest(
+ var id: Int,
+ var startDay: Int,
+ var numDays: Int,
+ events: ArrayList<Event?>,
+ successCallback: Runnable,
+ cancelCallback: Runnable
+ ) : LoadRequest {
+ var events: ArrayList<Event?>
+ var successCallback: Runnable
+ var cancelCallback: Runnable
+ @Override
+ override fun processRequest(eventLoader: EventLoader?) {
+ Event.loadEvents(eventLoader?.mContext, events, startDay,
+ numDays, id, eventLoader?.mSequenceNumber)
+
+ // Check if we are still the most recent request.
+ if (id == eventLoader?.mSequenceNumber?.get()) {
+ eventLoader?.mHandler?.post(successCallback)
+ } else {
+ eventLoader?.mHandler?.post(cancelCallback)
+ }
+ }
+
+ @Override
+ override fun skipRequest(eventLoader: EventLoader?) {
+ eventLoader?.mHandler?.post(cancelCallback)
+ }
+
+ init {
+ this.events = events
+ this.successCallback = successCallback
+ this.cancelCallback = cancelCallback
+ }
+ }
+
+ private class LoaderThread(
+ queue: LinkedBlockingQueue<LoadRequest>,
+ eventLoader: EventLoader
+ ) : Thread() {
+ var mQueue: LinkedBlockingQueue<LoadRequest>
+ var mEventLoader: EventLoader
+ fun shutdown() {
+ try {
+ mQueue.put(ShutdownRequest())
+ } catch (ex: InterruptedException) {
+ // The put() method fails with InterruptedException if the
+ // queue is full. This should never happen because the queue
+ // has no limit.
+ Log.e("Cal", "LoaderThread.shutdown() interrupted!")
+ }
+ }
+
+ @Override
+ override fun run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
+ while (true) {
+ try {
+ // Wait for the next request
+ var request: LoadRequest = mQueue.take()
+
+ // If there are a bunch of requests already waiting, then
+ // skip all but the most recent request.
+ while (!mQueue.isEmpty()) {
+ // Let the request know that it was skipped
+ request.skipRequest(mEventLoader)
+
+ // Skip to the next request
+ request = mQueue.take()
+ }
+ if (request is ShutdownRequest) {
+ return
+ }
+ request.processRequest(mEventLoader)
+ } catch (ex: InterruptedException) {
+ Log.e("Cal", "background LoaderThread interrupted!")
+ }
+ }
+ }
+
+ init {
+ mQueue = queue
+ mEventLoader = eventLoader
+ }
+ }
+
+ /**
+ * Call this from the activity's onResume()
+ */
+ fun startBackgroundThread() {
+ mLoaderThread = LoaderThread(mLoaderQueue, this)
+ mLoaderThread?.start()
+ }
+
+ /**
+ * Call this from the activity's onPause()
+ */
+ fun stopBackgroundThread() {
+ mLoaderThread!!.shutdown()
+ }
+
+ /**
+ * Loads "numDays" days worth of events, starting at start, into events.
+ * Posts uiCallback to the [Handler] for this view, which will run in the UI thread.
+ * Reuses an existing background thread, if events were already being loaded in the background.
+ * NOTE: events and uiCallback are not used if an existing background thread gets reused --
+ * the ones that were passed in on the call that results in the background thread getting
+ * created are used, and the most recent call's worth of data is loaded into events and posted
+ * via the uiCallback.
+ */
+ fun loadEventsInBackground(
+ numDays: Int,
+ events: ArrayList<Event?>,
+ startDay: Int,
+ successCallback: Runnable,
+ cancelCallback: Runnable
+ ) {
+
+ // Increment the sequence number for requests. We don't care if the
+ // sequence numbers wrap around because we test for equality with the
+ // latest one.
+ val id: Int = mSequenceNumber?.incrementAndGet() as Int
+
+ // Send the load request to the background thread
+ val request = LoadEventsRequest(id, startDay, numDays,
+ events, successCallback, cancelCallback)
+ try {
+ mLoaderQueue.put(request)
+ } catch (ex: InterruptedException) {
+ // The put() method fails with InterruptedException if the
+ // queue is full. This should never happen because the queue
+ // has no limit.
+ Log.e("Cal", "loadEventsInBackground() interrupted!")
+ }
+ }
+
+ /**
+ * Sends a request for the days with events to be marked. Loads "numDays"
+ * worth of days, starting at start, and fills in eventDays to express which
+ * days have events.
+ *
+ * @param startDay First day to check for events
+ * @param numDays Days following the start day to check
+ * @param eventDay Whether or not an event exists on that day
+ * @param uiCallback What to do when done (log data, redraw screen)
+ */
+ fun loadEventDaysInBackground(
+ startDay: Int,
+ numDays: Int,
+ eventDays: BooleanArray,
+ uiCallback: Runnable
+ ) {
+ // Send load request to the background thread
+ val request = LoadEventDaysRequest(startDay, numDays,
+ eventDays, uiCallback)
+ try {
+ mLoaderQueue.put(request)
+ } catch (ex: InterruptedException) {
+ // The put() method fails with InterruptedException if the
+ // queue is full. This should never happen because the queue
+ // has no limit.
+ Log.e("Cal", "loadEventDaysInBackground() interrupted!")
+ }
+ }
+
+ init {
+ mContext = context
+ mLoaderQueue = LinkedBlockingQueue<LoadRequest>()
+ mResolver = context.getContentResolver()
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/GeneralPreferences.java b/src/com/android/calendar/GeneralPreferences.java
deleted file mode 100644
index a42f07e3..00000000
--- a/src/com/android/calendar/GeneralPreferences.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.backup.BackupManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Vibrator;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.preference.RingtonePreference;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.CalendarCache;
-import android.provider.SearchRecentSuggestions;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.widget.Toast;
-
-import com.android.calendar.alerts.AlertReceiver;
-import com.android.timezonepicker.TimeZoneInfo;
-import com.android.timezonepicker.TimeZonePickerDialog;
-import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener;
-import com.android.timezonepicker.TimeZonePickerUtils;
-
-public class GeneralPreferences extends PreferenceFragment implements
- OnSharedPreferenceChangeListener, OnPreferenceChangeListener, OnTimeZoneSetListener {
- // The name of the shared preferences file. This name must be maintained for historical
- // reasons, as it's what PreferenceManager assigned the first time the file was created.
- static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
- static final String SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup";
-
- private static final String FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker";
-
- // Preference keys
- public static final String KEY_HIDE_DECLINED = "preferences_hide_declined";
- public static final String KEY_WEEK_START_DAY = "preferences_week_start_day";
- public static final String KEY_SHOW_WEEK_NUM = "preferences_show_week_num";
- public static final String KEY_DAYS_PER_WEEK = "preferences_days_per_week";
- public static final String KEY_SKIP_SETUP = "preferences_skip_setup";
-
- public static final String KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history";
-
- public static final String KEY_ALERTS_CATEGORY = "preferences_alerts_category";
- public static final String KEY_ALERTS = "preferences_alerts";
- public static final String KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate";
- public static final String KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone";
- public static final String KEY_ALERTS_POPUP = "preferences_alerts_popup";
-
- public static final String KEY_SHOW_CONTROLS = "preferences_show_controls";
-
- public static final String KEY_DEFAULT_REMINDER = "preferences_default_reminder";
- public static final int NO_REMINDER = -1;
- public static final String NO_REMINDER_STRING = "-1";
- public static final int REMINDER_DEFAULT_TIME = 10; // in minutes
-
- public static final String KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height";
- public static final String KEY_VERSION = "preferences_version";
-
- /** Key to SharePreference for default view (CalendarController.ViewType) */
- public static final String KEY_START_VIEW = "preferred_startView";
- /**
- * Key to SharePreference for default detail view (CalendarController.ViewType)
- * Typically used by widget
- */
- public static final String KEY_DETAILED_VIEW = "preferred_detailedView";
- public static final String KEY_DEFAULT_CALENDAR = "preference_defaultCalendar";
-
- // These must be in sync with the array preferences_week_start_day_values
- public static final String WEEK_START_DEFAULT = "-1";
- public static final String WEEK_START_SATURDAY = "7";
- public static final String WEEK_START_SUNDAY = "1";
- public static final String WEEK_START_MONDAY = "2";
-
- // These keys are kept to enable migrating users from previous versions
- private static final String KEY_ALERTS_TYPE = "preferences_alerts_type";
- private static final String ALERT_TYPE_ALERTS = "0";
- private static final String ALERT_TYPE_STATUS_BAR = "1";
- private static final String ALERT_TYPE_OFF = "2";
- static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
- static final String KEY_HOME_TZ = "preferences_home_tz";
-
- // Default preference values
- public static final int DEFAULT_START_VIEW = CalendarController.ViewType.WEEK;
- public static final int DEFAULT_DETAILED_VIEW = CalendarController.ViewType.DAY;
- public static final boolean DEFAULT_SHOW_WEEK_NUM = false;
- // This should match the XML file.
- public static final String DEFAULT_RINGTONE = "content://settings/system/notification_sound";
-
- CheckBoxPreference mAlert;
- CheckBoxPreference mVibrate;
- CheckBoxPreference mPopup;
- CheckBoxPreference mUseHomeTZ;
- CheckBoxPreference mHideDeclined;
- Preference mHomeTZ;
- TimeZonePickerUtils mTzPickerUtils;
- ListPreference mWeekStart;
- ListPreference mDefaultReminder;
-
- private String mTimeZoneId;
-
- /** Return a properly configured SharedPreferences instance */
- public static SharedPreferences getSharedPreferences(Context context) {
- return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- }
-
- /** Set the default shared preferences in the proper context */
- public static void setDefaultValues(Context context) {
- PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
- R.xml.general_preferences, false);
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Activity activity = getActivity();
-
- // Make sure to always use the same preferences file regardless of the package name
- // we're running under
- final PreferenceManager preferenceManager = getPreferenceManager();
- final SharedPreferences sharedPreferences = getSharedPreferences(activity);
- preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME);
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.general_preferences);
-
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- mAlert = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS);
- mVibrate = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE);
- Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
- if (vibrator == null || !vibrator.hasVibrator()) {
- PreferenceCategory mAlertGroup = (PreferenceCategory) preferenceScreen
- .findPreference(KEY_ALERTS_CATEGORY);
- mAlertGroup.removePreference(mVibrate);
- }
-
- mPopup = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_POPUP);
- mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED);
- mHideDeclined = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HIDE_DECLINED);
- mWeekStart = (ListPreference) preferenceScreen.findPreference(KEY_WEEK_START_DAY);
- mDefaultReminder = (ListPreference) preferenceScreen.findPreference(KEY_DEFAULT_REMINDER);
- mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ);
- mWeekStart.setSummary(mWeekStart.getEntry());
- mDefaultReminder.setSummary(mDefaultReminder.getEntry());
-
- // This triggers an asynchronous call to the provider to refresh the data in shared pref
- mTimeZoneId = Utils.getTimeZone(activity, null);
-
- SharedPreferences prefs = CalendarUtils.getSharedPreferences(activity,
- Utils.SHARED_PREFS_NAME);
-
- // Utils.getTimeZone will return the currentTimeZone instead of the one
- // in the shared_pref if home time zone is disabled. So if home tz is
- // off, we will explicitly read it.
- if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) {
- mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
- }
-
- mHomeTZ.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- showTimezoneDialog();
- return true;
- }
- });
-
- if (mTzPickerUtils == null) {
- mTzPickerUtils = new TimeZonePickerUtils(getActivity());
- }
- CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(getActivity(), mTimeZoneId,
- System.currentTimeMillis(), false);
- mHomeTZ.setSummary(timezoneName != null ? timezoneName : mTimeZoneId);
-
- TimeZonePickerDialog tzpd = (TimeZonePickerDialog) activity.getFragmentManager()
- .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
- if (tzpd != null) {
- tzpd.setOnTimeZoneSetListener(this);
- }
-
- migrateOldPreferences(sharedPreferences);
-
- updateChildPreferences();
- }
-
- private void showTimezoneDialog() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- Bundle b = new Bundle();
- b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis());
- b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null));
-
- FragmentManager fm = getActivity().getFragmentManager();
- TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm
- .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
- if (tzpd != null) {
- tzpd.dismiss();
- }
- tzpd = new TimeZonePickerDialog();
- tzpd.setArguments(b);
- tzpd.setOnTimeZoneSetListener(this);
- tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- setPreferenceListeners(this);
- }
-
- /**
- * Sets up all the preference change listeners to use the specified
- * listener.
- */
- private void setPreferenceListeners(OnPreferenceChangeListener listener) {
- mUseHomeTZ.setOnPreferenceChangeListener(listener);
- mHomeTZ.setOnPreferenceChangeListener(listener);
- mWeekStart.setOnPreferenceChangeListener(listener);
- mDefaultReminder.setOnPreferenceChangeListener(listener);
- mHideDeclined.setOnPreferenceChangeListener(listener);
- mVibrate.setOnPreferenceChangeListener(listener);
- }
-
- @Override
- public void onStop() {
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- setPreferenceListeners(null);
- super.onStop();
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- Activity a = getActivity();
- if (key.equals(KEY_ALERTS)) {
- updateChildPreferences();
- if (a != null) {
- Intent intent = new Intent();
- intent.setClass(a, AlertReceiver.class);
- if (mAlert.isChecked()) {
- intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS);
- } else {
- intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION);
- }
- a.sendBroadcast(intent);
- }
- }
- if (a != null) {
- BackupManager.dataChanged(a.getPackageName());
- }
- }
-
- /**
- * Handles time zone preference changes
- */
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- String tz;
- final Activity activity = getActivity();
- if (preference == mUseHomeTZ) {
- if ((Boolean)newValue) {
- tz = mTimeZoneId;
- } else {
- tz = CalendarCache.TIMEZONE_TYPE_AUTO;
- }
- Utils.setTimeZone(activity, tz);
- return true;
- } else if (preference == mHideDeclined) {
- mHideDeclined.setChecked((Boolean) newValue);
- Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(activity));
- intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE);
- activity.sendBroadcast(intent);
- return true;
- } else if (preference == mWeekStart) {
- mWeekStart.setValue((String) newValue);
- mWeekStart.setSummary(mWeekStart.getEntry());
- } else if (preference == mDefaultReminder) {
- mDefaultReminder.setValue((String) newValue);
- mDefaultReminder.setSummary(mDefaultReminder.getEntry());
- } else if (preference == mVibrate) {
- mVibrate.setChecked((Boolean) newValue);
- return true;
- } else {
- return true;
- }
- return false;
- }
-
- public String getRingtoneTitleFromUri(Context context, String uri) {
- if (TextUtils.isEmpty(uri)) {
- return null;
- }
-
- Ringtone ring = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri));
- if (ring != null) {
- return ring.getTitle(context);
- }
- return null;
- }
-
- /**
- * If necessary, upgrades previous versions of preferences to the current
- * set of keys and values.
- * @param prefs the preferences to upgrade
- */
- private void migrateOldPreferences(SharedPreferences prefs) {
- // If needed, migrate vibration setting from a previous version
-
- mVibrate.setChecked(Utils.getDefaultVibrate(getActivity(), prefs));
-
- // If needed, migrate the old alerts type settin
- if (!prefs.contains(KEY_ALERTS) && prefs.contains(KEY_ALERTS_TYPE)) {
- String type = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR);
- if (type.equals(ALERT_TYPE_OFF)) {
- mAlert.setChecked(false);
- mPopup.setChecked(false);
- mPopup.setEnabled(false);
- } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
- mAlert.setChecked(true);
- mPopup.setChecked(false);
- mPopup.setEnabled(true);
- } else if (type.equals(ALERT_TYPE_ALERTS)) {
- mAlert.setChecked(true);
- mPopup.setChecked(true);
- mPopup.setEnabled(true);
- }
- // clear out the old setting
- prefs.edit().remove(KEY_ALERTS_TYPE).commit();
- }
- }
-
- /**
- * Keeps the dependent settings in sync with the parent preference, so for
- * example, when notifications are turned off, we disable the preferences
- * for configuring the exact notification behavior.
- */
- private void updateChildPreferences() {
- if (mAlert.isChecked()) {
- mVibrate.setEnabled(true);
- mPopup.setEnabled(true);
- } else {
- mVibrate.setEnabled(false);
- mPopup.setEnabled(false);
- }
- }
-
-
- @Override
- public boolean onPreferenceTreeClick(
- PreferenceScreen preferenceScreen, Preference preference) {
- final String key = preference.getKey();
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- @Override
- public void onTimeZoneSet(TimeZoneInfo tzi) {
- if (mTzPickerUtils == null) {
- mTzPickerUtils = new TimeZonePickerUtils(getActivity());
- }
-
- final CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(
- getActivity(), tzi.mTzId, System.currentTimeMillis(), false);
- mHomeTZ.setSummary(timezoneName);
- Utils.setTimeZone(getActivity(), tzi.mTzId);
- }
-}
diff --git a/src/com/android/calendar/GeneralPreferences.kt b/src/com/android/calendar/GeneralPreferences.kt
new file mode 100644
index 00000000..dd4c9550
--- /dev/null
+++ b/src/com/android/calendar/GeneralPreferences.kt
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.FragmentManager
+import android.app.backup.BackupManager
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.media.Ringtone
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.Vibrator
+import android.preference.CheckBoxPreference
+import android.preference.ListPreference
+import android.preference.Preference
+import android.preference.Preference.OnPreferenceChangeListener
+import android.preference.Preference.OnPreferenceClickListener
+import android.preference.PreferenceCategory
+import android.preference.PreferenceFragment
+import android.preference.PreferenceManager
+import android.preference.PreferenceScreen
+import android.provider.CalendarContract
+import android.provider.CalendarContract.CalendarCache
+import android.text.TextUtils
+import android.text.format.Time
+import com.android.calendar.alerts.AlertReceiver
+import com.android.timezonepicker.TimeZoneInfo
+import com.android.timezonepicker.TimeZonePickerDialog
+import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener
+import com.android.timezonepicker.TimeZonePickerUtils
+
+class GeneralPreferences : PreferenceFragment(), OnSharedPreferenceChangeListener,
+ OnPreferenceChangeListener, OnTimeZoneSetListener {
+ var mAlert: CheckBoxPreference? = null
+ var mVibrate: CheckBoxPreference? = null
+ var mPopup: CheckBoxPreference? = null
+ var mUseHomeTZ: CheckBoxPreference? = null
+ var mHideDeclined: CheckBoxPreference? = null
+ var mHomeTZ: Preference? = null
+ var mTzPickerUtils: TimeZonePickerUtils? = null
+ var mWeekStart: ListPreference? = null
+ var mDefaultReminder: ListPreference? = null
+ private var mTimeZoneId: String? = null
+
+ @Override
+ override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ val activity: Activity = getActivity()
+
+ // Make sure to always use the same preferences file regardless of the package name
+ // we're running under
+ val preferenceManager: PreferenceManager = getPreferenceManager()
+ val sharedPreferences: SharedPreferences? = getSharedPreferences(activity)
+ preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME)
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.general_preferences)
+ val preferenceScreen: PreferenceScreen = getPreferenceScreen()
+ mAlert = preferenceScreen.findPreference(KEY_ALERTS) as CheckBoxPreference
+ mVibrate = preferenceScreen.findPreference(KEY_ALERTS_VIBRATE) as CheckBoxPreference
+ val vibrator: Vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ if (vibrator == null || !vibrator.hasVibrator()) {
+ val mAlertGroup: PreferenceCategory = preferenceScreen
+ .findPreference(KEY_ALERTS_CATEGORY) as PreferenceCategory
+ mAlertGroup.removePreference(mVibrate)
+ }
+ mPopup = preferenceScreen.findPreference(KEY_ALERTS_POPUP) as CheckBoxPreference
+ mUseHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED) as CheckBoxPreference
+ mHideDeclined = preferenceScreen.findPreference(KEY_HIDE_DECLINED) as CheckBoxPreference
+ mWeekStart = preferenceScreen.findPreference(KEY_WEEK_START_DAY) as ListPreference
+ mDefaultReminder = preferenceScreen.findPreference(KEY_DEFAULT_REMINDER) as ListPreference
+ mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ)
+ mWeekStart?.setSummary(mWeekStart?.getEntry())
+ mDefaultReminder?.setSummary(mDefaultReminder?.getEntry())
+
+ // This triggers an asynchronous call to the provider to refresh the data in shared pref
+ mTimeZoneId = Utils.getTimeZone(activity, null)
+ val prefs: SharedPreferences = CalendarUtils.getSharedPreferences(activity,
+ Utils.SHARED_PREFS_NAME)
+
+ // Utils.getTimeZone will return the currentTimeZone instead of the one
+ // in the shared_pref if home time zone is disabled. So if home tz is
+ // off, we will explicitly read it.
+ if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) {
+ mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone())
+ }
+
+ mHomeTZ?.setOnPreferenceClickListener(object : Preference.OnPreferenceClickListener {
+ @Override
+ override fun onPreferenceClick(preference: Preference?): Boolean {
+ showTimezoneDialog()
+ return true
+ }
+ })
+
+ if (mTzPickerUtils == null) {
+ mTzPickerUtils = TimeZonePickerUtils(getActivity())
+ }
+ val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(getActivity(),
+ mTimeZoneId, System.currentTimeMillis(), false)
+ mHomeTZ?.setSummary(timezoneName ?: mTimeZoneId)
+ val tzpd: TimeZonePickerDialog = activity.getFragmentManager()
+ .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog
+ if (tzpd != null) {
+ tzpd.setOnTimeZoneSetListener(this)
+ }
+ migrateOldPreferences(sharedPreferences)
+ updateChildPreferences()
+ }
+
+ private fun showTimezoneDialog() {
+ val activity: Activity = getActivity() ?: return
+ val b = Bundle()
+ b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis())
+ b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null))
+ val fm: FragmentManager = getActivity().getFragmentManager()
+ var tzpd: TimeZonePickerDialog? = fm
+ .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog
+ if (tzpd != null) {
+ tzpd.dismiss()
+ }
+ tzpd = TimeZonePickerDialog()
+ tzpd.setArguments(b)
+ tzpd.setOnTimeZoneSetListener(this)
+ tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER)
+ }
+
+ @Override
+ override fun onStart() {
+ super.onStart()
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this)
+ setPreferenceListeners(this)
+ }
+
+ /**
+ * Sets up all the preference change listeners to use the specified
+ * listener.
+ */
+ private fun setPreferenceListeners(listener: OnPreferenceChangeListener?) {
+ mUseHomeTZ?.setOnPreferenceChangeListener(listener)
+ mHomeTZ?.setOnPreferenceChangeListener(listener)
+ mWeekStart?.setOnPreferenceChangeListener(listener)
+ mDefaultReminder?.setOnPreferenceChangeListener(listener)
+ mHideDeclined?.setOnPreferenceChangeListener(listener)
+ mVibrate?.setOnPreferenceChangeListener(listener)
+ }
+
+ @Override
+ override fun onStop() {
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this)
+ setPreferenceListeners(null)
+ super.onStop()
+ }
+
+ @Override
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String) {
+ val a: Activity = getActivity()
+ if (key.equals(KEY_ALERTS)) {
+ updateChildPreferences()
+ if (a != null) {
+ val intent = Intent()
+ intent.setClass(a, AlertReceiver::class.java)
+ if (mAlert?.isChecked() ?: false) {
+ intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)
+ } else {
+ intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION)
+ }
+ a.sendBroadcast(intent)
+ }
+ }
+ if (a != null) {
+ BackupManager.dataChanged(a.getPackageName())
+ }
+ }
+
+ /**
+ * Handles time zone preference changes
+ */
+ @Override
+ override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
+ val tz: String?
+ val activity: Activity = getActivity()
+ if (preference === mUseHomeTZ) {
+ tz = if (newValue != null) {
+ mTimeZoneId
+ } else {
+ CalendarCache.TIMEZONE_TYPE_AUTO
+ }
+ Utils.setTimeZone(activity, tz)
+ return true
+ } else if (preference === mHideDeclined) {
+ mHideDeclined?.setChecked(newValue as Boolean)
+ val intent = Intent(Utils.getWidgetScheduledUpdateAction(activity))
+ intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
+ activity.sendBroadcast(intent)
+ return true
+ } else if (preference === mWeekStart) {
+ mWeekStart?.setValue(newValue as String)
+ mWeekStart?.setSummary(mWeekStart?.getEntry())
+ } else if (preference === mDefaultReminder) {
+ mDefaultReminder?.setValue(newValue as String)
+ mDefaultReminder?.setSummary(mDefaultReminder?.getEntry())
+ } else if (preference === mVibrate) {
+ mVibrate?.setChecked(newValue as Boolean)
+ return true
+ } else {
+ return true
+ }
+ return false
+ }
+
+ fun getRingtoneTitleFromUri(context: Context?, uri: String?): String? {
+ if (TextUtils.isEmpty(uri)) {
+ return null
+ }
+ val ring: Ringtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri))
+ return if (ring != null) {
+ ring.getTitle(context)
+ } else null
+ }
+
+ /**
+ * If necessary, upgrades previous versions of preferences to the current
+ * set of keys and values.
+ * @param prefs the preferences to upgrade
+ */
+ private fun migrateOldPreferences(prefs: SharedPreferences?) {
+ // If needed, migrate vibration setting from a previous version
+ mVibrate?.setChecked(Utils.getDefaultVibrate(getActivity(), prefs))
+
+ // If needed, migrate the old alerts type settin
+ if (prefs?.contains(KEY_ALERTS) == false && prefs?.contains(KEY_ALERTS_TYPE) == true) {
+ val type: String? = prefs?.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR)
+ if (type.equals(ALERT_TYPE_OFF)) {
+ mAlert?.setChecked(false)
+ mPopup?.setChecked(false)
+ mPopup?.setEnabled(false)
+ } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
+ mAlert?.setChecked(true)
+ mPopup?.setChecked(false)
+ mPopup?.setEnabled(true)
+ } else if (type.equals(ALERT_TYPE_ALERTS)) {
+ mAlert?.setChecked(true)
+ mPopup?.setChecked(true)
+ mPopup?.setEnabled(true)
+ }
+ // clear out the old setting
+ prefs?.edit().remove(KEY_ALERTS_TYPE).commit()
+ }
+ }
+
+ /**
+ * Keeps the dependent settings in sync with the parent preference, so for
+ * example, when notifications are turned off, we disable the preferences
+ * for configuring the exact notification behavior.
+ */
+ private fun updateChildPreferences() {
+ if (mAlert?.isChecked() ?: false) {
+ mVibrate?.setEnabled(true)
+ mPopup?.setEnabled(true)
+ } else {
+ mVibrate?.setEnabled(false)
+ mPopup?.setEnabled(false)
+ }
+ }
+
+ @Override
+ override fun onPreferenceTreeClick(
+ preferenceScreen: PreferenceScreen?,
+ preference: Preference
+ ): Boolean {
+ val key: String = preference.getKey()
+ return super.onPreferenceTreeClick(preferenceScreen, preference)
+ }
+
+ @Override
+ override fun onTimeZoneSet(tzi: TimeZoneInfo) {
+ if (mTzPickerUtils == null) {
+ mTzPickerUtils = TimeZonePickerUtils(getActivity())
+ }
+ val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(
+ getActivity(), tzi.mTzId, System.currentTimeMillis(), false)
+ mHomeTZ?.setSummary(timezoneName)
+ Utils.setTimeZone(getActivity(), tzi.mTzId)
+ }
+
+ companion object {
+ // The name of the shared preferences file. This name must be maintained for historical
+ // reasons, as it's what PreferenceManager assigned the first time the file was created.
+ const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+ const val SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup"
+ private const val FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker"
+
+ // Preference keys
+ const val KEY_HIDE_DECLINED = "preferences_hide_declined"
+ const val KEY_WEEK_START_DAY = "preferences_week_start_day"
+ const val KEY_SHOW_WEEK_NUM = "preferences_show_week_num"
+ const val KEY_DAYS_PER_WEEK = "preferences_days_per_week"
+ const val KEY_SKIP_SETUP = "preferences_skip_setup"
+ const val KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history"
+ const val KEY_ALERTS_CATEGORY = "preferences_alerts_category"
+ const val KEY_ALERTS = "preferences_alerts"
+ const val KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate"
+ const val KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone"
+ const val KEY_ALERTS_POPUP = "preferences_alerts_popup"
+ const val KEY_SHOW_CONTROLS = "preferences_show_controls"
+ const val KEY_DEFAULT_REMINDER = "preferences_default_reminder"
+ const val NO_REMINDER = -1
+ const val NO_REMINDER_STRING = "-1"
+ const val REMINDER_DEFAULT_TIME = 10 // in minutes
+ const val KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height"
+ const val KEY_VERSION = "preferences_version"
+
+ /** Key to SharePreference for default view (CalendarController.ViewType) */
+ const val KEY_START_VIEW = "preferred_startView"
+
+ /**
+ * Key to SharePreference for default detail view (CalendarController.ViewType)
+ * Typically used by widget
+ */
+ const val KEY_DETAILED_VIEW = "preferred_detailedView"
+ const val KEY_DEFAULT_CALENDAR = "preference_defaultCalendar"
+
+ // These must be in sync with the array preferences_week_start_day_values
+ const val WEEK_START_DEFAULT = "-1"
+ const val WEEK_START_SATURDAY = "7"
+ const val WEEK_START_SUNDAY = "1"
+ const val WEEK_START_MONDAY = "2"
+
+ // These keys are kept to enable migrating users from previous versions
+ private const val KEY_ALERTS_TYPE = "preferences_alerts_type"
+ private const val ALERT_TYPE_ALERTS = "0"
+ private const val ALERT_TYPE_STATUS_BAR = "1"
+ private const val ALERT_TYPE_OFF = "2"
+ const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled"
+ const val KEY_HOME_TZ = "preferences_home_tz"
+
+ // Default preference values
+ const val DEFAULT_START_VIEW: Int = CalendarController.ViewType.WEEK
+ const val DEFAULT_DETAILED_VIEW: Int = CalendarController.ViewType.DAY
+ const val DEFAULT_SHOW_WEEK_NUM = false
+
+ // This should match the XML file.
+ const val DEFAULT_RINGTONE = "content://settings/system/notification_sound"
+
+ /** Return a properly configured SharedPreferences instance */
+ @JvmStatic
+ fun getSharedPreferences(context: Context?): SharedPreferences? {
+ return context?.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
+ }
+
+ /** Set the default shared preferences in the proper context */
+ @JvmStatic
+ fun setDefaultValues(context: Context?) {
+ PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
+ R.xml.general_preferences, false)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java b/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt
index 3970115b..d2fe77f9 100644
--- a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
+++ b/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt
@@ -1,6 +1,5 @@
/*
-**
-** Copyright 2009, The Android Open Source Project
+** Copyright 2021, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -14,28 +13,25 @@
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** limitations under the License.
*/
+package com.android.calendar
-package com.android.calendar;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class GoogleCalendarUriIntentFilter extends Activity {
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
- Intent intent = getIntent();
+class GoogleCalendarUriIntentFilter : Activity() {
+ protected override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ val intent: Intent = getIntent()
if (intent != null) {
// Pass it on to the next Activity.
try {
- startNextMatchingActivity(intent);
- } catch (ActivityNotFoundException ex) {
+ startNextMatchingActivity(intent)
+ } catch (ex: ActivityNotFoundException) {
// no browser installed? Just drop it.
}
}
- finish();
+ finish()
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/calendar/MultiStateButton.java b/src/com/android/calendar/MultiStateButton.java
deleted file mode 100644
index 8034b28e..00000000
--- a/src/com/android/calendar/MultiStateButton.java
+++ /dev/null
@@ -1,192 +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.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.widget.Button;
-
-/**
- * <p>
- * A button with more than two states. When the button is pressed
- * or clicked, the state transitions automatically.
- * </p>
- *
- * <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link R.styleable#MultiStateButton
- * MultiStateButton Attributes}, {@link android.R.styleable#Button Button
- * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
- * android.R.styleable#View View Attributes}
- * </p>
- */
-
-public class MultiStateButton extends Button {
- //The current state for this button, ranging from 0 to maxState-1
- private int mState;
- //The maximum number of states allowed for this button.
- private int mMaxStates;
- //The currently displaying resource ID. This gets set to a default on creation and remains
- //on the last set if the resources get set to null.
- private int mButtonResource;
- //A list of all drawable resources used by this button in the order it uses them.
- private int[] mButtonResources;
- private Drawable mButtonDrawable;
-
- public MultiStateButton(Context context) {
- this(context, null);
- }
-
- public MultiStateButton(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MultiStateButton(Context context, AttributeSet attrs, int defStyle) {
- //Currently using the standard buttonStyle, will update when new resources are added.
- super(context, attrs, defStyle);
- mMaxStates = 1;
- mState = 0;
- //TODO add a more generic default button
- mButtonResources = new int[] { R.drawable.widget_show };
- setButtonDrawable(mButtonResources[mState]);
- }
-
- @Override
- public boolean performClick() {
- /* When clicked, toggle the state */
- transitionState();
- return super.performClick();
- }
-
- public void transitionState() {
- mState = (mState + 1) % mMaxStates;
- setButtonDrawable(mButtonResources[mState]);
- }
-
- /**
- * Allows for a new set of drawable resource ids to be set.
- *
- * This sets the maximum states allowed to the length of the resources array. It will also
- * set the current state to the maximum allowed if it's greater than the new max.
- */
- public void setButtonResources(int[] resources) throws IllegalArgumentException {
- if(resources == null) {
- throw new IllegalArgumentException("Button resources cannot be null");
- }
- mMaxStates = resources.length;
- if(mState >= mMaxStates) {
- mState = mMaxStates - 1;
- }
- mButtonResources = resources;
- }
-
- /**
- * Attempts to set the state. Returns true if successful, false otherwise.
- */
- public boolean setState(int state){
- if(state >= mMaxStates || state < 0) {
- //When moved out of Calendar the tag should be changed.
- Log.w("Cal", "MultiStateButton state set to value greater than maxState or < 0");
- return false;
- }
- mState = state;
- setButtonDrawable(mButtonResources[mState]);
- return true;
- }
-
- public int getState() {
- return mState;
- }
-
- /**
- * Set the background to a given Drawable, identified by its resource id.
- *
- * @param resid the resource id of the drawable to use as the background
- */
- public void setButtonDrawable(int resid) {
- if (resid != 0 && resid == mButtonResource) {
- return;
- }
-
- mButtonResource = resid;
-
- Drawable d = null;
- if (mButtonResource != 0) {
- d = getResources().getDrawable(mButtonResource);
- }
- setButtonDrawable(d);
- }
-
- /**
- * Set the background to a given Drawable
- *
- * @param d The Drawable to use as the background
- */
- public void setButtonDrawable(Drawable d) {
- if (d != null) {
- if (mButtonDrawable != null) {
- mButtonDrawable.setCallback(null);
- unscheduleDrawable(mButtonDrawable);
- }
- d.setCallback(this);
- d.setState(getDrawableState());
- d.setVisible(getVisibility() == VISIBLE, false);
- mButtonDrawable = d;
- mButtonDrawable.setState(null);
- setMinHeight(mButtonDrawable.getIntrinsicHeight());
- setWidth(mButtonDrawable.getIntrinsicWidth());
- }
- refreshDrawableState();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mButtonDrawable != null) {
- final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
- final int horizontalGravity = getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
- final int height = mButtonDrawable.getIntrinsicHeight();
- final int width = mButtonDrawable.getIntrinsicWidth();
-
- int y = 0;
- int x = 0;
-
- switch (verticalGravity) {
- case Gravity.BOTTOM:
- y = getHeight() - height;
- break;
- case Gravity.CENTER_VERTICAL:
- y = (getHeight() - height) / 2;
- break;
- }
- switch (horizontalGravity) {
- case Gravity.RIGHT:
- x = getWidth() - width;
- break;
- case Gravity.CENTER_HORIZONTAL:
- x = (getWidth() - width) / 2;
- break;
- }
-
- mButtonDrawable.setBounds(x, y, x + width, y + height);
- mButtonDrawable.draw(canvas);
- }
- }
-}
diff --git a/src/com/android/calendar/MultiStateButton.kt b/src/com/android/calendar/MultiStateButton.kt
new file mode 100644
index 00000000..f86ee6ba
--- /dev/null
+++ b/src/com/android/calendar/MultiStateButton.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.drawable.Drawable
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.widget.Button
+
+/**
+ * A button with more than two states. When the button is pressed
+ * or clicked, the state transitions automatically.
+ *
+ * **XML attributes**
+ * See [ MultiStateButton Attributes][R.styleable.MultiStateButton],
+ * [Button][android.R.styleable.Button], [TextView Attributes][android.R.styleable.TextView],
+ * [ ][android.R.styleable.View]
+ *
+ */
+class MultiStateButton(context: Context?, attrs: AttributeSet?, defStyle: Int) :
+ Button(context, attrs, defStyle) {
+ //The current state for this button, ranging from 0 to maxState-1
+ var mState = 0
+ private set
+
+ //The maximum number of states allowed for this button.
+ private var mMaxStates = 1
+
+ //The currently displaying resource ID. This gets set to a default on creation and remains
+ //on the last set if the resources get set to null.
+ private var mButtonResource = 0
+
+ //A list of all drawable resources used by this button in the order it uses them.
+ private var mButtonResources: IntArray
+ private var mButtonDrawable: Drawable? = null
+
+ constructor(context: Context?) : this(context, null) {}
+ constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) {}
+
+ override fun performClick(): Boolean {
+ /* When clicked, toggle the state */
+ transitionState()
+ return super.performClick()
+ }
+
+ fun transitionState() {
+ mState = (mState + 1) % mMaxStates
+ setButtonDrawable(mButtonResources[mState])
+ }
+
+ /**
+ * Allows for a new set of drawable resource ids to be set.
+ *
+ * This sets the maximum states allowed to the length of the resources array. It will also
+ * set the current state to the maximum allowed if it's greater than the new max.
+ */
+ //@Throws(IllegalArgumentException::class)
+ fun setButtonResources(resources: IntArray?) {
+ if (resources == null) {
+ throw IllegalArgumentException("Button resources cannot be null")
+ }
+ mMaxStates = resources.size
+ if (mState >= mMaxStates) {
+ mState = mMaxStates - 1
+ }
+ mButtonResources = resources
+ }
+
+ /**
+ * Attempts to set the state. Returns true if successful, false otherwise.
+ */
+ fun setState(state: Int): Boolean {
+ if (state >= mMaxStates || state < 0) {
+ //When moved out of Calendar the tag should be changed.
+ Log.w("Cal", "MultiStateButton state set to value greater than maxState or < 0")
+ return false
+ }
+ mState = state
+ setButtonDrawable(mButtonResources[mState])
+ return true
+ }
+
+ /**
+ * Set the background to a given Drawable, identified by its resource id.
+ *
+ * @param resid the resource id of the drawable to use as the background
+ */
+ fun setButtonDrawable(resid: Int) {
+ if (resid != 0 && resid == mButtonResource) {
+ return
+ }
+ mButtonResource = resid
+ var d: Drawable? = null
+ if (mButtonResource != 0) {
+ d = getResources().getDrawable(mButtonResource)
+ }
+ setButtonDrawable(d)
+ }
+
+ /**
+ * Set the background to a given Drawable
+ *
+ * @param d The Drawable to use as the background
+ */
+ fun setButtonDrawable(d: Drawable?) {
+ if (d != null) {
+ if (mButtonDrawable != null) {
+ mButtonDrawable?.setCallback(null)
+ unscheduleDrawable(mButtonDrawable)
+ }
+ d.setCallback(this)
+ d.setState(getDrawableState())
+ d.setVisible(getVisibility() === VISIBLE, false)
+ mButtonDrawable = d
+ mButtonDrawable?.setState(getDrawableState())
+ setMinHeight(mButtonDrawable?.getIntrinsicHeight() ?: 0)
+ setWidth(mButtonDrawable?.getIntrinsicWidth() ?: 0)
+ }
+ refreshDrawableState()
+ }
+
+ protected override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (mButtonDrawable != null) {
+ val verticalGravity: Int = getGravity() and Gravity.VERTICAL_GRAVITY_MASK
+ val horizontalGravity: Int = getGravity() and Gravity.HORIZONTAL_GRAVITY_MASK
+ val height: Int = mButtonDrawable?.getIntrinsicHeight() ?: 0
+ val width: Int = mButtonDrawable?.getIntrinsicWidth() ?: 0
+ var y = 0
+ var x = 0
+ when (verticalGravity) {
+ Gravity.BOTTOM -> y = getHeight() - height
+ Gravity.CENTER_VERTICAL -> y = (getHeight() - height) / 2
+ }
+ when (horizontalGravity) {
+ Gravity.RIGHT -> x = getWidth() - width
+ Gravity.CENTER_HORIZONTAL -> x = (getWidth() - width) / 2
+ }
+ mButtonDrawable?.setBounds(x, y, x + width, y + height)
+ mButtonDrawable?.draw(canvas)
+ }
+ }
+
+ init {
+ //Currently using the standard buttonStyle, will update when new resources are added.
+ //TODO add a more generic default button
+ mButtonResources = intArrayOf(R.drawable.widget_show)
+ setButtonDrawable(mButtonResources[mState])
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/OtherPreferences.java b/src/com/android/calendar/OtherPreferences.java
deleted file mode 100644
index a59d3f46..00000000
--- a/src/com/android/calendar/OtherPreferences.java
+++ /dev/null
@@ -1,210 +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.app.Activity;
-import android.app.Dialog;
-import android.app.TimePickerDialog;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.text.format.DateFormat;
-import android.text.format.Time;
-import android.util.Log;
-import android.widget.TimePicker;
-
-public class OtherPreferences extends PreferenceFragment implements OnPreferenceChangeListener{
- private static final String TAG = "CalendarOtherPreferences";
-
- // The name of the shared preferences file. This name must be maintained for
- // historical reasons, as it's what PreferenceManager assigned the first
- // time the file was created.
- static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-
- // Must be the same keys that are used in the other_preferences.xml file.
- public static final String KEY_OTHER_COPY_DB = "preferences_copy_db";
- public static final String KEY_OTHER_QUIET_HOURS = "preferences_reminders_quiet_hours";
- public static final String KEY_OTHER_REMINDERS_RESPONDED = "preferences_reminders_responded";
- public static final String KEY_OTHER_QUIET_HOURS_START =
- "preferences_reminders_quiet_hours_start";
- public static final String KEY_OTHER_QUIET_HOURS_START_HOUR =
- "preferences_reminders_quiet_hours_start_hour";
- public static final String KEY_OTHER_QUIET_HOURS_START_MINUTE =
- "preferences_reminders_quiet_hours_start_minute";
- public static final String KEY_OTHER_QUIET_HOURS_END =
- "preferences_reminders_quiet_hours_end";
- public static final String KEY_OTHER_QUIET_HOURS_END_HOUR =
- "preferences_reminders_quiet_hours_end_hour";
- public static final String KEY_OTHER_QUIET_HOURS_END_MINUTE =
- "preferences_reminders_quiet_hours_end_minute";
- public static final String KEY_OTHER_1 = "preferences_tardis_1";
-
- public static final int QUIET_HOURS_DEFAULT_START_HOUR = 22;
- public static final int QUIET_HOURS_DEFAULT_START_MINUTE = 0;
- public static final int QUIET_HOURS_DEFAULT_END_HOUR = 8;
- public static final int QUIET_HOURS_DEFAULT_END_MINUTE = 0;
-
- private static final int START_LISTENER = 1;
- private static final int END_LISTENER = 2;
- private static final String format24Hour = "%H:%M";
- private static final String format12Hour = "%I:%M%P";
-
- private Preference mCopyDb;
- private CheckBoxPreference mQuietHours;
- private Preference mQuietHoursStart;
- private Preference mQuietHoursEnd;
-
- private TimePickerDialog mTimePickerDialog;
- private TimeSetListener mQuietHoursStartListener;
- private TimePickerDialog mQuietHoursStartDialog;
- private TimeSetListener mQuietHoursEndListener;
- private TimePickerDialog mQuietHoursEndDialog;
- private boolean mIs24HourMode;
-
- public OtherPreferences() {
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- PreferenceManager manager = getPreferenceManager();
- manager.setSharedPreferencesName(SHARED_PREFS_NAME);
- SharedPreferences prefs = manager.getSharedPreferences();
-
- addPreferencesFromResource(R.xml.other_preferences);
- mCopyDb = findPreference(KEY_OTHER_COPY_DB);
-
- Activity activity = getActivity();
- if (activity == null) {
- Log.d(TAG, "Activity was null");
- }
- mIs24HourMode = DateFormat.is24HourFormat(activity);
-
- mQuietHours =
- (CheckBoxPreference) findPreference(KEY_OTHER_QUIET_HOURS);
-
- int startHour = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_HOUR,
- QUIET_HOURS_DEFAULT_START_HOUR);
- int startMinute = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_MINUTE,
- QUIET_HOURS_DEFAULT_START_MINUTE);
- mQuietHoursStart = findPreference(KEY_OTHER_QUIET_HOURS_START);
- mQuietHoursStartListener = new TimeSetListener(START_LISTENER);
- mQuietHoursStartDialog = new TimePickerDialog(
- activity, mQuietHoursStartListener,
- startHour, startMinute, mIs24HourMode);
- mQuietHoursStart.setSummary(formatTime(startHour, startMinute));
-
- int endHour = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_HOUR,
- QUIET_HOURS_DEFAULT_END_HOUR);
- int endMinute = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_MINUTE,
- QUIET_HOURS_DEFAULT_END_MINUTE);
- mQuietHoursEnd = findPreference(KEY_OTHER_QUIET_HOURS_END);
- mQuietHoursEndListener = new TimeSetListener(END_LISTENER);
- mQuietHoursEndDialog = new TimePickerDialog(
- activity, mQuietHoursEndListener,
- endHour, endMinute, mIs24HourMode);
- mQuietHoursEnd.setSummary(formatTime(endHour, endMinute));
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object objValue) {
- return true;
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
- if (preference == mCopyDb) {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName("com.android.providers.calendar",
- "com.android.providers.calendar.CalendarDebugActivity"));
- startActivity(intent);
- } else if (preference == mQuietHoursStart) {
- if (mTimePickerDialog == null) {
- mTimePickerDialog = mQuietHoursStartDialog;
- mTimePickerDialog.show();
- } else {
- Log.v(TAG, "not null");
- }
- } else if (preference == mQuietHoursEnd) {
- if (mTimePickerDialog == null) {
- mTimePickerDialog = mQuietHoursEndDialog;
- mTimePickerDialog.show();
- } else {
- Log.v(TAG, "not null");
- }
- } else {
- return super.onPreferenceTreeClick(screen, preference);
- }
- return true;
- }
-
- private class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
- private int mListenerId;
-
- public TimeSetListener(int listenerId) {
- mListenerId = listenerId;
- }
-
- @Override
- public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
- mTimePickerDialog = null;
-
- SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- SharedPreferences.Editor editor = prefs.edit();
-
- String summary = formatTime(hourOfDay, minute);
- switch (mListenerId) {
- case (START_LISTENER):
- mQuietHoursStart.setSummary(summary);
- editor.putInt(KEY_OTHER_QUIET_HOURS_START_HOUR, hourOfDay);
- editor.putInt(KEY_OTHER_QUIET_HOURS_START_MINUTE, minute);
- break;
- case (END_LISTENER):
- mQuietHoursEnd.setSummary(summary);
- editor.putInt(KEY_OTHER_QUIET_HOURS_END_HOUR, hourOfDay);
- editor.putInt(KEY_OTHER_QUIET_HOURS_END_MINUTE, minute);
- break;
- default:
- Log.d(TAG, "Set time for unknown listener: "+mListenerId);
- }
-
- editor.commit();
- }
- }
-
- /**
- * @param hourOfDay the hour of the day (0-24)
- * @param minute
- * @return human-readable string formatted based on 24-hour mode.
- */
- private String formatTime(int hourOfDay, int minute) {
- Time time = new Time();
- time.hour = hourOfDay;
- time.minute = minute;
-
- String format = mIs24HourMode? format24Hour : format12Hour;
- return time.format(format);
- }
-}
diff --git a/src/com/android/calendar/OtherPreferences.kt b/src/com/android/calendar/OtherPreferences.kt
new file mode 100644
index 00000000..f1507ccf
--- /dev/null
+++ b/src/com/android/calendar/OtherPreferences.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.TimePickerDialog
+import android.content.ComponentName
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.preference.CheckBoxPreference
+import android.preference.ListPreference
+import android.preference.Preference
+import android.preference.Preference.OnPreferenceChangeListener
+import android.preference.PreferenceFragment
+import android.preference.PreferenceManager
+import android.preference.PreferenceScreen
+import android.text.format.DateFormat
+import android.text.format.Time
+import android.util.Log
+import android.widget.TimePicker
+
+class OtherPreferences : PreferenceFragment(), OnPreferenceChangeListener {
+ private var mCopyDb: Preference? = null
+ private var mQuietHours: CheckBoxPreference? = null
+ private var mQuietHoursStart: Preference? = null
+ private var mQuietHoursEnd: Preference? = null
+ private var mTimePickerDialog: TimePickerDialog? = null
+ private var mQuietHoursStartListener: TimeSetListener? = null
+ private var mQuietHoursStartDialog: TimePickerDialog? = null
+ private var mQuietHoursEndListener: TimeSetListener? = null
+ private var mQuietHoursEndDialog: TimePickerDialog? = null
+ private var mIs24HourMode = false
+
+ @Override
+ override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ val manager: PreferenceManager = getPreferenceManager()
+ manager.setSharedPreferencesName(SHARED_PREFS_NAME)
+ val prefs: SharedPreferences = manager.getSharedPreferences()
+ addPreferencesFromResource(R.xml.other_preferences)
+ mCopyDb = findPreference(KEY_OTHER_COPY_DB)
+ val activity: Activity = getActivity()
+ if (activity == null) {
+ Log.d(TAG, "Activity was null")
+ }
+ mIs24HourMode = DateFormat.is24HourFormat(activity)
+ mQuietHours = findPreference(KEY_OTHER_QUIET_HOURS) as CheckBoxPreference?
+ val startHour: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_HOUR,
+ QUIET_HOURS_DEFAULT_START_HOUR)
+ val startMinute: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_MINUTE,
+ QUIET_HOURS_DEFAULT_START_MINUTE)
+ mQuietHoursStart = findPreference(KEY_OTHER_QUIET_HOURS_START)
+ mQuietHoursStartListener = TimeSetListener(START_LISTENER)
+ mQuietHoursStartDialog = TimePickerDialog(
+ activity, mQuietHoursStartListener,
+ startHour, startMinute, mIs24HourMode)
+ mQuietHoursStart?.setSummary(formatTime(startHour, startMinute))
+ val endHour: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_HOUR,
+ QUIET_HOURS_DEFAULT_END_HOUR)
+ val endMinute: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_MINUTE,
+ QUIET_HOURS_DEFAULT_END_MINUTE)
+ mQuietHoursEnd = findPreference(KEY_OTHER_QUIET_HOURS_END)
+ mQuietHoursEndListener = TimeSetListener(END_LISTENER)
+ mQuietHoursEndDialog = TimePickerDialog(
+ activity, mQuietHoursEndListener,
+ endHour, endMinute, mIs24HourMode)
+ mQuietHoursEnd?.setSummary(formatTime(endHour, endMinute))
+ }
+
+ @Override
+ override fun onPreferenceChange(preference: Preference?, objValue: Any?): Boolean {
+ return true
+ }
+
+ @Override
+ override fun onPreferenceTreeClick(screen: PreferenceScreen?, preference: Preference): Boolean {
+ if (preference === mCopyDb) {
+ val intent = Intent(Intent.ACTION_MAIN)
+ intent.setComponent(ComponentName("com.android.providers.calendar",
+ "com.android.providers.calendar.CalendarDebugActivity"))
+ startActivity(intent)
+ } else if (preference === mQuietHoursStart) {
+ if (mTimePickerDialog == null) {
+ mTimePickerDialog = mQuietHoursStartDialog
+ mTimePickerDialog?.show()
+ } else {
+ Log.v(TAG, "not null")
+ }
+ } else if (preference === mQuietHoursEnd) {
+ if (mTimePickerDialog == null) {
+ mTimePickerDialog = mQuietHoursEndDialog
+ mTimePickerDialog?.show()
+ } else {
+ Log.v(TAG, "not null")
+ }
+ } else {
+ return super.onPreferenceTreeClick(screen, preference)
+ }
+ return true
+ }
+
+ private inner class TimeSetListener(private val mListenerId: Int) :
+ TimePickerDialog.OnTimeSetListener {
+ @Override
+ override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
+ mTimePickerDialog = null
+ val prefs: SharedPreferences = getPreferenceManager().getSharedPreferences()
+ val editor: SharedPreferences.Editor = prefs.edit()
+ val summary = formatTime(hourOfDay, minute)
+ when (mListenerId) {
+ START_LISTENER -> {
+ mQuietHoursStart?.setSummary(summary)
+ editor.putInt(KEY_OTHER_QUIET_HOURS_START_HOUR, hourOfDay)
+ editor.putInt(KEY_OTHER_QUIET_HOURS_START_MINUTE, minute)
+ }
+ END_LISTENER -> {
+ mQuietHoursEnd?.setSummary(summary)
+ editor.putInt(KEY_OTHER_QUIET_HOURS_END_HOUR, hourOfDay)
+ editor.putInt(KEY_OTHER_QUIET_HOURS_END_MINUTE, minute)
+ }
+ else -> Log.d(TAG, "Set time for unknown listener: $mListenerId")
+ }
+ editor.commit()
+ }
+ }
+
+ /**
+ * @param hourOfDay the hour of the day (0-24)
+ * @param minute
+ * @return human-readable string formatted based on 24-hour mode.
+ */
+ private fun formatTime(hourOfDay: Int, minute: Int): String {
+ val time = Time()
+ time.hour = hourOfDay
+ time.minute = minute
+ val format = if (mIs24HourMode) format24Hour else format12Hour
+ return time.format(format)
+ }
+
+ companion object {
+ private const val TAG = "CalendarOtherPreferences"
+
+ // The name of the shared preferences file. This name must be maintained for
+ // historical reasons, as it's what PreferenceManager assigned the first
+ // time the file was created.
+ const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+
+ // Must be the same keys that are used in the other_preferences.xml file.
+ const val KEY_OTHER_COPY_DB = "preferences_copy_db"
+ const val KEY_OTHER_QUIET_HOURS = "preferences_reminders_quiet_hours"
+ const val KEY_OTHER_REMINDERS_RESPONDED = "preferences_reminders_responded"
+ const val KEY_OTHER_QUIET_HOURS_START = "preferences_reminders_quiet_hours_start"
+ const val KEY_OTHER_QUIET_HOURS_START_HOUR = "preferences_reminders_quiet_hours_start_hour"
+ const val KEY_OTHER_QUIET_HOURS_START_MINUTE =
+ "preferences_reminders_quiet_hours_start_minute"
+ const val KEY_OTHER_QUIET_HOURS_END = "preferences_reminders_quiet_hours_end"
+ const val KEY_OTHER_QUIET_HOURS_END_HOUR = "preferences_reminders_quiet_hours_end_hour"
+ const val KEY_OTHER_QUIET_HOURS_END_MINUTE = "preferences_reminders_quiet_hours_end_minute"
+ const val KEY_OTHER_1 = "preferences_tardis_1"
+ const val QUIET_HOURS_DEFAULT_START_HOUR = 22
+ const val QUIET_HOURS_DEFAULT_START_MINUTE = 0
+ const val QUIET_HOURS_DEFAULT_END_HOUR = 8
+ const val QUIET_HOURS_DEFAULT_END_MINUTE = 0
+ private const val START_LISTENER = 1
+ private const val END_LISTENER = 2
+ private const val format24Hour = "%H:%M"
+ private const val format12Hour = "%I:%M%P"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/StickyHeaderListView.java b/src/com/android/calendar/StickyHeaderListView.java
deleted file mode 100644
index 981e7af7..00000000
--- a/src/com/android/calendar/StickyHeaderListView.java
+++ /dev/null
@@ -1,395 +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.Color;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.Adapter;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-
-/**
- * Implements a ListView class with a sticky header at the top. The header is
- * per section and it is pinned to the top as long as its section is at the top
- * of the view. If it is not, the header slides up or down (depending on the
- * scroll movement) and the header of the current section slides to the top.
- * Notes:
- * 1. The class uses the first available child ListView as the working
- * ListView. If no ListView child exists, the class will create a default one.
- * 2. The ListView's adapter must be passed to this class using the 'setAdapter'
- * method. The adapter must implement the HeaderIndexer interface. If no adapter
- * is specified, the class will try to extract it from the ListView
- * 3. The class registers itself as a listener to scroll events (OnScrollListener), if the
- * ListView needs to receive scroll events, it must register its listener using
- * this class' setOnScrollListener method.
- * 4. Headers for the list view must be added before using the StickyHeaderListView
- * 5. The implementation should register to listen to dataset changes. Right now this is not done
- * since a change the dataset in a listview forces a call to OnScroll. The needed code is
- * commented out.
- */
-public class StickyHeaderListView extends FrameLayout implements OnScrollListener {
-
- private static final String TAG = "StickyHeaderListView";
- protected boolean mChildViewsCreated = false;
- protected boolean mDoHeaderReset = false;
-
- protected Context mContext = null;
- protected Adapter mAdapter = null;
- protected HeaderIndexer mIndexer = null;
- protected HeaderHeightListener mHeaderHeightListener = null;
- protected View mStickyHeader = null;
- protected View mNonessentialHeader = null; // A invisible header used when a section has no header
- protected ListView mListView = null;
- protected ListView.OnScrollListener mListener = null;
-
- private int mSeparatorWidth;
- private View mSeparatorView;
- private int mLastStickyHeaderHeight = 0;
-
- // This code is needed only if dataset changes do not force a call to OnScroll
- // protected DataSetObserver mListDataObserver = null;
-
-
- protected int mCurrentSectionPos = -1; // Position of section that has its header on the
- // top of the view
- protected int mNextSectionPosition = -1; // Position of next section's header
- protected int mListViewHeadersCount = 0;
-
- /**
- * Interface that must be implemented by the ListView adapter to provide headers locations
- * and number of items under each header.
- *
- */
- public interface HeaderIndexer {
- /**
- * Calculates the position of the header of a specific item in the adapter's data set.
- * For example: Assuming you have a list with albums and songs names:
- * Album A, song 1, song 2, ...., song 10, Album B, song 1, ..., song 7. A call to
- * this method with the position of song 5 in Album B, should return the position
- * of Album B.
- * @param position - Position of the item in the ListView dataset
- * @return Position of header. -1 if the is no header
- */
-
- int getHeaderPositionFromItemPosition(int position);
-
- /**
- * Calculates the number of items in the section defined by the header (not including
- * the header).
- * For example: A list with albums and songs, the method should return
- * the number of songs names (without the album name).
- *
- * @param headerPosition - the value returned by 'getHeaderPositionFromItemPosition'
- * @return Number of items. -1 on error.
- */
- int getHeaderItemsNumber(int headerPosition);
- }
-
- /***
- *
- * Interface that is used to update the sticky header's height
- *
- */
- public interface HeaderHeightListener {
-
- /***
- * Updated a change in the sticky header's size
- *
- * @param height - new height of sticky header
- */
- void OnHeaderHeightChanged(int height);
- }
-
- /**
- * Sets the adapter to be used by the class to get views of headers
- *
- * @param adapter - The adapter.
- */
-
- public void setAdapter(Adapter adapter) {
-
- // This code is needed only if dataset changes do not force a call to
- // OnScroll
- // if (mAdapter != null && mListDataObserver != null) {
- // mAdapter.unregisterDataSetObserver(mListDataObserver);
- // }
-
- if (adapter != null) {
- mAdapter = adapter;
- // This code is needed only if dataset changes do not force a call
- // to OnScroll
- // mAdapter.registerDataSetObserver(mListDataObserver);
- }
- }
-
- /**
- * Sets the indexer object (that implements the HeaderIndexer interface).
- *
- * @param indexer - The indexer.
- */
-
- public void setIndexer(HeaderIndexer indexer) {
- mIndexer = indexer;
- }
-
- /**
- * Sets the list view that is displayed
- * @param lv - The list view.
- */
-
- public void setListView(ListView lv) {
- mListView = lv;
- mListView.setOnScrollListener(this);
- mListViewHeadersCount = mListView.getHeaderViewsCount();
- }
-
- /**
- * Sets an external OnScroll listener. Since the StickyHeaderListView sets
- * itself as the scroll events listener of the listview, this method allows
- * the user to register another listener that will be called after this
- * class listener is called.
- *
- * @param listener - The external listener.
- */
- public void setOnScrollListener(ListView.OnScrollListener listener) {
- mListener = listener;
- }
-
- public void setHeaderHeightListener(HeaderHeightListener listener) {
- mHeaderHeightListener = listener;
- }
-
- // This code is needed only if dataset changes do not force a call to OnScroll
- // protected void createDataListener() {
- // mListDataObserver = new DataSetObserver() {
- // @Override
- // public void onChanged() {
- // onDataChanged();
- // }
- // };
- // }
-
- /**
- * Constructor
- *
- * @param context - application context.
- * @param attrs - layout attributes.
- */
- public StickyHeaderListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- // This code is needed only if dataset changes do not force a call to OnScroll
- // createDataListener();
- }
-
- /**
- * Scroll status changes listener
- *
- * @param view - the scrolled view
- * @param scrollState - new scroll state.
- */
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- if (mListener != null) {
- mListener.onScrollStateChanged(view, scrollState);
- }
- }
-
- /**
- * Scroll events listener
- *
- * @param view - the scrolled view
- * @param firstVisibleItem - the index (in the list's adapter) of the top
- * visible item.
- * @param visibleItemCount - the number of visible items in the list
- * @param totalItemCount - the total number items in the list
- */
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
-
- updateStickyHeader(firstVisibleItem);
-
- if (mListener != null) {
- mListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
- }
- }
-
- /**
- * Sets a separator below the sticky header, which will be visible while the sticky header
- * is not scrolling up.
- * @param color - color of separator
- * @param width - width in pixels of separator
- */
- public void setHeaderSeparator(int color, int width) {
- mSeparatorView = new View(mContext);
- ViewGroup.LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
- width, Gravity.TOP);
- mSeparatorView.setLayoutParams(params);
- mSeparatorView.setBackgroundColor(color);
- mSeparatorWidth = width;
- this.addView(mSeparatorView);
- }
-
- protected void updateStickyHeader(int firstVisibleItem) {
-
- // Try to make sure we have an adapter to work with (may not succeed).
- if (mAdapter == null && mListView != null) {
- setAdapter(mListView.getAdapter());
- }
-
- firstVisibleItem -= mListViewHeadersCount;
- if (mAdapter != null && mIndexer != null && mDoHeaderReset) {
-
- // Get the section header position
- int sectionSize = 0;
- int sectionPos = mIndexer.getHeaderPositionFromItemPosition(firstVisibleItem);
-
- // New section - set it in the header view
- boolean newView = false;
- if (sectionPos != mCurrentSectionPos) {
-
- // No header for current position , use the nonessential invisible one, hide the separator
- if (sectionPos == -1) {
- sectionSize = 0;
- this.removeView(mStickyHeader);
- mStickyHeader = mNonessentialHeader;
- if (mSeparatorView != null) {
- mSeparatorView.setVisibility(View.GONE);
- }
- newView = true;
- } else {
- // Create a copy of the header view to show on top
- sectionSize = mIndexer.getHeaderItemsNumber(sectionPos);
- View v = mAdapter.getView(sectionPos + mListViewHeadersCount, null, mListView);
- v.measure(MeasureSpec.makeMeasureSpec(mListView.getWidth(),
- MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mListView.getHeight(),
- MeasureSpec.AT_MOST));
- this.removeView(mStickyHeader);
- mStickyHeader = v;
- newView = true;
- }
- mCurrentSectionPos = sectionPos;
- mNextSectionPosition = sectionSize + sectionPos + 1;
- }
-
-
- // Do transitions
- // If position of bottom of last item in a section is smaller than the height of the
- // sticky header - shift drawable of header.
- if (mStickyHeader != null) {
- int sectionLastItemPosition = mNextSectionPosition - firstVisibleItem - 1;
- int stickyHeaderHeight = mStickyHeader.getHeight();
- if (stickyHeaderHeight == 0) {
- stickyHeaderHeight = mStickyHeader.getMeasuredHeight();
- }
-
- // Update new header height
- if (mHeaderHeightListener != null &&
- mLastStickyHeaderHeight != stickyHeaderHeight) {
- mLastStickyHeaderHeight = stickyHeaderHeight;
- mHeaderHeightListener.OnHeaderHeightChanged(stickyHeaderHeight);
- }
-
- View SectionLastView = mListView.getChildAt(sectionLastItemPosition);
- if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
- int lastViewBottom = SectionLastView.getBottom();
- mStickyHeader.setTranslationY(lastViewBottom - stickyHeaderHeight);
- if (mSeparatorView != null) {
- mSeparatorView.setVisibility(View.GONE);
- }
- } else if (stickyHeaderHeight != 0) {
- mStickyHeader.setTranslationY(0);
- if (mSeparatorView != null && !mStickyHeader.equals(mNonessentialHeader)) {
- mSeparatorView.setVisibility(View.VISIBLE);
- }
- }
- if (newView) {
- mStickyHeader.setVisibility(View.INVISIBLE);
- this.addView(mStickyHeader);
- if (mSeparatorView != null && !mStickyHeader.equals(mNonessentialHeader)){
- FrameLayout.LayoutParams params =
- new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
- mSeparatorWidth);
- params.setMargins(0, mStickyHeader.getMeasuredHeight(), 0, 0);
- mSeparatorView.setLayoutParams(params);
- mSeparatorView.setVisibility(View.VISIBLE);
- }
- mStickyHeader.setVisibility(View.VISIBLE);
- }
- }
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- if (!mChildViewsCreated) {
- setChildViews();
- }
- mDoHeaderReset = true;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (!mChildViewsCreated) {
- setChildViews();
- }
- mDoHeaderReset = true;
- }
-
-
- // Resets the sticky header when the adapter data set was changed
- // This code is needed only if dataset changes do not force a call to OnScroll
- // protected void onDataChanged() {
- // Should do a call to updateStickyHeader if needed
- // }
-
- private void setChildViews() {
-
- // Find a child ListView (if any)
- int iChildNum = getChildCount();
- for (int i = 0; i < iChildNum; i++) {
- Object v = getChildAt(i);
- if (v instanceof ListView) {
- setListView((ListView) v);
- }
- }
-
- // No child ListView - add one
- if (mListView == null) {
- setListView(new ListView(mContext));
- }
-
- // Create a nonessential view , it will be used in case a section has no header
- mNonessentialHeader = new View (mContext);
- ViewGroup.LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
- 1, Gravity.TOP);
- mNonessentialHeader.setLayoutParams(params);
- mNonessentialHeader.setBackgroundColor(Color.TRANSPARENT);
-
- mChildViewsCreated = true;
- }
-
-}
diff --git a/src/com/android/calendar/StickyHeaderListView.kt b/src/com/android/calendar/StickyHeaderListView.kt
new file mode 100644
index 00000000..37733b7b
--- /dev/null
+++ b/src/com/android/calendar/StickyHeaderListView.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.Color
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+import android.widget.Adapter
+import android.widget.FrameLayout
+import android.widget.ListView
+
+/**
+ * Implements a ListView class with a sticky header at the top. The header is
+ * per section and it is pinned to the top as long as its section is at the top
+ * of the view. If it is not, the header slides up or down (depending on the
+ * scroll movement) and the header of the current section slides to the top.
+ * Notes:
+ * 1. The class uses the first available child ListView as the working
+ * ListView. If no ListView child exists, the class will create a default one.
+ * 2. The ListView's adapter must be passed to this class using the 'setAdapter'
+ * method. The adapter must implement the HeaderIndexer interface. If no adapter
+ * is specified, the class will try to extract it from the ListView
+ * 3. The class registers itself as a listener to scroll events (OnScrollListener), if the
+ * ListView needs to receive scroll events, it must register its listener using
+ * this class' setOnScrollListener method.
+ * 4. Headers for the list view must be added before using the StickyHeaderListView
+ * 5. The implementation should register to listen to dataset changes. Right now this is not done
+ * since a change the dataset in a listview forces a call to OnScroll. The needed code is
+ * commented out.
+ */
+class StickyHeaderListView(context: Context, attrs: AttributeSet?) :
+ FrameLayout(context, attrs), OnScrollListener {
+ protected var mChildViewsCreated = false
+ protected var mDoHeaderReset = false
+ protected var mContext: Context? = null
+ protected var mAdapter: Adapter? = null
+ protected var mIndexer: HeaderIndexer? = null
+ protected var mHeaderHeightListener: HeaderHeightListener? = null
+ protected var mStickyHeader: View? = null
+ // A invisible header used when a section has no header
+ protected var mNonessentialHeader: View? = null
+ protected var mListView: ListView? = null
+ protected var mListener: AbsListView.OnScrollListener? = null
+ private var mSeparatorWidth = 0
+ private var mSeparatorView: View? = null
+ private var mLastStickyHeaderHeight = 0
+
+ // This code is needed only if dataset changes do not force a call to OnScroll
+ // protected DataSetObserver mListDataObserver = null;
+ protected var mCurrentSectionPos = -1 // Position of section that has its header on the
+
+ // top of the view
+ protected var mNextSectionPosition = -1 // Position of next section's header
+ protected var mListViewHeadersCount = 0
+
+ /**
+ * Interface that must be implemented by the ListView adapter to provide headers locations
+ * and number of items under each header.
+ *
+ */
+ interface HeaderIndexer {
+ /**
+ * Calculates the position of the header of a specific item in the adapter's data set.
+ * For example: Assuming you have a list with albums and songs names:
+ * Album A, song 1, song 2, ...., song 10, Album B, song 1, ..., song 7. A call to
+ * this method with the position of song 5 in Album B, should return the position
+ * of Album B.
+ * @param position - Position of the item in the ListView dataset
+ * @return Position of header. -1 if the is no header
+ */
+ fun getHeaderPositionFromItemPosition(position: Int): Int
+
+ /**
+ * Calculates the number of items in the section defined by the header (not including
+ * the header).
+ * For example: A list with albums and songs, the method should return
+ * the number of songs names (without the album name).
+ *
+ * @param headerPosition - the value returned by 'getHeaderPositionFromItemPosition'
+ * @return Number of items. -1 on error.
+ */
+ fun getHeaderItemsNumber(headerPosition: Int): Int
+ }
+
+ /***
+ *
+ * Interface that is used to update the sticky header's height
+ *
+ */
+ interface HeaderHeightListener {
+ /***
+ * Updated a change in the sticky header's size
+ *
+ * @param height - new height of sticky header
+ */
+ fun OnHeaderHeightChanged(height: Int)
+ }
+
+ /**
+ * Sets the adapter to be used by the class to get views of headers
+ *
+ * @param adapter - The adapter.
+ */
+ fun setAdapter(adapter: Adapter?) {
+ // This code is needed only if dataset changes do not force a call to
+ // OnScroll
+ // if (mAdapter != null && mListDataObserver != null) {
+ // mAdapter.unregisterDataSetObserver(mListDataObserver);
+ // }
+ if (adapter != null) {
+ mAdapter = adapter
+ // This code is needed only if dataset changes do not force a call
+ // to OnScroll
+ // mAdapter.registerDataSetObserver(mListDataObserver);
+ }
+ }
+
+ /**
+ * Sets the indexer object (that implements the HeaderIndexer interface).
+ *
+ * @param indexer - The indexer.
+ */
+ fun setIndexer(indexer: HeaderIndexer?) {
+ mIndexer = indexer
+ }
+
+ /**
+ * Sets the list view that is displayed
+ * @param lv - The list view.
+ */
+ fun setListView(lv: ListView?) {
+ mListView = lv
+ mListView?.setOnScrollListener(this)
+ mListViewHeadersCount = mListView?.getHeaderViewsCount() as Int
+ }
+
+ /**
+ * Sets an external OnScroll listener. Since the StickyHeaderListView sets
+ * itself as the scroll events listener of the listview, this method allows
+ * the user to register another listener that will be called after this
+ * class listener is called.
+ *
+ * @param listener - The external listener.
+ */
+ fun setOnScrollListener(listener: AbsListView.OnScrollListener?) {
+ mListener = listener
+ }
+
+ fun setHeaderHeightListener(listener: HeaderHeightListener?) {
+ mHeaderHeightListener = listener
+ }
+
+ /**
+ * Scroll status changes listener
+ *
+ * @param view - the scrolled view
+ * @param scrollState - new scroll state.
+ */
+ @Override
+ override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+ if (mListener != null) {
+ mListener?.onScrollStateChanged(view, scrollState)
+ }
+ }
+
+ /**
+ * Scroll events listener
+ *
+ * @param view - the scrolled view
+ * @param firstVisibleItem - the index (in the list's adapter) of the top
+ * visible item.
+ * @param visibleItemCount - the number of visible items in the list
+ * @param totalItemCount - the total number items in the list
+ */
+ @Override
+ override fun onScroll(
+ view: AbsListView?,
+ firstVisibleItem: Int,
+ visibleItemCount: Int,
+ totalItemCount: Int
+ ) {
+ updateStickyHeader(firstVisibleItem)
+ if (mListener != null) {
+ mListener?.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount)
+ }
+ }
+
+ /**
+ * Sets a separator below the sticky header, which will be visible while the sticky header
+ * is not scrolling up.
+ * @param color - color of separator
+ * @param width - width in pixels of separator
+ */
+ fun setHeaderSeparator(color: Int, width: Int) {
+ mSeparatorView = View(mContext)
+ val params: ViewGroup.LayoutParams = LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ width, Gravity.TOP
+ )
+ mSeparatorView?.setLayoutParams(params)
+ mSeparatorView?.setBackgroundColor(color)
+ mSeparatorWidth = width
+ this.addView(mSeparatorView)
+ }
+
+ protected fun updateStickyHeader(firstVisibleItemInput: Int) {
+ // Try to make sure we have an adapter to work with (may not succeed).
+ var firstVisibleItem = firstVisibleItemInput
+ if (mAdapter == null && mListView != null) {
+ setAdapter(mListView?.getAdapter())
+ }
+ firstVisibleItem -= mListViewHeadersCount
+ if (mAdapter != null && mIndexer != null && mDoHeaderReset) {
+
+ // Get the section header position
+ var sectionSize = 0
+ val sectionPos = mIndexer!!.getHeaderPositionFromItemPosition(firstVisibleItem)
+
+ // New section - set it in the header view
+ var newView = false
+ if (sectionPos != mCurrentSectionPos) {
+
+ // No header for current position , use the nonessential invisible one,
+ // hide the separator
+ if (sectionPos == -1) {
+ sectionSize = 0
+ this.removeView(mStickyHeader)
+ mStickyHeader = mNonessentialHeader
+ if (mSeparatorView != null) {
+ mSeparatorView?.setVisibility(View.GONE)
+ }
+ newView = true
+ } else {
+ // Create a copy of the header view to show on top
+ sectionSize = mIndexer!!.getHeaderItemsNumber(sectionPos)
+ val v: View? =
+ mAdapter?.getView(sectionPos + mListViewHeadersCount, null, mListView)
+ v?.measure(
+ MeasureSpec.makeMeasureSpec(
+ mListView?.getWidth() as Int,
+ MeasureSpec.EXACTLY
+ ), MeasureSpec.makeMeasureSpec(
+ mListView?.getHeight() as Int,
+ MeasureSpec.AT_MOST
+ )
+ )
+ this.removeView(mStickyHeader)
+ mStickyHeader = v
+ newView = true
+ }
+ mCurrentSectionPos = sectionPos
+ mNextSectionPosition = sectionSize + sectionPos + 1
+ }
+
+ // Do transitions
+ // If position of bottom of last item in a section is smaller than the height of the
+ // sticky header - shift drawable of header.
+ if (mStickyHeader != null) {
+ val sectionLastItemPosition = mNextSectionPosition - firstVisibleItem - 1
+ var stickyHeaderHeight: Int = mStickyHeader?.getHeight() as Int
+ if (stickyHeaderHeight == 0) {
+ stickyHeaderHeight = mStickyHeader?.getMeasuredHeight() as Int
+ }
+
+ // Update new header height
+ if (mHeaderHeightListener != null &&
+ mLastStickyHeaderHeight != stickyHeaderHeight
+ ) {
+ mLastStickyHeaderHeight = stickyHeaderHeight
+ mHeaderHeightListener!!.OnHeaderHeightChanged(stickyHeaderHeight)
+ }
+ val SectionLastView: View? = mListView?.getChildAt(sectionLastItemPosition)
+ if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
+ val lastViewBottom: Int = SectionLastView.getBottom()
+ mStickyHeader?.setTranslationY(lastViewBottom.toFloat() -
+ stickyHeaderHeight.toFloat())
+ if (mSeparatorView != null) {
+ mSeparatorView?.setVisibility(View.GONE)
+ }
+ } else if (stickyHeaderHeight != 0) {
+ mStickyHeader?.setTranslationY(0f)
+ if (mSeparatorView != null &&
+ mStickyHeader?.equals(mNonessentialHeader) == false) {
+ mSeparatorView?.setVisibility(View.VISIBLE)
+ }
+ }
+ if (newView) {
+ mStickyHeader?.setVisibility(View.INVISIBLE)
+ this.addView(mStickyHeader)
+ if (mSeparatorView != null &&
+ mStickyHeader?.equals(mNonessentialHeader) == false) {
+ val params: FrameLayout.LayoutParams = LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ mSeparatorWidth
+ )
+ params.setMargins(0, mStickyHeader?.getMeasuredHeight() as Int, 0, 0)
+ mSeparatorView?.setLayoutParams(params)
+ mSeparatorView?.setVisibility(View.VISIBLE)
+ }
+ mStickyHeader?.setVisibility(View.VISIBLE)
+ }
+ }
+ }
+ }
+
+ @Override
+ protected override fun onFinishInflate() {
+ super.onFinishInflate()
+ if (!mChildViewsCreated) {
+ setChildViews()
+ }
+ mDoHeaderReset = true
+ }
+
+ @Override
+ protected override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ if (!mChildViewsCreated) {
+ setChildViews()
+ }
+ mDoHeaderReset = true
+ }
+
+ // Resets the sticky header when the adapter data set was changed
+ // This code is needed only if dataset changes do not force a call to OnScroll
+ // protected void onDataChanged() {
+ // Should do a call to updateStickyHeader if needed
+ // }
+ private fun setChildViews() {
+ // Find a child ListView (if any)
+ val iChildNum: Int = getChildCount()
+ for (i in 0 until iChildNum) {
+ val v: Object = getChildAt(i) as Object
+ if (v is ListView) {
+ setListView(v as ListView)
+ }
+ }
+
+ // No child ListView - add one
+ if (mListView == null) {
+ setListView(ListView(mContext))
+ }
+
+ // Create a nonessential view , it will be used in case a section has no header
+ mNonessentialHeader = View(mContext)
+ val params: ViewGroup.LayoutParams = LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ 1, Gravity.TOP
+ )
+ mNonessentialHeader?.setLayoutParams(params)
+ mNonessentialHeader?.setBackgroundColor(Color.TRANSPARENT)
+ mChildViewsCreated = true
+ }
+
+ companion object {
+ private const val TAG = "StickyHeaderListView"
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context - application context.
+ * @param attrs - layout attributes.
+ */
+ init {
+ mContext = context
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/UpgradeReceiver.java b/src/com/android/calendar/UpgradeReceiver.kt
index 0e89286d..ab2de1de 100644
--- a/src/com/android/calendar/UpgradeReceiver.java
+++ b/src/com/android/calendar/UpgradeReceiver.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,17 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.calendar
-package com.android.calendar;
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class UpgradeReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- Utils.trySyncAndDisableUpgradeReceiver(context);
+class UpgradeReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Utils.trySyncAndDisableUpgradeReceiver(context)
}
-
} \ No newline at end of file
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
deleted file mode 100644
index cc55c999..00000000
--- a/src/com/android/calendar/Utils.java
+++ /dev/null
@@ -1,1499 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.CalendarContract.Calendars;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.Log;
-
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.CalendarUtils.TimeZoneUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Utils {
- private static final boolean DEBUG = false;
- private static final String TAG = "CalUtils";
-
- // Set to 0 until we have UI to perform undo
- public static final long UNDO_DELAY = 0;
-
- // For recurring events which instances of the series are being modified
- public static final int MODIFY_UNINITIALIZED = 0;
- public static final int MODIFY_SELECTED = 1;
- public static final int MODIFY_ALL_FOLLOWING = 2;
- public static final int MODIFY_ALL = 3;
-
- // When the edit event view finishes it passes back the appropriate exit
- // code.
- public static final int DONE_REVERT = 1 << 0;
- public static final int DONE_SAVE = 1 << 1;
- public static final int DONE_DELETE = 1 << 2;
- // And should re run with DONE_EXIT if it should also leave the view, just
- // exiting is identical to reverting
- public static final int DONE_EXIT = 1 << 0;
-
- public static final String OPEN_EMAIL_MARKER = " <";
- public static final String CLOSE_EMAIL_MARKER = ">";
-
- public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW";
- public static final String INTENT_KEY_VIEW_TYPE = "VIEW";
- public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY";
- public static final String INTENT_KEY_HOME = "KEY_HOME";
-
- public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
- public static final int DECLINED_EVENT_ALPHA = 0x66;
- public static final int DECLINED_EVENT_TEXT_ALPHA = 0xC0;
-
- private static final float SATURATION_ADJUST = 1.3f;
- private static final float INTENSITY_ADJUST = 0.8f;
-
- // Defines used by the DNA generation code
- static final int DAY_IN_MINUTES = 60 * 24;
- static final int WEEK_IN_MINUTES = DAY_IN_MINUTES * 7;
- // The work day is being counted as 6am to 8pm
- static int WORK_DAY_MINUTES = 14 * 60;
- static int WORK_DAY_START_MINUTES = 6 * 60;
- static int WORK_DAY_END_MINUTES = 20 * 60;
- static int WORK_DAY_END_LENGTH = (24 * 60) - WORK_DAY_END_MINUTES;
- static int CONFLICT_COLOR = 0xFF000000;
- static boolean mMinutesLoaded = false;
-
- public static final int YEAR_MIN = 1970;
- public static final int YEAR_MAX = 2036;
-
- // The name of the shared preferences file. This name must be maintained for
- // historical
- // reasons, as it's what PreferenceManager assigned the first time the file
- // was created.
- static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-
- public static final String KEY_QUICK_RESPONSES = "preferences_quick_responses";
-
- public static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen";
-
- public static final String APPWIDGET_DATA_TYPE = "vnd.android.data/update";
-
- static final String MACHINE_GENERATED_ADDRESS = "calendar.google.com";
-
- private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME);
- private static boolean mAllowWeekForDetailView = false;
- private static long mTardis = 0;
- private static String sVersion = null;
-
- private static final Pattern mWildcardPattern = Pattern.compile("^.*$");
-
- /**
- * A coordinate must be of the following form for Google Maps to correctly use it:
- * Latitude, Longitude
- *
- * This may be in decimal form:
- * Latitude: {-90 to 90}
- * Longitude: {-180 to 180}
- *
- * Or, in degrees, minutes, and seconds:
- * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
- * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
- * + or - degrees may also be represented with N or n, S or s for latitude, and with
- * E or e, W or w for longitude, where the direction may either precede or follow the value.
- *
- * Some examples of coordinates that will be accepted by the regex:
- * 37.422081°, -122.084576°
- * 37.422081,-122.084576
- * +37°25'19.49", -122°5'4.47"
- * 37°25'19.49"N, 122°5'4.47"W
- * N 37° 25' 19.49", W 122° 5' 4.47"
- **/
- private static final String COORD_DEGREES_LATITUDE =
- "([-+NnSs]" + "(\\s)*)?"
- + "[1-9]?[0-9](\u00B0)" + "(\\s)*"
- + "([1-5]?[0-9]\')?" + "(\\s)*"
- + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
- + "((\\s)*" + "[NnSs])?";
- private static final String COORD_DEGREES_LONGITUDE =
- "([-+EeWw]" + "(\\s)*)?"
- + "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*"
- + "([1-5]?[0-9]\')?" + "(\\s)*"
- + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
- + "((\\s)*" + "[EeWw])?";
- private static final String COORD_DEGREES_PATTERN =
- COORD_DEGREES_LATITUDE
- + "(\\s)*" + "," + "(\\s)*"
- + COORD_DEGREES_LONGITUDE;
- private static final String COORD_DECIMAL_LATITUDE =
- "[+-]?"
- + "[1-9]?[0-9]" + "(\\.[0-9]+)"
- + "(\u00B0)?";
- private static final String COORD_DECIMAL_LONGITUDE =
- "[+-]?"
- + "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)"
- + "(\u00B0)?";
- private static final String COORD_DECIMAL_PATTERN =
- COORD_DECIMAL_LATITUDE
- + "(\\s)*" + "," + "(\\s)*"
- + COORD_DECIMAL_LONGITUDE;
- private static final Pattern COORD_PATTERN =
- Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN);
-
- private static final String NANP_ALLOWED_SYMBOLS = "()+-*#.";
- private static final int NANP_MIN_DIGITS = 7;
- private static final int NANP_MAX_DIGITS = 11;
-
-
- /**
- * Returns whether the SDK is the Jellybean release or later.
- */
- public static boolean isJellybeanOrLater() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
- }
-
- /**
- * Returns whether the SDK is the KeyLimePie release or later.
- */
- public static boolean isKeyLimePieOrLater() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
- }
-
- public static int getViewTypeFromIntentAndSharedPref(Activity activity) {
- Intent intent = activity.getIntent();
- Bundle extras = intent.getExtras();
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(activity);
-
- if (TextUtils.equals(intent.getAction(), Intent.ACTION_EDIT)) {
- return ViewType.EDIT;
- }
- if (extras != null) {
- if (extras.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
- // This is the "detail" view which is either agenda or day view
- return prefs.getInt(GeneralPreferences.KEY_DETAILED_VIEW,
- GeneralPreferences.DEFAULT_DETAILED_VIEW);
- } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras.getString(INTENT_KEY_VIEW_TYPE))) {
- // Not sure who uses this. This logic came from LaunchActivity
- return ViewType.DAY;
- }
- }
-
- // Default to the last view
- return prefs.getInt(
- GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
- }
-
- /**
- * Gets the intent action for telling the widget to update.
- */
- public static String getWidgetUpdateAction(Context context) {
- return context.getPackageName() + ".APPWIDGET_UPDATE";
- }
-
- /**
- * Gets the intent action for telling the widget to update.
- */
- public static String getWidgetScheduledUpdateAction(Context context) {
- return context.getPackageName() + ".APPWIDGET_SCHEDULED_UPDATE";
- }
-
- /**
- * Writes a new home time zone to the db. Updates the home time zone in the
- * db asynchronously and updates the local cache. Sending a time zone of
- * **tbd** will cause it to be set to the device's time zone. null or empty
- * tz will be ignored.
- *
- * @param context The calling activity
- * @param timeZone The time zone to set Calendar to, or **tbd**
- */
- public static void setTimeZone(Context context, String timeZone) {
- mTZUtils.setTimeZone(context, timeZone);
- }
-
- /**
- * Gets the time zone that Calendar should be displayed in This is a helper
- * method to get the appropriate time zone for Calendar. If this is the
- * first time this method has been called it will initiate an asynchronous
- * query to verify that the data in preferences is correct. The callback
- * supplied will only be called if this query returns a value other than
- * what is stored in preferences and should cause the calling activity to
- * refresh anything that depends on calling this method.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns new
- * values
- * @return The string value representing the time zone Calendar should
- * display
- */
- public static String getTimeZone(Context context, Runnable callback) {
- return mTZUtils.getTimeZone(context, callback);
- }
-
- /**
- * Formats a date or a time range according to the local conventions.
- *
- * @param context the context is required only if the time is shown
- * @param startMillis the start time in UTC milliseconds
- * @param endMillis the end time in UTC milliseconds
- * @param flags a bit mask of options See {@link DateUtils#formatDateRange(Context, Formatter,
- * long, long, int, String) formatDateRange}
- * @return a string containing the formatted date/time range.
- */
- public static String formatDateRange(
- Context context, long startMillis, long endMillis, int flags) {
- return mTZUtils.formatDateRange(context, startMillis, endMillis, flags);
- }
-
- public static boolean getDefaultVibrate(Context context, SharedPreferences prefs) {
- boolean vibrate;
- if (prefs.contains(KEY_ALERTS_VIBRATE_WHEN)) {
- // Migrate setting to new 4.2 behavior
- //
- // silent and never -> off
- // always -> on
- String vibrateWhen = prefs.getString(KEY_ALERTS_VIBRATE_WHEN, null);
- vibrate = vibrateWhen != null && vibrateWhen.equals(context
- .getString(R.string.prefDefault_alerts_vibrate_true));
- prefs.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit();
- Log.d(TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" + vibrateWhen
- + ") to KEY_ALERTS_VIBRATE = " + vibrate);
- } else {
- vibrate = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE,
- false);
- }
- return vibrate;
- }
-
- public static String[] getSharedPreference(Context context, String key, String[] defaultValue) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- Set<String> ss = prefs.getStringSet(key, null);
- if (ss != null) {
- String strings[] = new String[ss.size()];
- return ss.toArray(strings);
- }
- return defaultValue;
- }
-
- public static String getSharedPreference(Context context, String key, String defaultValue) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getString(key, defaultValue);
- }
-
- public static int getSharedPreference(Context context, String key, int defaultValue) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getInt(key, defaultValue);
- }
-
- public static boolean getSharedPreference(Context context, String key, boolean defaultValue) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getBoolean(key, defaultValue);
- }
-
- /**
- * Asynchronously sets the preference with the given key to the given value
- *
- * @param context the context to use to get preferences from
- * @param key the key of the preference to set
- * @param value the value to set
- */
- public static void setSharedPreference(Context context, String key, String value) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- prefs.edit().putString(key, value).apply();
- }
-
- public static void setSharedPreference(Context context, String key, String[] values) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- LinkedHashSet<String> set = new LinkedHashSet<String>();
- for (String value : values) {
- set.add(value);
- }
- prefs.edit().putStringSet(key, set).apply();
- }
-
- protected static void tardis() {
- mTardis = System.currentTimeMillis();
- }
-
- protected static long getTardis() {
- return mTardis;
- }
-
- public static void setSharedPreference(Context context, String key, boolean value) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(key, value);
- editor.apply();
- }
-
- static void setSharedPreference(Context context, String key, int value) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(key, value);
- editor.apply();
- }
-
- public static void removeSharedPreference(Context context, String key) {
- SharedPreferences prefs = context.getSharedPreferences(
- GeneralPreferences.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- prefs.edit().remove(key).apply();
- }
-
- /**
- * Save default agenda/day/week/month view for next time
- *
- * @param context
- * @param viewId {@link CalendarController.ViewType}
- */
- static void setDefaultView(Context context, int viewId) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
-
- boolean validDetailView = false;
- if (mAllowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
- validDetailView = true;
- } else {
- validDetailView = viewId == CalendarController.ViewType.AGENDA
- || viewId == CalendarController.ViewType.DAY;
- }
-
- if (validDetailView) {
- // Record the detail start view
- editor.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId);
- }
-
- // Record the (new) start view
- editor.putInt(GeneralPreferences.KEY_START_VIEW, viewId);
- editor.apply();
- }
-
- public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
- if (cursor == null) {
- return null;
- }
-
- String[] columnNames = cursor.getColumnNames();
- if (columnNames == null) {
- columnNames = new String[] {};
- }
- MatrixCursor newCursor = new MatrixCursor(columnNames);
- int numColumns = cursor.getColumnCount();
- String data[] = new String[numColumns];
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- for (int i = 0; i < numColumns; i++) {
- data[i] = cursor.getString(i);
- }
- newCursor.addRow(data);
- }
- return newCursor;
- }
-
- /**
- * Compares two cursors to see if they contain the same data.
- *
- * @return Returns true of the cursors contain the same data and are not
- * null, false otherwise
- */
- public static boolean compareCursors(Cursor c1, Cursor c2) {
- if (c1 == null || c2 == null) {
- return false;
- }
-
- int numColumns = c1.getColumnCount();
- if (numColumns != c2.getColumnCount()) {
- return false;
- }
-
- if (c1.getCount() != c2.getCount()) {
- return false;
- }
-
- c1.moveToPosition(-1);
- c2.moveToPosition(-1);
- while (c1.moveToNext() && c2.moveToNext()) {
- for (int i = 0; i < numColumns; i++) {
- if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * If the given intent specifies a time (in milliseconds since the epoch),
- * then that time is returned. Otherwise, the current time is returned.
- */
- public static final long timeFromIntentInMillis(Intent intent) {
- // If the time was specified, then use that. Otherwise, use the current
- // time.
- Uri data = intent.getData();
- long millis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1);
- if (millis == -1 && data != null && data.isHierarchical()) {
- List<String> path = data.getPathSegments();
- if (path.size() == 2 && path.get(0).equals("time")) {
- try {
- millis = Long.valueOf(data.getLastPathSegment());
- } catch (NumberFormatException e) {
- Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time "
- + "found. Using current time.");
- }
- }
- }
- if (millis <= 0) {
- millis = System.currentTimeMillis();
- }
- return millis;
- }
-
- /**
- * Formats the given Time object so that it gives the month and year (for
- * example, "September 2007").
- *
- * @param time the time to format
- * @return the string containing the weekday and the date
- */
- public static String formatMonthYear(Context context, Time time) {
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR;
- long millis = time.toMillis(true);
- return formatDateRange(context, millis, millis, flags);
- }
-
- /**
- * Returns a list joined together by the provided delimiter, for example,
- * ["a", "b", "c"] could be joined into "a,b,c"
- *
- * @param things the things to join together
- * @param delim the delimiter to use
- * @return a string contained the things joined together
- */
- public static String join(List<?> things, String delim) {
- StringBuilder builder = new StringBuilder();
- boolean first = true;
- for (Object thing : things) {
- if (first) {
- first = false;
- } else {
- builder.append(delim);
- }
- builder.append(thing.toString());
- }
- return builder.toString();
- }
-
- /**
- * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
- * adjusted for first day of week.
- *
- * This takes a julian day and the week start day and calculates which
- * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting
- * at 0. *Do not* use this to compute the ISO week number for the year.
- *
- * @param julianDay The julian day to calculate the week number for
- * @param firstDayOfWeek Which week day is the first day of the week,
- * see {@link Time#SUNDAY}
- * @return Weeks since the epoch
- */
- public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
- int diff = Time.THURSDAY - firstDayOfWeek;
- if (diff < 0) {
- diff += 7;
- }
- int refDay = Time.EPOCH_JULIAN_DAY - diff;
- return (julianDay - refDay) / 7;
- }
-
- /**
- * Takes a number of weeks since the epoch and calculates the Julian day of
- * the Monday for that week.
- *
- * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY}
- * is considered week 0. It returns the Julian day for the Monday
- * {@code week} weeks after the Monday of the week containing the epoch.
- *
- * @param week Number of weeks since the epoch
- * @return The julian day for the Monday of the given week since the epoch
- */
- public static int getJulianMondayFromWeeksSinceEpoch(int week) {
- return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
- }
-
- /**
- * Get first day of week as android.text.format.Time constant.
- *
- * @return the first day of week in android.text.format.Time
- */
- public static int getFirstDayOfWeek(Context context) {
- SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- String pref = prefs.getString(
- GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT);
-
- int startDay;
- if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
- startDay = Calendar.getInstance().getFirstDayOfWeek();
- } else {
- startDay = Integer.parseInt(pref);
- }
-
- if (startDay == Calendar.SATURDAY) {
- return Time.SATURDAY;
- } else if (startDay == Calendar.MONDAY) {
- return Time.MONDAY;
- } else {
- return Time.SUNDAY;
- }
- }
-
- /**
- * Get first day of week as java.util.Calendar constant.
- *
- * @return the first day of week as a java.util.Calendar constant
- */
- public static int getFirstDayOfWeekAsCalendar(Context context) {
- return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context));
- }
-
- /**
- * Converts the day of the week from android.text.format.Time to java.util.Calendar
- */
- public static int convertDayOfWeekFromTimeToCalendar(int timeDayOfWeek) {
- switch (timeDayOfWeek) {
- case Time.MONDAY:
- return Calendar.MONDAY;
- case Time.TUESDAY:
- return Calendar.TUESDAY;
- case Time.WEDNESDAY:
- return Calendar.WEDNESDAY;
- case Time.THURSDAY:
- return Calendar.THURSDAY;
- case Time.FRIDAY:
- return Calendar.FRIDAY;
- case Time.SATURDAY:
- return Calendar.SATURDAY;
- case Time.SUNDAY:
- return Calendar.SUNDAY;
- default:
- throw new IllegalArgumentException("Argument must be between Time.SUNDAY and " +
- "Time.SATURDAY");
- }
- }
-
- /**
- * @return true when week number should be shown.
- */
- public static boolean getShowWeekNumber(Context context) {
- final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getBoolean(
- GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM);
- }
-
- /**
- * @return true when declined events should be hidden.
- */
- public static boolean getHideDeclinedEvents(Context context) {
- final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false);
- }
-
- public static int getDaysPerWeek(Context context) {
- final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
- return prefs.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7);
- }
-
- /**
- * Determine whether the column position is Saturday or not.
- *
- * @param column the column position
- * @param firstDayOfWeek the first day of week in android.text.format.Time
- * @return true if the column is Saturday position
- */
- public static boolean isSaturday(int column, int firstDayOfWeek) {
- return (firstDayOfWeek == Time.SUNDAY && column == 6)
- || (firstDayOfWeek == Time.MONDAY && column == 5)
- || (firstDayOfWeek == Time.SATURDAY && column == 0);
- }
-
- /**
- * Determine whether the column position is Sunday or not.
- *
- * @param column the column position
- * @param firstDayOfWeek the first day of week in android.text.format.Time
- * @return true if the column is Sunday position
- */
- public static boolean isSunday(int column, int firstDayOfWeek) {
- return (firstDayOfWeek == Time.SUNDAY && column == 0)
- || (firstDayOfWeek == Time.MONDAY && column == 6)
- || (firstDayOfWeek == Time.SATURDAY && column == 1);
- }
-
- /**
- * Convert given UTC time into current local time. This assumes it is for an
- * allday event and will adjust the time to be on a midnight boundary.
- *
- * @param recycle Time object to recycle, otherwise null.
- * @param utcTime Time to convert, in UTC.
- * @param tz The time zone to convert this time to.
- */
- public static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz) {
- if (recycle == null) {
- recycle = new Time();
- }
- recycle.timezone = Time.TIMEZONE_UTC;
- recycle.set(utcTime);
- recycle.timezone = tz;
- return recycle.normalize(true);
- }
-
- public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) {
- if (recycle == null) {
- recycle = new Time();
- }
- recycle.timezone = tz;
- recycle.set(localTime);
- recycle.timezone = Time.TIMEZONE_UTC;
- return recycle.normalize(true);
- }
-
- /**
- * Finds and returns the next midnight after "theTime" in milliseconds UTC
- *
- * @param recycle - Time object to recycle, otherwise null.
- * @param theTime - Time used for calculations (in UTC)
- * @param tz The time zone to convert this time to.
- */
- public static long getNextMidnight(Time recycle, long theTime, String tz) {
- if (recycle == null) {
- recycle = new Time();
- }
- recycle.timezone = tz;
- recycle.set(theTime);
- recycle.monthDay ++;
- recycle.hour = 0;
- recycle.minute = 0;
- recycle.second = 0;
- return recycle.normalize(true);
- }
-
- public static void setAllowWeekForDetailView(boolean allowWeekView) {
- mAllowWeekForDetailView = allowWeekView;
- }
-
- public static boolean getAllowWeekForDetailView() {
- return mAllowWeekForDetailView;
- }
-
- public static boolean getConfigBool(Context c, int key) {
- return c.getResources().getBoolean(key);
- }
-
- /**
- * For devices with Jellybean or later, darkens the given color to ensure that white text is
- * clearly visible on top of it. For devices prior to Jellybean, does nothing, as the
- * sync adapter handles the color change.
- *
- * @param color
- */
- public static int getDisplayColorFromColor(int color) {
- if (!isJellybeanOrLater()) {
- return color;
- }
-
- float[] hsv = new float[3];
- Color.colorToHSV(color, hsv);
- hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f);
- hsv[2] = hsv[2] * INTENSITY_ADJUST;
- return Color.HSVToColor(hsv);
- }
-
- // This takes a color and computes what it would look like blended with
- // white. The result is the color that should be used for declined events.
- public static int getDeclinedColorFromColor(int color) {
- int bg = 0xffffffff;
- int a = DECLINED_EVENT_ALPHA;
- int r = (((color & 0x00ff0000) * a) + ((bg & 0x00ff0000) * (0xff - a))) & 0xff000000;
- int g = (((color & 0x0000ff00) * a) + ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000;
- int b = (((color & 0x000000ff) * a) + ((bg & 0x000000ff) * (0xff - a))) & 0x0000ff00;
- return (0xff000000) | ((r | g | b) >> 8);
- }
-
- public static void trySyncAndDisableUpgradeReceiver(Context context) {
- final PackageManager pm = context.getPackageManager();
- ComponentName upgradeComponent = new ComponentName(context, UpgradeReceiver.class);
- if (pm.getComponentEnabledSetting(upgradeComponent) ==
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
- // The upgrade receiver has been disabled, which means this code has been run before,
- // so no need to sync.
- return;
- }
-
- Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
- ContentResolver.requestSync(
- null /* no account */,
- Calendars.CONTENT_URI.getAuthority(),
- extras);
-
- // Now unregister the receiver so that we won't continue to sync every time.
- pm.setComponentEnabledSetting(upgradeComponent,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
- }
-
- // A single strand represents one color of events. Events are divided up by
- // color to make them convenient to draw. The black strand is special in
- // that it holds conflicting events as well as color settings for allday on
- // each day.
- public static class DNAStrand {
- public float[] points;
- public int[] allDays; // color for the allday, 0 means no event
- int position;
- public int color;
- int count;
- }
-
- // A segment is a single continuous length of time occupied by a single
- // color. Segments should never span multiple days.
- private static class DNASegment {
- int startMinute; // in minutes since the start of the week
- int endMinute;
- int color; // Calendar color or black for conflicts
- int day; // quick reference to the day this segment is on
- }
-
- /**
- * Converts a list of events to a list of segments to draw. Assumes list is
- * ordered by start time of the events. The function processes events for a
- * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1.
- * The algorithm goes over all the events and creates a set of segments
- * ordered by start time. This list of segments is then converted into a
- * HashMap of strands which contain the draw points and are organized by
- * color. The strands can then be drawn by setting the paint color to each
- * strand's color and calling drawLines on its set of points. The points are
- * set up using the following parameters.
- * <ul>
- * <li>Events between midnight and WORK_DAY_START_MINUTES are compressed
- * into the first 1/8th of the space between top and bottom.</li>
- * <li>Events between WORK_DAY_END_MINUTES and the following midnight are
- * compressed into the last 1/8th of the space between top and bottom</li>
- * <li>Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use
- * the remaining 3/4ths of the space</li>
- * <li>All segments drawn will maintain at least minPixels height, except
- * for conflicts in the first or last 1/8th, which may be smaller</li>
- * </ul>
- *
- * @param firstJulianDay The julian day of the first day of events
- * @param events A list of events sorted by start time
- * @param top The lowest y value the dna should be drawn at
- * @param bottom The highest y value the dna should be drawn at
- * @param dayXs An array of x values to draw the dna at, one for each day
- * @param conflictColor the color to use for conflicts
- * @return
- */
- public static HashMap<Integer, DNAStrand> createDNAStrands(int firstJulianDay,
- ArrayList<Event> events, int top, int bottom, int minPixels, int[] dayXs,
- Context context) {
-
- if (!mMinutesLoaded) {
- if (context == null) {
- Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA.");
- }
- Resources res = context.getResources();
- CONFLICT_COLOR = res.getColor(R.color.month_dna_conflict_time_color);
- WORK_DAY_START_MINUTES = res.getInteger(R.integer.work_start_minutes);
- WORK_DAY_END_MINUTES = res.getInteger(R.integer.work_end_minutes);
- WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES;
- WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES;
- mMinutesLoaded = true;
- }
-
- if (events == null || events.isEmpty() || dayXs == null || dayXs.length < 1
- || bottom - top < 8 || minPixels < 0) {
- Log.e(TAG,
- "Bad values for createDNAStrands! events:" + events + " dayXs:"
- + Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:"
- + minPixels);
- return null;
- }
-
- LinkedList<DNASegment> segments = new LinkedList<DNASegment>();
- HashMap<Integer, DNAStrand> strands = new HashMap<Integer, DNAStrand>();
- // add a black strand by default, other colors will get added in
- // the loop
- DNAStrand blackStrand = new DNAStrand();
- blackStrand.color = CONFLICT_COLOR;
- strands.put(CONFLICT_COLOR, blackStrand);
- // the min length is the number of minutes that will occupy
- // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the
- // minutes/pixel * minpx where the number of pixels are 3/4 the total
- // dna height: 4*(mins/(px * 3/4))
- int minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top));
-
- // There are slightly fewer than half as many pixels in 1/6 the space,
- // so round to 2.5x for the min minutes in the non-work area
- int minOtherMinutes = minMinutes * 5 / 2;
- int lastJulianDay = firstJulianDay + dayXs.length - 1;
-
- Event event = new Event();
- // Go through all the events for the week
- for (Event currEvent : events) {
- // if this event is outside the weeks range skip it
- if (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay) {
- continue;
- }
- if (currEvent.drawAsAllday()) {
- addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.length);
- continue;
- }
- // Copy the event over so we can clip its start and end to our range
- currEvent.copyTo(event);
- if (event.startDay < firstJulianDay) {
- event.startDay = firstJulianDay;
- event.startTime = 0;
- }
- // If it starts after the work day make sure the start is at least
- // minPixels from midnight
- if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) {
- event.startTime = DAY_IN_MINUTES - minOtherMinutes;
- }
- if (event.endDay > lastJulianDay) {
- event.endDay = lastJulianDay;
- event.endTime = DAY_IN_MINUTES - 1;
- }
- // If the end time is before the work day make sure it ends at least
- // minPixels after midnight
- if (event.endTime < minOtherMinutes) {
- event.endTime = minOtherMinutes;
- }
- // If the start and end are on the same day make sure they are at
- // least minPixels apart. This only needs to be done for times
- // outside the work day as the min distance for within the work day
- // is enforced in the segment code.
- if (event.startDay == event.endDay &&
- event.endTime - event.startTime < minOtherMinutes) {
- // If it's less than minPixels in an area before the work
- // day
- if (event.startTime < WORK_DAY_START_MINUTES) {
- // extend the end to the first easy guarantee that it's
- // minPixels
- event.endTime = Math.min(event.startTime + minOtherMinutes,
- WORK_DAY_START_MINUTES + minMinutes);
- // if it's in the area after the work day
- } else if (event.endTime > WORK_DAY_END_MINUTES) {
- // First try shifting the end but not past midnight
- event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1);
- // if it's still too small move the start back
- if (event.endTime - event.startTime < minOtherMinutes) {
- event.startTime = event.endTime - minOtherMinutes;
- }
- }
- }
-
- // This handles adding the first segment
- if (segments.size() == 0) {
- addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes);
- continue;
- }
- // Now compare our current start time to the end time of the last
- // segment in the list
- DNASegment lastSegment = segments.getLast();
- int startMinute = (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime;
- int endMinute = Math.max((event.endDay - firstJulianDay) * DAY_IN_MINUTES
- + event.endTime, startMinute + minMinutes);
-
- if (startMinute < 0) {
- startMinute = 0;
- }
- if (endMinute >= WEEK_IN_MINUTES) {
- endMinute = WEEK_IN_MINUTES - 1;
- }
- // If we start before the last segment in the list ends we need to
- // start going through the list as this may conflict with other
- // events
- if (startMinute < lastSegment.endMinute) {
- int i = segments.size();
- // find the last segment this event intersects with
- while (--i >= 0 && endMinute < segments.get(i).startMinute);
-
- DNASegment currSegment;
- // for each segment this event intersects with
- for (; i >= 0 && startMinute <= (currSegment = segments.get(i)).endMinute; i--) {
- // if the segment is already a conflict ignore it
- if (currSegment.color == CONFLICT_COLOR) {
- continue;
- }
- // if the event ends before the segment and wouldn't create
- // a segment that is too small split off the right side
- if (endMinute < currSegment.endMinute - minMinutes) {
- DNASegment rhs = new DNASegment();
- rhs.endMinute = currSegment.endMinute;
- rhs.color = currSegment.color;
- rhs.startMinute = endMinute + 1;
- rhs.day = currSegment.day;
- currSegment.endMinute = endMinute;
- segments.add(i + 1, rhs);
- strands.get(rhs.color).count++;
- if (DEBUG) {
- Log.d(TAG, "Added rhs, curr:" + currSegment.toString() + " i:"
- + segments.get(i).toString());
- }
- }
- // if the event starts after the segment and wouldn't create
- // a segment that is too small split off the left side
- if (startMinute > currSegment.startMinute + minMinutes) {
- DNASegment lhs = new DNASegment();
- lhs.startMinute = currSegment.startMinute;
- lhs.color = currSegment.color;
- lhs.endMinute = startMinute - 1;
- lhs.day = currSegment.day;
- currSegment.startMinute = startMinute;
- // increment i so that we are at the right position when
- // referencing the segments to the right and left of the
- // current segment.
- segments.add(i++, lhs);
- strands.get(lhs.color).count++;
- if (DEBUG) {
- Log.d(TAG, "Added lhs, curr:" + currSegment.toString() + " i:"
- + segments.get(i).toString());
- }
- }
- // if the right side is black merge this with the segment to
- // the right if they're on the same day and overlap
- if (i + 1 < segments.size()) {
- DNASegment rhs = segments.get(i + 1);
- if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day
- && rhs.startMinute <= currSegment.endMinute + 1) {
- rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute);
- segments.remove(currSegment);
- strands.get(currSegment.color).count--;
- // point at the new current segment
- currSegment = rhs;
- }
- }
- // if the left side is black merge this with the segment to
- // the left if they're on the same day and overlap
- if (i - 1 >= 0) {
- DNASegment lhs = segments.get(i - 1);
- if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day
- && lhs.endMinute >= currSegment.startMinute - 1) {
- lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute);
- segments.remove(currSegment);
- strands.get(currSegment.color).count--;
- // point at the new current segment
- currSegment = lhs;
- // point i at the new current segment in case new
- // code is added
- i--;
- }
- }
- // if we're still not black, decrement the count for the
- // color being removed, change this to black, and increment
- // the black count
- if (currSegment.color != CONFLICT_COLOR) {
- strands.get(currSegment.color).count--;
- currSegment.color = CONFLICT_COLOR;
- strands.get(CONFLICT_COLOR).count++;
- }
- }
-
- }
- // If this event extends beyond the last segment add a new segment
- if (endMinute > lastSegment.endMinute) {
- addNewSegment(segments, event, strands, firstJulianDay, lastSegment.endMinute,
- minMinutes);
- }
- }
- weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs);
- return strands;
- }
-
- // This figures out allDay colors as allDay events are found
- private static void addAllDayToStrands(Event event, HashMap<Integer, DNAStrand> strands,
- int firstJulianDay, int numDays) {
- DNAStrand strand = getOrCreateStrand(strands, CONFLICT_COLOR);
- // if we haven't initialized the allDay portion create it now
- if (strand.allDays == null) {
- strand.allDays = new int[numDays];
- }
-
- // For each day this event is on update the color
- int end = Math.min(event.endDay - firstJulianDay, numDays - 1);
- for (int i = Math.max(event.startDay - firstJulianDay, 0); i <= end; i++) {
- if (strand.allDays[i] != 0) {
- // if this day already had a color, it is now a conflict
- strand.allDays[i] = CONFLICT_COLOR;
- } else {
- // else it's just the color of the event
- strand.allDays[i] = event.color;
- }
- }
- }
-
- // This processes all the segments, sorts them by color, and generates a
- // list of points to draw
- private static void weaveDNAStrands(LinkedList<DNASegment> segments, int firstJulianDay,
- HashMap<Integer, DNAStrand> strands, int top, int bottom, int[] dayXs) {
- // First, get rid of any colors that ended up with no segments
- Iterator<DNAStrand> strandIterator = strands.values().iterator();
- while (strandIterator.hasNext()) {
- DNAStrand strand = strandIterator.next();
- if (strand.count < 1 && strand.allDays == null) {
- strandIterator.remove();
- continue;
- }
- strand.points = new float[strand.count * 4];
- strand.position = 0;
- }
- // Go through each segment and compute its points
- for (DNASegment segment : segments) {
- // Add the points to the strand of that color
- DNAStrand strand = strands.get(segment.color);
- int dayIndex = segment.day - firstJulianDay;
- int dayStartMinute = segment.startMinute % DAY_IN_MINUTES;
- int dayEndMinute = segment.endMinute % DAY_IN_MINUTES;
- int height = bottom - top;
- int workDayHeight = height * 3 / 4;
- int remainderHeight = (height - workDayHeight) / 2;
-
- int x = dayXs[dayIndex];
- int y0 = 0;
- int y1 = 0;
-
- y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight);
- y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight);
- if (DEBUG) {
- Log.d(TAG, "Adding " + Integer.toHexString(segment.color) + " at x,y0,y1: " + x
- + " " + y0 + " " + y1 + " for " + dayStartMinute + " " + dayEndMinute);
- }
- strand.points[strand.position++] = x;
- strand.points[strand.position++] = y0;
- strand.points[strand.position++] = x;
- strand.points[strand.position++] = y1;
- }
- }
-
- /**
- * Compute a pixel offset from the top for a given minute from the work day
- * height and the height of the top area.
- */
- private static int getPixelOffsetFromMinutes(int minute, int workDayHeight,
- int remainderHeight) {
- int y;
- if (minute < WORK_DAY_START_MINUTES) {
- y = minute * remainderHeight / WORK_DAY_START_MINUTES;
- } else if (minute < WORK_DAY_END_MINUTES) {
- y = remainderHeight + (minute - WORK_DAY_START_MINUTES) * workDayHeight
- / WORK_DAY_MINUTES;
- } else {
- y = remainderHeight + workDayHeight + (minute - WORK_DAY_END_MINUTES) * remainderHeight
- / WORK_DAY_END_LENGTH;
- }
- return y;
- }
-
- /**
- * Add a new segment based on the event provided. This will handle splitting
- * segments across day boundaries and ensures a minimum size for segments.
- */
- private static void addNewSegment(LinkedList<DNASegment> segments, Event event,
- HashMap<Integer, DNAStrand> strands, int firstJulianDay, int minStart, int minMinutes) {
- if (event.startDay > event.endDay) {
- Log.wtf(TAG, "Event starts after it ends: " + event.toString());
- }
- // If this is a multiday event split it up by day
- if (event.startDay != event.endDay) {
- Event lhs = new Event();
- lhs.color = event.color;
- lhs.startDay = event.startDay;
- // the first day we want the start time to be the actual start time
- lhs.startTime = event.startTime;
- lhs.endDay = lhs.startDay;
- lhs.endTime = DAY_IN_MINUTES - 1;
- // Nearly recursive iteration!
- while (lhs.startDay != event.endDay) {
- addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes);
- // The days in between are all day, even though that shouldn't
- // actually happen due to the allday filtering
- lhs.startDay++;
- lhs.endDay = lhs.startDay;
- lhs.startTime = 0;
- minStart = 0;
- }
- // The last day we want the end time to be the actual end time
- lhs.endTime = event.endTime;
- event = lhs;
- }
- // Create the new segment and compute its fields
- DNASegment segment = new DNASegment();
- int dayOffset = (event.startDay - firstJulianDay) * DAY_IN_MINUTES;
- int endOfDay = dayOffset + DAY_IN_MINUTES - 1;
- // clip the start if needed
- segment.startMinute = Math.max(dayOffset + event.startTime, minStart);
- // and extend the end if it's too small, but not beyond the end of the
- // day
- int minEnd = Math.min(segment.startMinute + minMinutes, endOfDay);
- segment.endMinute = Math.max(dayOffset + event.endTime, minEnd);
- if (segment.endMinute > endOfDay) {
- segment.endMinute = endOfDay;
- }
-
- segment.color = event.color;
- segment.day = event.startDay;
- segments.add(segment);
- // increment the count for the correct color or add a new strand if we
- // don't have that color yet
- DNAStrand strand = getOrCreateStrand(strands, segment.color);
- strand.count++;
- }
-
- /**
- * Try to get a strand of the given color. Create it if it doesn't exist.
- */
- private static DNAStrand getOrCreateStrand(HashMap<Integer, DNAStrand> strands, int color) {
- DNAStrand strand = strands.get(color);
- if (strand == null) {
- strand = new DNAStrand();
- strand.color = color;
- strand.count = 0;
- strands.put(strand.color, strand);
- }
- return strand;
- }
-
- /**
- * Sends an intent to launch the top level Calendar view.
- *
- * @param context
- */
- public static void returnToCalendarHome(Context context) {
- Intent launchIntent = new Intent(context, AllInOneActivity.class);
- launchIntent.setAction(Intent.ACTION_DEFAULT);
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- launchIntent.putExtra(INTENT_KEY_HOME, true);
- context.startActivity(launchIntent);
- }
-
- /**
- * Given a context and a time in millis since unix epoch figures out the
- * correct week of the year for that time.
- *
- * @param millisSinceEpoch
- * @return
- */
- public static int getWeekNumberFromTime(long millisSinceEpoch, Context context) {
- Time weekTime = new Time(getTimeZone(context, null));
- weekTime.set(millisSinceEpoch);
- weekTime.normalize(true);
- int firstDayOfWeek = getFirstDayOfWeek(context);
- // if the date is on Saturday or Sunday and the start of the week
- // isn't Monday we may need to shift the date to be in the correct
- // week
- if (weekTime.weekDay == Time.SUNDAY
- && (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)) {
- weekTime.monthDay++;
- weekTime.normalize(true);
- } else if (weekTime.weekDay == Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) {
- weekTime.monthDay += 2;
- weekTime.normalize(true);
- }
- return weekTime.getWeekNumber();
- }
-
- /**
- * Formats a day of the week string. This is either just the name of the day
- * or a combination of yesterday/today/tomorrow and the day of the week.
- *
- * @param julianDay The julian day to get the string for
- * @param todayJulianDay The julian day for today's date
- * @param millis A utc millis since epoch time that falls on julian day
- * @param context The calling context, used to get the timezone and do the
- * formatting
- * @return
- */
- public static String getDayOfWeekString(int julianDay, int todayJulianDay, long millis,
- Context context) {
- getTimeZone(context, null);
- int flags = DateUtils.FORMAT_SHOW_WEEKDAY;
- String dayViewText;
- if (julianDay == todayJulianDay) {
- dayViewText = context.getString(R.string.agenda_today,
- mTZUtils.formatDateRange(context, millis, millis, flags).toString());
- } else if (julianDay == todayJulianDay - 1) {
- dayViewText = context.getString(R.string.agenda_yesterday,
- mTZUtils.formatDateRange(context, millis, millis, flags).toString());
- } else if (julianDay == todayJulianDay + 1) {
- dayViewText = context.getString(R.string.agenda_tomorrow,
- mTZUtils.formatDateRange(context, millis, millis, flags).toString());
- } else {
- dayViewText = mTZUtils.formatDateRange(context, millis, millis, flags).toString();
- }
- dayViewText = dayViewText.toUpperCase();
- return dayViewText;
- }
-
- // Calculate the time until midnight + 1 second and set the handler to
- // do run the runnable
- public static void setMidnightUpdater(Handler h, Runnable r, String timezone) {
- if (h == null || r == null || timezone == null) {
- return;
- }
- long now = System.currentTimeMillis();
- Time time = new Time(timezone);
- time.set(now);
- long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 -
- time.second + 1) * 1000;
- h.removeCallbacks(r);
- h.postDelayed(r, runInMillis);
- }
-
- // Stop the midnight update thread
- public static void resetMidnightUpdater(Handler h, Runnable r) {
- if (h == null || r == null) {
- return;
- }
- h.removeCallbacks(r);
- }
-
- /**
- * Returns a string description of the specified time interval.
- */
- public static String getDisplayedDatetime(long startMillis, long endMillis, long currentMillis,
- String localTimezone, boolean allDay, Context context) {
- // Configure date/time formatting.
- int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
- int flagsTime = DateUtils.FORMAT_SHOW_TIME;
- if (DateFormat.is24HourFormat(context)) {
- flagsTime |= DateUtils.FORMAT_24HOUR;
- }
-
- Time currentTime = new Time(localTimezone);
- currentTime.set(currentMillis);
- Resources resources = context.getResources();
- String datetimeString = null;
- if (allDay) {
- // All day events require special timezone adjustment.
- long localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone);
- long localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone);
- if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
- // If possible, use "Today" or "Tomorrow" instead of a full date string.
- int todayOrTomorrow = isTodayOrTomorrow(context.getResources(),
- localStartMillis, currentMillis, currentTime.gmtoff);
- if (TODAY == todayOrTomorrow) {
- datetimeString = resources.getString(R.string.today);
- } else if (TOMORROW == todayOrTomorrow) {
- datetimeString = resources.getString(R.string.tomorrow);
- }
- }
- if (datetimeString == null) {
- // For multi-day allday events or single-day all-day events that are not
- // today or tomorrow, use framework formatter.
- Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
- datetimeString = DateUtils.formatDateRange(context, f, startMillis,
- endMillis, flagsDate, Time.TIMEZONE_UTC).toString();
- }
- } else {
- if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
- // Format the time.
- String timeString = Utils.formatDateRange(context, startMillis, endMillis,
- flagsTime);
-
- // If possible, use "Today" or "Tomorrow" instead of a full date string.
- int todayOrTomorrow = isTodayOrTomorrow(context.getResources(), startMillis,
- currentMillis, currentTime.gmtoff);
- if (TODAY == todayOrTomorrow) {
- // Example: "Today at 1:00pm - 2:00 pm"
- datetimeString = resources.getString(R.string.today_at_time_fmt,
- timeString);
- } else if (TOMORROW == todayOrTomorrow) {
- // Example: "Tomorrow at 1:00pm - 2:00 pm"
- datetimeString = resources.getString(R.string.tomorrow_at_time_fmt,
- timeString);
- } else {
- // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
- String dateString = Utils.formatDateRange(context, startMillis, endMillis,
- flagsDate);
- datetimeString = resources.getString(R.string.date_time_fmt, dateString,
- timeString);
- }
- } else {
- // For multiday events, shorten day/month names.
- // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
- int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH |
- DateUtils.FORMAT_ABBREV_WEEKDAY;
- datetimeString = Utils.formatDateRange(context, startMillis, endMillis,
- flagsDatetime);
- }
- }
- return datetimeString;
- }
-
- /**
- * Returns the timezone to display in the event info, if the local timezone is different
- * from the event timezone. Otherwise returns null.
- */
- public static String getDisplayedTimezone(long startMillis, String localTimezone,
- String eventTimezone) {
- String tzDisplay = null;
- if (!TextUtils.equals(localTimezone, eventTimezone)) {
- // Figure out if this is in DST
- TimeZone tz = TimeZone.getTimeZone(localTimezone);
- if (tz == null || tz.getID().equals("GMT")) {
- tzDisplay = localTimezone;
- } else {
- Time startTime = new Time(localTimezone);
- startTime.set(startMillis);
- tzDisplay = tz.getDisplayName(startTime.isDst != 0, TimeZone.SHORT);
- }
- }
- return tzDisplay;
- }
-
- /**
- * Returns whether the specified time interval is in a single day.
- */
- private static boolean singleDayEvent(long startMillis, long endMillis, long localGmtOffset) {
- if (startMillis == endMillis) {
- return true;
- }
-
- // An event ending at midnight should still be a single-day event, so check
- // time end-1.
- int startDay = Time.getJulianDay(startMillis, localGmtOffset);
- int endDay = Time.getJulianDay(endMillis - 1, localGmtOffset);
- return startDay == endDay;
- }
-
- // Using int constants as a return value instead of an enum to minimize resources.
- private static final int TODAY = 1;
- private static final int TOMORROW = 2;
- private static final int NONE = 0;
-
- /**
- * Returns TODAY or TOMORROW if applicable. Otherwise returns NONE.
- */
- private static int isTodayOrTomorrow(Resources r, long dayMillis,
- long currentMillis, long localGmtOffset) {
- int startDay = Time.getJulianDay(dayMillis, localGmtOffset);
- int currentDay = Time.getJulianDay(currentMillis, localGmtOffset);
-
- int days = startDay - currentDay;
- if (days == 1) {
- return TOMORROW;
- } else if (days == 0) {
- return TODAY;
- } else {
- return NONE;
- }
- }
-
- /**
- * 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
- */
- public static void setTodayIcon(LayerDrawable icon, Context c, String timezone) {
- DayOfMonthDrawable today;
-
- // Reuse current drawable if possible
- Drawable currentDrawable = icon.findDrawableByLayerId(R.id.today_icon_day);
- if (currentDrawable != null && currentDrawable instanceof DayOfMonthDrawable) {
- today = (DayOfMonthDrawable)currentDrawable;
- } else {
- today = new DayOfMonthDrawable(c);
- }
- // Set the day and update the icon
- Time now = new Time(timezone);
- now.setToNow();
- now.normalize(false);
- today.setDayOfMonth(now.monthDay);
- icon.mutate();
- icon.setDrawableByLayerId(R.id.today_icon_day, today);
- }
-
- /**
- * Get a list of quick responses used for emailing guests from the
- * SharedPreferences. If not are found, get the hard coded ones that shipped
- * with the app
- *
- * @param context
- * @return a list of quick responses.
- */
- public static String[] getQuickResponses(Context context) {
- String[] s = Utils.getSharedPreference(context, KEY_QUICK_RESPONSES, (String[]) null);
-
- if (s == null) {
- s = context.getResources().getStringArray(R.array.quick_response_defaults);
- }
-
- return s;
- }
-
- /**
- * Return the app version code.
- */
- public static String getVersionCode(Context context) {
- if (sVersion == null) {
- try {
- sVersion = context.getPackageManager().getPackageInfo(
- context.getPackageName(), 0).versionName;
- } catch (PackageManager.NameNotFoundException e) {
- // Can't find version; just leave it blank.
- Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName);
- }
- }
- return sVersion;
- }
-}
diff --git a/src/com/android/calendar/Utils.kt b/src/com/android/calendar/Utils.kt
new file mode 100644
index 00000000..ef780485
--- /dev/null
+++ b/src/com/android/calendar/Utils.kt
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ComponentName
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.CalendarUtils.TimeZoneUtils
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Calendar
+import java.util.Formatter
+import java.util.HashMap
+import java.util.LinkedHashSet
+import java.util.LinkedList
+import java.util.List
+import java.util.Locale
+import java.util.TimeZone
+import java.util.regex.Pattern
+
+object Utils {
+ private const val DEBUG = false
+ private const val TAG = "CalUtils"
+
+ // Set to 0 until we have UI to perform undo
+ const val UNDO_DELAY: Long = 0
+
+ // For recurring events which instances of the series are being modified
+ const val MODIFY_UNINITIALIZED = 0
+ const val MODIFY_SELECTED = 1
+ const val MODIFY_ALL_FOLLOWING = 2
+ const val MODIFY_ALL = 3
+
+ // When the edit event view finishes it passes back the appropriate exit
+ // code.
+ const val DONE_REVERT = 1 shl 0
+ const val DONE_SAVE = 1 shl 1
+ const val DONE_DELETE = 1 shl 2
+
+ // And should re run with DONE_EXIT if it should also leave the view, just
+ // exiting is identical to reverting
+ const val DONE_EXIT = 1 shl 0
+ const val OPEN_EMAIL_MARKER = " <"
+ const val CLOSE_EMAIL_MARKER = ">"
+ const val INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW"
+ const val INTENT_KEY_VIEW_TYPE = "VIEW"
+ const val INTENT_VALUE_VIEW_TYPE_DAY = "DAY"
+ const val INTENT_KEY_HOME = "KEY_HOME"
+ val MONDAY_BEFORE_JULIAN_EPOCH: Int = Time.EPOCH_JULIAN_DAY - 3
+ const val DECLINED_EVENT_ALPHA = 0x66
+ const val DECLINED_EVENT_TEXT_ALPHA = 0xC0
+ private const val SATURATION_ADJUST = 1.3f
+ private const val INTENSITY_ADJUST = 0.8f
+
+ // Defines used by the DNA generation code
+ const val DAY_IN_MINUTES = 60 * 24
+ const val WEEK_IN_MINUTES = DAY_IN_MINUTES * 7
+
+ // The work day is being counted as 6am to 8pm
+ var WORK_DAY_MINUTES = 14 * 60
+ var WORK_DAY_START_MINUTES = 6 * 60
+ var WORK_DAY_END_MINUTES = 20 * 60
+ var WORK_DAY_END_LENGTH = 24 * 60 - WORK_DAY_END_MINUTES
+ var CONFLICT_COLOR = -0x1000000
+ var mMinutesLoaded = false
+ const val YEAR_MIN = 1970
+ const val YEAR_MAX = 2036
+
+ // The name of the shared preferences file. This name must be maintained for
+ // historical
+ // reasons, as it's what PreferenceManager assigned the first time the file
+ // was created.
+ const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+ const val KEY_QUICK_RESPONSES = "preferences_quick_responses"
+ const val KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen"
+ const val APPWIDGET_DATA_TYPE = "vnd.android.data/update"
+ const val MACHINE_GENERATED_ADDRESS = "calendar.google.com"
+ private val mTZUtils: TimeZoneUtils? = TimeZoneUtils(SHARED_PREFS_NAME)
+ @JvmField var allowWeekForDetailView = false
+ internal var tardis: Long = 0
+ private set
+ private var sVersion: String? = null
+ private val mWildcardPattern: Pattern = Pattern.compile("^.*$")
+
+ /**
+ * A coordinate must be of the following form for Google Maps to correctly use it:
+ * Latitude, Longitude
+ *
+ * This may be in decimal form:
+ * Latitude: {-90 to 90}
+ * Longitude: {-180 to 180}
+ *
+ * Or, in degrees, minutes, and seconds:
+ * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
+ * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
+ * + or - degrees may also be represented with N or n, S or s for latitude, and with
+ * E or e, W or w for longitude, where the direction may either precede or follow the value.
+ *
+ * Some examples of coordinates that will be accepted by the regex:
+ * 37.422081°, -122.084576°
+ * 37.422081,-122.084576
+ * +37°25'19.49", -122°5'4.47"
+ * 37°25'19.49"N, 122°5'4.47"W
+ * N 37° 25' 19.49", W 122° 5' 4.47"
+ */
+ private const val COORD_DEGREES_LATITUDE = ("([-+NnSs]" + "(\\s)*)?" +
+ "[1-9]?[0-9](\u00B0)" + "(\\s)*" +
+ "([1-5]?[0-9]\')?" + "(\\s)*" +
+ "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
+ "((\\s)*" + "[NnSs])?")
+ private const val COORD_DEGREES_LONGITUDE = ("([-+EeWw]" + "(\\s)*)?" +
+ "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*" +
+ "([1-5]?[0-9]\')?" + "(\\s)*" +
+ "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
+ "((\\s)*" + "[EeWw])?")
+ private const val COORD_DEGREES_PATTERN = (COORD_DEGREES_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
+ COORD_DEGREES_LONGITUDE)
+ private const val COORD_DECIMAL_LATITUDE = ("[+-]?" +
+ "[1-9]?[0-9]" + "(\\.[0-9]+)" +
+ "(\u00B0)?")
+ private const val COORD_DECIMAL_LONGITUDE = ("[+-]?" +
+ "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)" +
+ "(\u00B0)?")
+ private const val COORD_DECIMAL_PATTERN = (COORD_DECIMAL_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
+ COORD_DECIMAL_LONGITUDE)
+ private val COORD_PATTERN: Pattern =
+ Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN)
+ private const val NANP_ALLOWED_SYMBOLS = "()+-*#."
+ private const val NANP_MIN_DIGITS = 7
+ private const val NANP_MAX_DIGITS = 11
+
+ /**
+ * Returns whether the SDK is the KeyLimePie release or later.
+ */
+ @JvmStatic fun isKeyLimePieOrLater(): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ }
+
+ /**
+ * Returns whether the SDK is the Jellybean release or later.
+ */
+ @JvmStatic fun isJellybeanOrLater(): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+ }
+
+ @JvmStatic fun getViewTypeFromIntentAndSharedPref(activity: Activity): Int {
+ val intent: Intent? = activity.getIntent()
+ val extras: Bundle? = intent?.getExtras()
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(activity)
+ if (TextUtils.equals(intent?.getAction(), Intent.ACTION_EDIT)) {
+ return ViewType.EDIT
+ }
+ if (extras != null) {
+ if (extras?.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
+ // This is the "detail" view which is either agenda or day view
+ return prefs?.getInt(
+ GeneralPreferences.KEY_DETAILED_VIEW,
+ GeneralPreferences.DEFAULT_DETAILED_VIEW
+ ) as Int
+ } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras?.getString(INTENT_KEY_VIEW_TYPE))) {
+ // Not sure who uses this. This logic came from LaunchActivity
+ return ViewType.DAY
+ }
+ }
+
+ // Default to the last view
+ return prefs?.getInt(
+ GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW
+ ) as Int
+ }
+
+ /**
+ * Gets the intent action for telling the widget to update.
+ */
+ @JvmStatic fun getWidgetUpdateAction(context: Context): String {
+ return context.getPackageName().toString() + ".APPWIDGET_UPDATE"
+ }
+
+ /**
+ * Gets the intent action for telling the widget to update.
+ */
+ @JvmStatic fun getWidgetScheduledUpdateAction(context: Context): String {
+ return context.getPackageName().toString() + ".APPWIDGET_SCHEDULED_UPDATE"
+ }
+
+ /**
+ * Writes a new home time zone to the db. Updates the home time zone in the
+ * db asynchronously and updates the local cache. Sending a time zone of
+ * **tbd** will cause it to be set to the device's time zone. null or empty
+ * tz will be ignored.
+ *
+ * @param context The calling activity
+ * @param timeZone The time zone to set Calendar to, or **tbd**
+ */
+ @JvmStatic fun setTimeZone(context: Context?, timeZone: String?) {
+ mTZUtils?.setTimeZone(context as Context, timeZone as String)
+ }
+
+ /**
+ * Gets the time zone that Calendar should be displayed in This is a helper
+ * method to get the appropriate time zone for Calendar. If this is the
+ * first time this method has been called it will initiate an asynchronous
+ * query to verify that the data in preferences is correct. The callback
+ * supplied will only be called if this query returns a value other than
+ * what is stored in preferences and should cause the calling activity to
+ * refresh anything that depends on calling this method.
+ *
+ * @param context The calling activity
+ * @param callback The runnable that should execute if a query returns new
+ * values
+ * @return The string value representing the time zone Calendar should
+ * display
+ */
+ @JvmStatic fun getTimeZone(context: Context?, callback: Runnable?): String? {
+ return mTZUtils?.getTimeZone(context as Context, callback)
+ }
+
+ /**
+ * Formats a date or a time range according to the local conventions.
+ *
+ * @param context the context is required only if the time is shown
+ * @param startMillis the start time in UTC milliseconds
+ * @param endMillis the end time in UTC milliseconds
+ * @param flags a bit mask of options See [formatDateRange][DateUtils.formatDateRange]
+ * @return a string containing the formatted date/time range.
+ */
+ @JvmStatic fun formatDateRange(
+ context: Context?,
+ startMillis: Long,
+ endMillis: Long,
+ flags: Int
+ ): String? {
+ return mTZUtils?.formatDateRange(context as Context, startMillis, endMillis, flags)
+ }
+
+ @JvmStatic fun getDefaultVibrate(context: Context, prefs: SharedPreferences?): Boolean {
+ val vibrate: Boolean
+ if (prefs?.contains(KEY_ALERTS_VIBRATE_WHEN) == true) {
+ // Migrate setting to new 4.2 behavior
+ //
+ // silent and never -> off
+ // always -> on
+ val vibrateWhen: String? = prefs?.getString(KEY_ALERTS_VIBRATE_WHEN, null)
+ vibrate = vibrateWhen != null && vibrateWhen.equals(
+ context
+ .getString(R.string.prefDefault_alerts_vibrate_true)
+ )
+ prefs?.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit()
+ Log.d(
+ TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" +
+ vibrateWhen + ") to KEY_ALERTS_VIBRATE = " + vibrate
+ )
+ } else {
+ vibrate = prefs?.getBoolean(
+ GeneralPreferences.KEY_ALERTS_VIBRATE,
+ false
+ ) as Boolean
+ }
+ return vibrate
+ }
+
+ @JvmStatic fun getSharedPreference(
+ context: Context?,
+ key: String?,
+ defaultValue: Array<String>?
+ ): Array<String>? {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val ss = prefs?.getStringSet(key, null)
+ if (ss != null) {
+ val strings = arrayOfNulls<String>(ss?.size)
+ return ss?.toTypedArray()
+ }
+ return defaultValue
+ }
+
+ @JvmStatic fun getSharedPreference(
+ context: Context?,
+ key: String?,
+ defaultValue: String?
+ ): String? {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getString(key, defaultValue)
+ }
+
+ @JvmStatic fun getSharedPreference(context: Context?, key: String?, defaultValue: Int): Int {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getInt(key, defaultValue) as Int
+ }
+
+ @JvmStatic fun getSharedPreference(
+ context: Context?,
+ key: String?,
+ defaultValue: Boolean
+ ): Boolean {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getBoolean(key, defaultValue) as Boolean
+ }
+
+ /**
+ * Asynchronously sets the preference with the given key to the given value
+ *
+ * @param context the context to use to get preferences from
+ * @param key the key of the preference to set
+ * @param value the value to set
+ */
+ @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: String?) {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ prefs?.edit()?.putString(key, value)?.apply()
+ }
+
+ @JvmStatic fun setSharedPreference(context: Context?, key: String?, values: Array<String?>) {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val set: LinkedHashSet<String?> = LinkedHashSet<String?>()
+ for (value in values) {
+ set.add(value)
+ }
+ prefs?.edit()?.putStringSet(key, set)?.apply()
+ }
+
+ internal fun tardis() {
+ tardis = System.currentTimeMillis()
+ }
+
+ @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Boolean) {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val editor: SharedPreferences.Editor? = prefs?.edit()
+ editor?.putBoolean(key, value)
+ editor?.apply()
+ }
+
+ @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Int) {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val editor: SharedPreferences.Editor? = prefs?.edit()
+ editor?.putInt(key, value)
+ editor?.apply()
+ }
+
+ @JvmStatic fun removeSharedPreference(context: Context?, key: String?) {
+ val prefs: SharedPreferences? = context?.getSharedPreferences(
+ GeneralPreferences.SHARED_PREFS_NAME, Context.MODE_PRIVATE
+ )
+ prefs?.edit()?.remove(key)?.apply()
+ }
+
+ /**
+ * Save default agenda/day/week/month view for next time
+ *
+ * @param context
+ * @param viewId [CalendarController.ViewType]
+ */
+ @JvmStatic fun setDefaultView(context: Context?, viewId: Int) {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val editor: SharedPreferences.Editor? = prefs?.edit()
+ var validDetailView = false
+ validDetailView =
+ if (allowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
+ true
+ } else {
+ (viewId == CalendarController.ViewType.AGENDA ||
+ viewId == CalendarController.ViewType.DAY)
+ }
+ if (validDetailView) {
+ // Record the detail start view
+ editor?.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId)
+ }
+
+ // Record the (new) start view
+ editor?.putInt(GeneralPreferences.KEY_START_VIEW, viewId)
+ editor?.apply()
+ }
+
+ @JvmStatic fun matrixCursorFromCursor(cursor: Cursor?): MatrixCursor? {
+ if (cursor == null) {
+ return null
+ }
+ var columnNames: Array<String?> = cursor.getColumnNames()
+ if (columnNames == null) {
+ columnNames = arrayOf()
+ }
+ val newCursor = MatrixCursor(columnNames)
+ val numColumns: Int = cursor.getColumnCount()
+ val data = arrayOfNulls<String>(numColumns)
+ cursor.moveToPosition(-1)
+ while (cursor.moveToNext()) {
+ for (i in 0 until numColumns) {
+ data[i] = cursor.getString(i)
+ }
+ newCursor.addRow(data)
+ }
+ return newCursor
+ }
+
+ /**
+ * Compares two cursors to see if they contain the same data.
+ *
+ * @return Returns true of the cursors contain the same data and are not
+ * null, false otherwise
+ */
+ @JvmStatic fun compareCursors(c1: Cursor?, c2: Cursor?): Boolean {
+ if (c1 == null || c2 == null) {
+ return false
+ }
+ val numColumns: Int = c1.getColumnCount()
+ if (numColumns != c2.getColumnCount()) {
+ return false
+ }
+ if (c1.getCount() !== c2.getCount()) {
+ return false
+ }
+ c1.moveToPosition(-1)
+ c2.moveToPosition(-1)
+ while (c1.moveToNext() && c2.moveToNext()) {
+ for (i in 0 until numColumns) {
+ if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ /**
+ * If the given intent specifies a time (in milliseconds since the epoch),
+ * then that time is returned. Otherwise, the current time is returned.
+ */
+ @JvmStatic fun timeFromIntentInMillis(intent: Intent?): Long? {
+ // If the time was specified, then use that. Otherwise, use the current
+ // time.
+ val data: Uri? = intent?.getData()
+ var millis: Long? = intent?.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1)?.toLong()
+ if (millis == -1L && data != null && data?.isHierarchical()) {
+ val path: List<String> = data?.getPathSegments() as List<String>
+ if (path.size == 2 && path[0].equals("time")) {
+ try {
+ millis = (data?.getLastPathSegment()?.toLong())
+ } catch (e: NumberFormatException) {
+ Log.i(
+ "Calendar", "timeFromIntentInMillis: Data existed but no valid time " +
+ "found. Using current time."
+ )
+ }
+ }
+ }
+ if ((millis ?: 0L) <= 0) {
+ millis = System.currentTimeMillis()
+ }
+ return millis
+ }
+
+ /**
+ * Formats the given Time object so that it gives the month and year (for
+ * example, "September 2007").
+ *
+ * @param time the time to format
+ * @return the string containing the weekday and the date
+ */
+ @JvmStatic fun formatMonthYear(context: Context?, time: Time): String? {
+ val flags: Int = (DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY
+ or DateUtils.FORMAT_SHOW_YEAR)
+ val millis: Long = time.toMillis(true)
+ return formatDateRange(context, millis, millis, flags)
+ }
+
+ /**
+ * Returns a list joined together by the provided delimiter, for example,
+ * ["a", "b", "c"] could be joined into "a,b,c"
+ *
+ * @param things the things to join together
+ * @param delim the delimiter to use
+ * @return a string contained the things joined together
+ */
+ @JvmStatic fun join(things: List<*>, delim: String?): String {
+ val builder = StringBuilder()
+ var first = true
+ for (thing in things) {
+ if (first) {
+ first = false
+ } else {
+ builder.append(delim)
+ }
+ builder.append(thing.toString())
+ }
+ return builder.toString()
+ }
+
+ /**
+ * Returns the week since [Time.EPOCH_JULIAN_DAY] (Jan 1, 1970)
+ * adjusted for first day of week.
+ *
+ * This takes a julian day and the week start day and calculates which
+ * week since [Time.EPOCH_JULIAN_DAY] that day occurs in, starting
+ * at 0. *Do not* use this to compute the ISO week number for the year.
+ *
+ * @param julianDay The julian day to calculate the week number for
+ * @param firstDayOfWeek Which week day is the first day of the week,
+ * see [Time.SUNDAY]
+ * @return Weeks since the epoch
+ */
+ @JvmStatic fun getWeeksSinceEpochFromJulianDay(julianDay: Int, firstDayOfWeek: Int): Int {
+ var diff: Int = Time.THURSDAY - firstDayOfWeek
+ if (diff < 0) {
+ diff += 7
+ }
+ val refDay: Int = Time.EPOCH_JULIAN_DAY - diff
+ return (julianDay - refDay) / 7
+ }
+
+ /**
+ * Takes a number of weeks since the epoch and calculates the Julian day of
+ * the Monday for that week.
+ *
+ * This assumes that the week containing the [Time.EPOCH_JULIAN_DAY]
+ * is considered week 0. It returns the Julian day for the Monday
+ * `week` weeks after the Monday of the week containing the epoch.
+ *
+ * @param week Number of weeks since the epoch
+ * @return The julian day for the Monday of the given week since the epoch
+ */
+ @JvmStatic fun getJulianMondayFromWeeksSinceEpoch(week: Int): Int {
+ return MONDAY_BEFORE_JULIAN_EPOCH + week * 7
+ }
+
+ /**
+ * Get first day of week as android.text.format.Time constant.
+ *
+ * @return the first day of week in android.text.format.Time
+ */
+ @JvmStatic fun getFirstDayOfWeek(context: Context?): Int {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ val pref: String? = prefs?.getString(
+ GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT
+ )
+ val startDay: Int
+ startDay = if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
+ Calendar.getInstance().getFirstDayOfWeek()
+ } else {
+ Integer.parseInt(pref)
+ }
+ return if (startDay == Calendar.SATURDAY) {
+ Time.SATURDAY
+ } else if (startDay == Calendar.MONDAY) {
+ Time.MONDAY
+ } else {
+ Time.SUNDAY
+ }
+ }
+
+ /**
+ * Get first day of week as java.util.Calendar constant.
+ *
+ * @return the first day of week as a java.util.Calendar constant
+ */
+ @JvmStatic fun getFirstDayOfWeekAsCalendar(context: Context?): Int {
+ return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context))
+ }
+
+ /**
+ * Converts the day of the week from android.text.format.Time to java.util.Calendar
+ */
+ @JvmStatic fun convertDayOfWeekFromTimeToCalendar(timeDayOfWeek: Int): Int {
+ return when (timeDayOfWeek) {
+ Time.MONDAY -> Calendar.MONDAY
+ Time.TUESDAY -> Calendar.TUESDAY
+ Time.WEDNESDAY -> Calendar.WEDNESDAY
+ Time.THURSDAY -> Calendar.THURSDAY
+ Time.FRIDAY -> Calendar.FRIDAY
+ Time.SATURDAY -> Calendar.SATURDAY
+ Time.SUNDAY -> Calendar.SUNDAY
+ else -> throw IllegalArgumentException(
+ "Argument must be between Time.SUNDAY and " +
+ "Time.SATURDAY"
+ )
+ }
+ }
+
+ /**
+ * @return true when week number should be shown.
+ */
+ @JvmStatic fun getShowWeekNumber(context: Context?): Boolean {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getBoolean(
+ GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM
+ ) as Boolean
+ }
+
+ /**
+ * @return true when declined events should be hidden.
+ */
+ @JvmStatic fun getHideDeclinedEvents(context: Context?): Boolean {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false) as Boolean
+ }
+
+ @JvmStatic fun getDaysPerWeek(context: Context?): Int {
+ val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+ return prefs?.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7) as Int
+ }
+
+ /**
+ * Determine whether the column position is Saturday or not.
+ *
+ * @param column the column position
+ * @param firstDayOfWeek the first day of week in android.text.format.Time
+ * @return true if the column is Saturday position
+ */
+ @JvmStatic fun isSaturday(column: Int, firstDayOfWeek: Int): Boolean {
+ return (firstDayOfWeek == Time.SUNDAY && column == 6 ||
+ firstDayOfWeek == Time.MONDAY && column == 5 ||
+ firstDayOfWeek == Time.SATURDAY && column == 0)
+ }
+
+ /**
+ * Determine whether the column position is Sunday or not.
+ *
+ * @param column the column position
+ * @param firstDayOfWeek the first day of week in android.text.format.Time
+ * @return true if the column is Sunday position
+ */
+ @JvmStatic fun isSunday(column: Int, firstDayOfWeek: Int): Boolean {
+ return (firstDayOfWeek == Time.SUNDAY && column == 0 ||
+ firstDayOfWeek == Time.MONDAY && column == 6 ||
+ firstDayOfWeek == Time.SATURDAY && column == 1)
+ }
+
+ /**
+ * Convert given UTC time into current local time. This assumes it is for an
+ * allday event and will adjust the time to be on a midnight boundary.
+ *
+ * @param recycle Time object to recycle, otherwise null.
+ * @param utcTime Time to convert, in UTC.
+ * @param tz The time zone to convert this time to.
+ */
+ @JvmStatic fun convertAlldayUtcToLocal(recycle: Time?, utcTime: Long, tz: String): Long {
+ var recycle: Time? = recycle
+ if (recycle == null) {
+ recycle = Time()
+ }
+ recycle.timezone = Time.TIMEZONE_UTC
+ recycle.set(utcTime)
+ recycle.timezone = tz
+ return recycle.normalize(true)
+ }
+
+ @JvmStatic fun convertAlldayLocalToUTC(recycle: Time?, localTime: Long, tz: String): Long {
+ var recycle: Time? = recycle
+ if (recycle == null) {
+ recycle = Time()
+ }
+ recycle.timezone = tz
+ recycle.set(localTime)
+ recycle.timezone = Time.TIMEZONE_UTC
+ return recycle.normalize(true)
+ }
+
+ /**
+ * Finds and returns the next midnight after "theTime" in milliseconds UTC
+ *
+ * @param recycle - Time object to recycle, otherwise null.
+ * @param theTime - Time used for calculations (in UTC)
+ * @param tz The time zone to convert this time to.
+ */
+ @JvmStatic fun getNextMidnight(recycle: Time?, theTime: Long, tz: String): Long {
+ var recycle: Time? = recycle
+ if (recycle == null) {
+ recycle = Time()
+ }
+ recycle.timezone = tz
+ recycle.set(theTime)
+ recycle.monthDay++
+ recycle.hour = 0
+ recycle.minute = 0
+ recycle.second = 0
+ return recycle.normalize(true)
+ }
+
+ @JvmStatic fun setAllowWeekForDetailView(allowWeekView: Boolean) {
+ this.allowWeekForDetailView = allowWeekView
+ }
+
+ @JvmStatic fun getAllowWeekForDetailView(): Boolean {
+ return this.allowWeekForDetailView
+ }
+
+ @JvmStatic fun getConfigBool(c: Context, key: Int): Boolean {
+ return c.getResources().getBoolean(key)
+ }
+
+ /**
+ * For devices with Jellybean or later, darkens the given color to ensure that white text is
+ * clearly visible on top of it. For devices prior to Jellybean, does nothing, as the
+ * sync adapter handles the color change.
+ *
+ * @param color
+ */
+ @JvmStatic fun getDisplayColorFromColor(color: Int): Int {
+ if (!isJellybeanOrLater()) {
+ return color
+ }
+ val hsv = FloatArray(3)
+ Color.colorToHSV(color, hsv)
+ hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f)
+ hsv[2] = hsv[2] * INTENSITY_ADJUST
+ return Color.HSVToColor(hsv)
+ }
+
+ // This takes a color and computes what it would look like blended with
+ // white. The result is the color that should be used for declined events.
+ @JvmStatic fun getDeclinedColorFromColor(color: Int): Int {
+ val bg = -0x1
+ val a = DECLINED_EVENT_ALPHA
+ val r = (color and 0x00ff0000) * a + (bg and 0x00ff0000) * (0xff - a) and -0x1000000
+ val g = (color and 0x0000ff00) * a + (bg and 0x0000ff00) * (0xff - a) and 0x00ff0000
+ val b = (color and 0x000000ff) * a + (bg and 0x000000ff) * (0xff - a) and 0x0000ff00
+ return -0x1000000 or (r or g or b shr 8)
+ }
+
+ @JvmStatic fun trySyncAndDisableUpgradeReceiver(context: Context?) {
+ val pm: PackageManager? = context?.getPackageManager()
+ val upgradeComponent = ComponentName(context as Context, UpgradeReceiver::class.java)
+ if (pm?.getComponentEnabledSetting(upgradeComponent) ===
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ ) {
+ // The upgrade receiver has been disabled, which means this code has been run before,
+ // so no need to sync.
+ return
+ }
+ val extras = Bundle()
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
+ ContentResolver.requestSync(
+ null /* no account */,
+ Calendars.CONTENT_URI.getAuthority(),
+ extras
+ )
+
+ // Now unregister the receiver so that we won't continue to sync every time.
+ pm?.setComponentEnabledSetting(
+ upgradeComponent,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
+ )
+ }
+
+ /**
+ * Converts a list of events to a list of segments to draw. Assumes list is
+ * ordered by start time of the events. The function processes events for a
+ * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1.
+ * The algorithm goes over all the events and creates a set of segments
+ * ordered by start time. This list of segments is then converted into a
+ * HashMap of strands which contain the draw points and are organized by
+ * color. The strands can then be drawn by setting the paint color to each
+ * strand's color and calling drawLines on its set of points. The points are
+ * set up using the following parameters.
+ *
+ * * Events between midnight and WORK_DAY_START_MINUTES are compressed
+ * into the first 1/8th of the space between top and bottom.
+ * * Events between WORK_DAY_END_MINUTES and the following midnight are
+ * compressed into the last 1/8th of the space between top and bottom
+ * * Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use
+ * the remaining 3/4ths of the space
+ * * All segments drawn will maintain at least minPixels height, except
+ * for conflicts in the first or last 1/8th, which may be smaller
+ *
+ *
+ * @param firstJulianDay The julian day of the first day of events
+ * @param events A list of events sorted by start time
+ * @param top The lowest y value the dna should be drawn at
+ * @param bottom The highest y value the dna should be drawn at
+ * @param dayXs An array of x values to draw the dna at, one for each day
+ * @param conflictColor the color to use for conflicts
+ * @return
+ */
+ @JvmStatic fun createDNAStrands(
+ firstJulianDay: Int,
+ events: ArrayList<Event?>?,
+ top: Int,
+ bottom: Int,
+ minPixels: Int,
+ dayXs: IntArray?,
+ context: Context?
+ ): HashMap<Int, DNAStrand>? {
+ if (!mMinutesLoaded) {
+ if (context == null) {
+ Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA.")
+ }
+ val res: Resources? = context?.getResources()
+ CONFLICT_COLOR = res?.getColor(R.color.month_dna_conflict_time_color) as Int
+ WORK_DAY_START_MINUTES = res?.getInteger(R.integer.work_start_minutes) as Int
+ WORK_DAY_END_MINUTES = res?.getInteger(R.integer.work_end_minutes) as Int
+ WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES
+ WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES
+ mMinutesLoaded = true
+ }
+ if (events == null || events.isEmpty() || dayXs == null || dayXs.size < 1 ||
+ bottom - top < 8 || minPixels < 0) {
+ Log.e(
+ TAG,
+ "Bad values for createDNAStrands! events:" + events + " dayXs:" +
+ Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:" +
+ minPixels
+ )
+ return null
+ }
+ val segments: LinkedList<DNASegment> = LinkedList<DNASegment>()
+ val strands: HashMap<Int, DNAStrand> = HashMap<Int, DNAStrand>()
+ // add a black strand by default, other colors will get added in
+ // the loop
+ val blackStrand = DNAStrand()
+ blackStrand.color = CONFLICT_COLOR
+ strands.put(CONFLICT_COLOR, blackStrand)
+ // the min length is the number of minutes that will occupy
+ // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the
+ // minutes/pixel * minpx where the number of pixels are 3/4 the total
+ // dna height: 4*(mins/(px * 3/4))
+ val minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top))
+
+ // There are slightly fewer than half as many pixels in 1/6 the space,
+ // so round to 2.5x for the min minutes in the non-work area
+ val minOtherMinutes = minMinutes * 5 / 2
+ val lastJulianDay = firstJulianDay + dayXs.size - 1
+ val event = Event()
+ // Go through all the events for the week
+ for (currEvent in events) {
+ // if this event is outside the weeks range skip it
+ if (currEvent != null &&
+ (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay)) {
+ continue
+ }
+ if (currEvent?.drawAsAllday() == true) {
+ addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.size)
+ continue
+ }
+ // Copy the event over so we can clip its start and end to our range
+ currEvent?.copyTo(event)
+ if (event.startDay < firstJulianDay) {
+ event.startDay = firstJulianDay
+ event.startTime = 0
+ }
+ // If it starts after the work day make sure the start is at least
+ // minPixels from midnight
+ if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) {
+ event.startTime = DAY_IN_MINUTES - minOtherMinutes
+ }
+ if (event.endDay > lastJulianDay) {
+ event.endDay = lastJulianDay
+ event.endTime = DAY_IN_MINUTES - 1
+ }
+ // If the end time is before the work day make sure it ends at least
+ // minPixels after midnight
+ if (event.endTime < minOtherMinutes) {
+ event.endTime = minOtherMinutes
+ }
+ // If the start and end are on the same day make sure they are at
+ // least minPixels apart. This only needs to be done for times
+ // outside the work day as the min distance for within the work day
+ // is enforced in the segment code.
+ if (event.startDay === event.endDay &&
+ event.endTime - event.startTime < minOtherMinutes
+ ) {
+ // If it's less than minPixels in an area before the work
+ // day
+ if (event.startTime < WORK_DAY_START_MINUTES) {
+ // extend the end to the first easy guarantee that it's
+ // minPixels
+ event.endTime = Math.min(
+ event.startTime + minOtherMinutes,
+ WORK_DAY_START_MINUTES + minMinutes
+ )
+ // if it's in the area after the work day
+ } else if (event.endTime > WORK_DAY_END_MINUTES) {
+ // First try shifting the end but not past midnight
+ event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1)
+ // if it's still too small move the start back
+ if (event.endTime - event.startTime < minOtherMinutes) {
+ event.startTime = event.endTime - minOtherMinutes
+ }
+ }
+ }
+
+ // This handles adding the first segment
+ if (segments.size == 0) {
+ addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes)
+ continue
+ }
+ // Now compare our current start time to the end time of the last
+ // segment in the list
+ val lastSegment: DNASegment = segments.getLast()
+ var startMinute: Int =
+ (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime
+ var endMinute: Int = Math.max(
+ (event.endDay - firstJulianDay) * DAY_IN_MINUTES +
+ event.endTime, startMinute + minMinutes
+ )
+ if (startMinute < 0) {
+ startMinute = 0
+ }
+ if (endMinute >= WEEK_IN_MINUTES) {
+ endMinute = WEEK_IN_MINUTES - 1
+ }
+ // If we start before the last segment in the list ends we need to
+ // start going through the list as this may conflict with other
+ // events
+ if (startMinute < lastSegment.endMinute) {
+ var i: Int = segments.size
+ // find the last segment this event intersects with
+ while (--i >= 0 && endMinute < segments.get(i).startMinute) {}
+
+ var currSegment: DNASegment = DNASegment()
+ // for each segment this event intersects with
+ while (i >= 0 && startMinute <= segments.get(i)
+ .also { currSegment = it }.endMinute) {
+
+ // if the segment is already a conflict ignore it
+ if (currSegment.color == CONFLICT_COLOR) {
+ i--
+ continue
+ }
+ // if the event ends before the segment and wouldn't create
+ // a segment that is too small split off the right side
+ if (endMinute < currSegment.endMinute - minMinutes) {
+ val rhs = DNASegment()
+ rhs.endMinute = currSegment.endMinute
+ rhs.color = currSegment.color
+ rhs.startMinute = endMinute + 1
+ rhs.day = currSegment.day
+ currSegment.endMinute = endMinute
+ segments.add(i + 1, rhs)
+ // Equivalent to strands.get(rhs.color)?.count++
+ // but there is no null safe invocation for ++
+ strands.get(rhs.color)?.count = strands.get(rhs.color)?.count?.inc() as Int
+ if (DEBUG) {
+ Log.d(
+ TAG, "Added rhs, curr:" + currSegment.toString() + " i:" +
+ segments.get(i).toString()
+ )
+ }
+ }
+ // if the event starts after the segment and wouldn't create
+ // a segment that is too small split off the left side
+ if (startMinute > currSegment.startMinute + minMinutes) {
+ val lhs = DNASegment()
+ lhs.startMinute = currSegment.startMinute
+ lhs.color = currSegment.color
+ lhs.endMinute = startMinute - 1
+ lhs.day = currSegment.day
+ currSegment.startMinute = startMinute
+ // increment i so that we are at the right position when
+ // referencing the segments to the right and left of the
+ // current segment.
+ segments.add(i++, lhs)
+ strands.get(lhs.color)?.count = strands.get(lhs.color)?.count?.inc() as Int
+ if (DEBUG) {
+ Log.d(
+ TAG, "Added lhs, curr:" + currSegment.toString() + " i:" +
+ segments.get(i).toString()
+ )
+ }
+ }
+ // if the right side is black merge this with the segment to
+ // the right if they're on the same day and overlap
+ if (i + 1 < segments.size) {
+ val rhs: DNASegment = segments.get(i + 1)
+ if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day &&
+ rhs.startMinute <= currSegment.endMinute + 1) {
+ rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute)
+ segments.remove(currSegment)
+ strands.get(currSegment.color)?.count =
+ strands.get(currSegment.color)?.count?.dec() as Int
+ // point at the new current segment
+ currSegment = rhs
+ }
+ }
+ // if the left side is black merge this with the segment to
+ // the left if they're on the same day and overlap
+ if (i - 1 >= 0) {
+ val lhs: DNASegment = segments.get(i - 1)
+ if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day &&
+ lhs.endMinute >= currSegment.startMinute - 1) {
+ lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute)
+ segments.remove(currSegment)
+ strands.get(currSegment.color)?.count =
+ strands.get(currSegment.color)?.count?.dec() as Int
+ // point at the new current segment
+ currSegment = lhs
+ // point i at the new current segment in case new
+ // code is added
+ i--
+ }
+ }
+ // if we're still not black, decrement the count for the
+ // color being removed, change this to black, and increment
+ // the black count
+ if (currSegment.color != CONFLICT_COLOR) {
+ strands.get(currSegment.color)?.count =
+ strands.get(currSegment.color)?.count?.dec() as Int
+ currSegment.color = CONFLICT_COLOR
+ strands.get(CONFLICT_COLOR)?.count =
+ strands.get(CONFLICT_COLOR)?.count?.inc() as Int
+ }
+ i--
+ }
+ }
+ // If this event extends beyond the last segment add a new segment
+ if (endMinute > lastSegment.endMinute) {
+ addNewSegment(
+ segments, event, strands, firstJulianDay, lastSegment.endMinute,
+ minMinutes
+ )
+ }
+ }
+ weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs)
+ return strands
+ }
+
+ // This figures out allDay colors as allDay events are found
+ private fun addAllDayToStrands(
+ event: Event?,
+ strands: HashMap<Int, DNAStrand>,
+ firstJulianDay: Int,
+ numDays: Int
+ ) {
+ val strand = getOrCreateStrand(strands, CONFLICT_COLOR)
+ // if we haven't initialized the allDay portion create it now
+ if (strand?.allDays == null) {
+ strand?.allDays = IntArray(numDays)
+ }
+
+ // For each day this event is on update the color
+ val end: Int = Math.min((event?.endDay ?: 0) - firstJulianDay, numDays - 1)
+ for (i in Math.max((event?.startDay ?: 0) - firstJulianDay, 0)..end) {
+ if (strand?.allDays!![i] != 0) {
+ // if this day already had a color, it is now a conflict
+ strand?.allDays!![i] = CONFLICT_COLOR
+ } else {
+ // else it's just the color of the event
+ strand?.allDays!![i] = event?.color as Int
+ }
+ }
+ }
+
+ // This processes all the segments, sorts them by color, and generates a
+ // list of points to draw
+ private fun weaveDNAStrands(
+ segments: LinkedList<DNASegment>,
+ firstJulianDay: Int,
+ strands: HashMap<Int, DNAStrand>,
+ top: Int,
+ bottom: Int,
+ dayXs: IntArray
+ ) {
+ // First, get rid of any colors that ended up with no segments
+ val strandIterator = strands.values.iterator()
+ while (strandIterator.hasNext()) {
+ val strand = strandIterator.next()
+ if (strand?.count < 1 && strand.allDays == null) {
+ strandIterator.remove()
+ continue
+ }
+ strand.points = FloatArray(strand.count * 4)
+ strand.position = 0
+ }
+ // Go through each segment and compute its points
+ for (segment in segments) {
+ // Add the points to the strand of that color
+ val strand: DNAStrand? = strands.get(segment.color)
+ val dayIndex = segment.day - firstJulianDay
+ val dayStartMinute = segment.startMinute % DAY_IN_MINUTES
+ val dayEndMinute = segment.endMinute % DAY_IN_MINUTES
+ val height = bottom - top
+ val workDayHeight = height * 3 / 4
+ val remainderHeight = (height - workDayHeight) / 2
+ val x = dayXs[dayIndex]
+ var y0 = 0
+ var y1 = 0
+ y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight)
+ y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight)
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Adding " + Integer.toHexString(segment.color).toString() + " at x,y0,y1: " + x
+ .toString() + " " + y0.toString() + " " + y1.toString() +
+ " for " + dayStartMinute.toString() + " " + dayEndMinute
+ )
+ }
+ strand?.points!![strand?.position] = x.toFloat()
+ strand?.position = strand?.position?.inc() as Int
+
+ strand?.points!![strand?.position] = y0.toFloat()
+ strand?.position = strand?.position?.inc() as Int
+
+ strand?.points!![strand?.position] = x.toFloat()
+ strand?.position = strand?.position.inc() as Int
+
+ strand?.points!![strand?.position] = y1.toFloat()
+ strand?.position = strand?.position.inc() as Int
+ }
+ }
+
+ /**
+ * Compute a pixel offset from the top for a given minute from the work day
+ * height and the height of the top area.
+ */
+ private fun getPixelOffsetFromMinutes(
+ minute: Int,
+ workDayHeight: Int,
+ remainderHeight: Int
+ ): Int {
+ val y: Int
+ if (minute < WORK_DAY_START_MINUTES) {
+ y = minute * remainderHeight / WORK_DAY_START_MINUTES
+ } else if (minute < WORK_DAY_END_MINUTES) {
+ y = remainderHeight + (minute - WORK_DAY_START_MINUTES) *
+ workDayHeight / WORK_DAY_MINUTES
+ } else {
+ y = remainderHeight + workDayHeight +
+ (minute - WORK_DAY_END_MINUTES) * remainderHeight / WORK_DAY_END_LENGTH
+ }
+ return y
+ }
+
+ /**
+ * Add a new segment based on the event provided. This will handle splitting
+ * segments across day boundaries and ensures a minimum size for segments.
+ */
+ private fun addNewSegment(
+ segments: LinkedList<DNASegment>,
+ event: Event,
+ strands: HashMap<Int, DNAStrand>,
+ firstJulianDay: Int,
+ minStart: Int,
+ minMinutes: Int
+ ) {
+ var event: Event = event
+ var minStart = minStart
+ if (event.startDay > event.endDay) {
+ Log.wtf(TAG, "Event starts after it ends: " + event.toString())
+ }
+ // If this is a multiday event split it up by day
+ if (event.startDay !== event.endDay) {
+ val lhs = Event()
+ lhs.color = event.color
+ lhs.startDay = event.startDay
+ // the first day we want the start time to be the actual start time
+ lhs.startTime = event.startTime
+ lhs.endDay = lhs.startDay
+ lhs.endTime = DAY_IN_MINUTES - 1
+ // Nearly recursive iteration!
+ while (lhs.startDay !== event.endDay) {
+ addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes)
+ // The days in between are all day, even though that shouldn't
+ // actually happen due to the allday filtering
+ lhs.startDay++
+ lhs.endDay = lhs.startDay
+ lhs.startTime = 0
+ minStart = 0
+ }
+ // The last day we want the end time to be the actual end time
+ lhs.endTime = event.endTime
+ event = lhs
+ }
+ // Create the new segment and compute its fields
+ val segment = DNASegment()
+ val dayOffset: Int = (event.startDay - firstJulianDay) * DAY_IN_MINUTES
+ val endOfDay = dayOffset + DAY_IN_MINUTES - 1
+ // clip the start if needed
+ segment.startMinute = Math.max(dayOffset + event.startTime, minStart)
+ // and extend the end if it's too small, but not beyond the end of the
+ // day
+ val minEnd: Int = Math.min(segment.startMinute + minMinutes, endOfDay)
+ segment.endMinute = Math.max(dayOffset + event.endTime, minEnd)
+ if (segment.endMinute > endOfDay) {
+ segment.endMinute = endOfDay
+ }
+ segment.color = event.color
+ segment.day = event.startDay
+ segments.add(segment)
+ // increment the count for the correct color or add a new strand if we
+ // don't have that color yet
+ val strand = getOrCreateStrand(strands, segment.color)
+ strand?.count
+ strand?.count = strand?.count?.inc() as Int
+ }
+
+ /**
+ * Try to get a strand of the given color. Create it if it doesn't exist.
+ */
+ private fun getOrCreateStrand(strands: HashMap<Int, DNAStrand>, color: Int): DNAStrand? {
+ var strand: DNAStrand? = strands.get(color)
+ if (strand == null) {
+ strand = DNAStrand()
+ strand?.color = color
+ strand?.count = 0
+ strands?.put(strand?.color, strand)
+ }
+ return strand
+ }
+
+ /**
+ * Sends an intent to launch the top level Calendar view.
+ *
+ * @param context
+ */
+ @JvmStatic fun returnToCalendarHome(context: Context) {
+ val launchIntent = Intent(context, AllInOneActivity::class.java)
+ launchIntent.setAction(Intent.ACTION_DEFAULT)
+ launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ launchIntent.putExtra(INTENT_KEY_HOME, true)
+ context.startActivity(launchIntent)
+ }
+
+ /**
+ * Given a context and a time in millis since unix epoch figures out the
+ * correct week of the year for that time.
+ *
+ * @param millisSinceEpoch
+ * @return
+ */
+ @JvmStatic fun getWeekNumberFromTime(millisSinceEpoch: Long, context: Context?): Int {
+ val weekTime = Time(getTimeZone(context, null))
+ weekTime.set(millisSinceEpoch)
+ weekTime.normalize(true)
+ val firstDayOfWeek = getFirstDayOfWeek(context)
+ // if the date is on Saturday or Sunday and the start of the week
+ // isn't Monday we may need to shift the date to be in the correct
+ // week
+ if (weekTime.weekDay === Time.SUNDAY &&
+ (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)
+ ) {
+ weekTime.monthDay++
+ weekTime.normalize(true)
+ } else if (weekTime.weekDay === Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) {
+ weekTime.monthDay += 2
+ weekTime.normalize(true)
+ }
+ return weekTime.getWeekNumber()
+ }
+
+ /**
+ * Formats a day of the week string. This is either just the name of the day
+ * or a combination of yesterday/today/tomorrow and the day of the week.
+ *
+ * @param julianDay The julian day to get the string for
+ * @param todayJulianDay The julian day for today's date
+ * @param millis A utc millis since epoch time that falls on julian day
+ * @param context The calling context, used to get the timezone and do the
+ * formatting
+ * @return
+ */
+ @JvmStatic fun getDayOfWeekString(
+ julianDay: Int,
+ todayJulianDay: Int,
+ millis: Long,
+ context: Context
+ ): String {
+ getTimeZone(context, null)
+ val flags: Int = DateUtils.FORMAT_SHOW_WEEKDAY
+ var dayViewText: String
+ dayViewText = if (julianDay == todayJulianDay) {
+ context.getString(
+ R.string.agenda_today,
+ mTZUtils?.formatDateRange(context, millis, millis, flags)
+ .toString()
+ )
+ } else if (julianDay == todayJulianDay - 1) {
+ context.getString(
+ R.string.agenda_yesterday,
+ mTZUtils?.formatDateRange(context, millis, millis, flags)
+ .toString()
+ )
+ } else if (julianDay == todayJulianDay + 1) {
+ context.getString(
+ R.string.agenda_tomorrow,
+ mTZUtils?.formatDateRange(context, millis, millis, flags)
+ .toString()
+ )
+ } else {
+ mTZUtils?.formatDateRange(context, millis, millis, flags)
+ .toString()
+ }
+ dayViewText = dayViewText.toUpperCase()
+ return dayViewText
+ }
+
+ // Calculate the time until midnight + 1 second and set the handler to
+ // do run the runnable
+ @JvmStatic fun setMidnightUpdater(h: Handler?, r: Runnable?, timezone: String?) {
+ if (h == null || r == null || timezone == null) {
+ return
+ }
+ val now: Long = System.currentTimeMillis()
+ val time = Time(timezone)
+ time.set(now)
+ val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 -
+ time.second + 1) * 1000).toLong()
+ h.removeCallbacks(r)
+ h.postDelayed(r, runInMillis)
+ }
+
+ // Stop the midnight update thread
+ @JvmStatic fun resetMidnightUpdater(h: Handler?, r: Runnable?) {
+ if (h == null || r == null) {
+ return
+ }
+ h.removeCallbacks(r)
+ }
+
+ /**
+ * Returns a string description of the specified time interval.
+ */
+ @JvmStatic fun getDisplayedDatetime(
+ startMillis: Long,
+ endMillis: Long,
+ currentMillis: Long,
+ localTimezone: String,
+ allDay: Boolean,
+ context: Context
+ ): String? {
+ // Configure date/time formatting.
+ val flagsDate: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY
+ var flagsTime: Int = DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(context)) {
+ flagsTime = flagsTime or DateUtils.FORMAT_24HOUR
+ }
+ val currentTime = Time(localTimezone)
+ currentTime.set(currentMillis)
+ val resources: Resources = context.getResources()
+ var datetimeString: String? = null
+ if (allDay) {
+ // All day events require special timezone adjustment.
+ val localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone)
+ val localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone)
+ if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
+ // If possible, use "Today" or "Tomorrow" instead of a full date string.
+ val todayOrTomorrow = isTodayOrTomorrow(
+ context.getResources(),
+ localStartMillis, currentMillis, currentTime.gmtoff
+ )
+ if (TODAY == todayOrTomorrow) {
+ datetimeString = resources.getString(R.string.today)
+ } else if (TOMORROW == todayOrTomorrow) {
+ datetimeString = resources.getString(R.string.tomorrow)
+ }
+ }
+ if (datetimeString == null) {
+ // For multi-day allday events or single-day all-day events that are not
+ // today or tomorrow, use framework formatter.
+ val f = Formatter(StringBuilder(50), Locale.getDefault())
+ datetimeString = DateUtils.formatDateRange(
+ context, f, startMillis,
+ endMillis, flagsDate, Time.TIMEZONE_UTC
+ ).toString()
+ }
+ } else {
+ datetimeString = if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
+ // Format the time.
+ val timeString = formatDateRange(
+ context, startMillis, endMillis,
+ flagsTime
+ )
+
+ // If possible, use "Today" or "Tomorrow" instead of a full date string.
+ val todayOrTomorrow = isTodayOrTomorrow(
+ context.getResources(), startMillis,
+ currentMillis, currentTime.gmtoff
+ )
+ if (TODAY == todayOrTomorrow) {
+ // Example: "Today at 1:00pm - 2:00 pm"
+ resources.getString(
+ R.string.today_at_time_fmt,
+ timeString
+ )
+ } else if (TOMORROW == todayOrTomorrow) {
+ // Example: "Tomorrow at 1:00pm - 2:00 pm"
+ resources.getString(
+ R.string.tomorrow_at_time_fmt,
+ timeString
+ )
+ } else {
+ // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
+ val dateString = formatDateRange(
+ context, startMillis, endMillis,
+ flagsDate
+ )
+ resources.getString(
+ R.string.date_time_fmt, dateString,
+ timeString
+ )
+ }
+ } else {
+ // For multiday events, shorten day/month names.
+ // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
+ val flagsDatetime = flagsDate or flagsTime or DateUtils.FORMAT_ABBREV_MONTH or
+ DateUtils.FORMAT_ABBREV_WEEKDAY
+ formatDateRange(
+ context, startMillis, endMillis,
+ flagsDatetime
+ )
+ }
+ }
+ return datetimeString
+ }
+
+ /**
+ * Returns the timezone to display in the event info, if the local timezone is different
+ * from the event timezone. Otherwise returns null.
+ */
+ @JvmStatic fun getDisplayedTimezone(
+ startMillis: Long,
+ localTimezone: String?,
+ eventTimezone: String?
+ ): String? {
+ var tzDisplay: String? = null
+ if (!TextUtils.equals(localTimezone, eventTimezone)) {
+ // Figure out if this is in DST
+ val tz: TimeZone = TimeZone.getTimeZone(localTimezone)
+ tzDisplay = if (tz == null || tz.getID().equals("GMT")) {
+ localTimezone
+ } else {
+ val startTime = Time(localTimezone)
+ startTime.set(startMillis)
+ tz.getDisplayName(startTime.isDst !== 0, TimeZone.SHORT)
+ }
+ }
+ return tzDisplay
+ }
+
+ /**
+ * Returns whether the specified time interval is in a single day.
+ */
+ private fun singleDayEvent(startMillis: Long, endMillis: Long, localGmtOffset: Long): Boolean {
+ if (startMillis == endMillis) {
+ return true
+ }
+
+ // An event ending at midnight should still be a single-day event, so check
+ // time end-1.
+ val startDay: Int = Time.getJulianDay(startMillis, localGmtOffset)
+ val endDay: Int = Time.getJulianDay(endMillis - 1, localGmtOffset)
+ return startDay == endDay
+ }
+
+ // Using int constants as a return value instead of an enum to minimize resources.
+ private const val TODAY = 1
+ private const val TOMORROW = 2
+ private const val NONE = 0
+
+ /**
+ * Returns TODAY or TOMORROW if applicable. Otherwise returns NONE.
+ */
+ private fun isTodayOrTomorrow(
+ r: Resources,
+ dayMillis: Long,
+ currentMillis: Long,
+ localGmtOffset: Long
+ ): Int {
+ val startDay: Int = Time.getJulianDay(dayMillis, localGmtOffset)
+ val currentDay: Int = Time.getJulianDay(currentMillis, localGmtOffset)
+ val days = startDay - currentDay
+ return if (days == 1) {
+ TOMORROW
+ } else if (days == 0) {
+ TODAY
+ } else {
+ NONE
+ }
+ }
+
+ /**
+ * 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
+ */
+ @JvmStatic fun setTodayIcon(icon: LayerDrawable, c: Context?, timezone: String?) {
+ val today: DayOfMonthDrawable
+
+ // Reuse current drawable if possible
+ val currentDrawable: Drawable? = icon.findDrawableByLayerId(R.id.today_icon_day)
+ if (currentDrawable != null && currentDrawable is DayOfMonthDrawable) {
+ today = currentDrawable as DayOfMonthDrawable
+ } else {
+ today = DayOfMonthDrawable(c as Context)
+ }
+ // Set the day and update the icon
+ val now = Time(timezone)
+ now.setToNow()
+ now.normalize(false)
+ today.setDayOfMonth(now.monthDay)
+ icon.mutate()
+ icon.setDrawableByLayerId(R.id.today_icon_day, today)
+ }
+
+ /**
+ * Get a list of quick responses used for emailing guests from the
+ * SharedPreferences. If not are found, get the hard coded ones that shipped
+ * with the app
+ *
+ * @param context
+ * @return a list of quick responses.
+ */
+ fun getQuickResponses(context: Context): Array<String> {
+ var s = getSharedPreference(context, KEY_QUICK_RESPONSES, null as Array<String>?)
+ if (s == null) {
+ s = context.getResources().getStringArray(R.array.quick_response_defaults)
+ }
+ return s
+ }
+
+ /**
+ * Return the app version code.
+ */
+ fun getVersionCode(context: Context): String? {
+ if (sVersion == null) {
+ try {
+ sVersion = context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0
+ ).versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Can't find version; just leave it blank.
+ Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName)
+ }
+ }
+ return sVersion
+ }
+
+ // A single strand represents one color of events. Events are divided up by
+ // color to make them convenient to draw. The black strand is special in
+ // that it holds conflicting events as well as color settings for allday on
+ // each day.
+ class DNAStrand {
+ @JvmField var points: FloatArray? = null
+ @JvmField var allDays: IntArray? = null // color for the allday, 0 means no event
+ @JvmField var position = 0
+ @JvmField var color = 0
+ @JvmField var count = 0
+ }
+
+ // A segment is a single continuous length of time occupied by a single
+ // color. Segments should never span multiple days.
+ private class DNASegment {
+ var startMinute = 0 // in minutes since the start of the week =
+ var endMinute = 0
+ var color = 0 // Calendar color or black for conflicts =
+ var day = 0 // quick reference to the day this segment is on =
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlarmManagerInterface.java b/src/com/android/calendar/alerts/AlarmManagerInterface.kt
index 3c66434d..be9d86f2 100644
--- a/src/com/android/calendar/alerts/AlarmManagerInterface.java
+++ b/src/com/android/calendar/alerts/AlarmManagerInterface.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.calendar.alerts
-package com.android.calendar.alerts;
-
-import android.app.PendingIntent;
+import android.app.PendingIntent
/**
* AlarmManager abstracted to an interface for testability.
*/
-public interface AlarmManagerInterface {
- public void set(int type, long triggerAtMillis, PendingIntent operation);
-}
+interface AlarmManagerInterface {
+ operator fun set(type: Int, triggerAtMillis: Long, operation: PendingIntent?)
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.java b/src/com/android/calendar/alerts/AlarmScheduler.java
deleted file mode 100644
index 97828229..00000000
--- a/src/com/android/calendar/alerts/AlarmScheduler.java
+++ /dev/null
@@ -1,322 +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.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Instances;
-import android.provider.CalendarContract.Reminders;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events
- * and reminders tables for the next upcoming alert.
- */
-public class AlarmScheduler {
- private static final String TAG = "AlarmScheduler";
-
- private static final String INSTANCES_WHERE = Events.VISIBLE + "=? AND "
- + Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND "
- + Events.ALL_DAY + "=?";
- static final String[] INSTANCES_PROJECTION = new String[] {
- Instances.EVENT_ID,
- Instances.BEGIN,
- Instances.ALL_DAY,
- };
- private static final int INSTANCES_INDEX_EVENTID = 0;
- private static final int INSTANCES_INDEX_BEGIN = 1;
- private static final int INSTANCES_INDEX_ALL_DAY = 2;
-
- private static final String REMINDERS_WHERE = Reminders.METHOD + "=1 AND "
- + Reminders.EVENT_ID + " IN ";
- static final String[] REMINDERS_PROJECTION = new String[] {
- Reminders.EVENT_ID,
- Reminders.MINUTES,
- Reminders.METHOD,
- };
- private static final int REMINDERS_INDEX_EVENT_ID = 0;
- private static final int REMINDERS_INDEX_MINUTES = 1;
- private static final int REMINDERS_INDEX_METHOD = 2;
-
- // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons:
- // (1) so that the concurrent reminder broadcast from the provider doesn't result
- // in a double ring, and (2) some OEMs modified the provider to not add an alert to
- // the CalendarAlerts table until the alert time, so for the unbundled app's
- // notifications to work on these devices, a delay ensures that AlertService won't
- // read from the CalendarAlerts table until the alert is present.
- static final int ALARM_DELAY_MS = 1000;
-
- // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...". This
- // sets the max # of events in the query before batching into multiple queries, to
- // limit the SQL query length.
- private static final int REMINDER_QUERY_BATCH_SIZE = 50;
-
- // We really need to query for reminder times that fall in some interval, but
- // the Reminders table only stores the reminder interval (10min, 15min, etc), and
- // we cannot do the join with the Events table to calculate the actual alert time
- // from outside of the provider. So the best we can do for now consider events
- // whose start times begin within some interval (ie. 1 week out). This means
- // reminders which are configured for more than 1 week out won't fire on time. We
- // can minimize this to being only 1 day late by putting a 1 day max on the alarm time.
- private static final long EVENT_LOOKAHEAD_WINDOW_MS = DateUtils.WEEK_IN_MILLIS;
- private static final long MAX_ALARM_ELAPSED_MS = DateUtils.DAY_IN_MILLIS;
-
- /**
- * Schedules the nearest upcoming alarm, to refresh notifications.
- *
- * This is historically done in the provider but we dupe this here so the unbundled
- * app will work on devices that have modified this portion of the provider. This
- * has the limitation of querying events within some interval from now (ie. looks at
- * reminders for all events occurring in the next week). This means for example,
- * a 2 week notification will not fire on time.
- */
- public static void scheduleNextAlarm(Context context) {
- scheduleNextAlarm(context, AlertUtils.createAlarmManager(context),
- REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis());
- }
-
- // VisibleForTesting
- static void scheduleNextAlarm(Context context, AlarmManagerInterface alarmManager,
- int batchSize, long currentMillis) {
- Cursor instancesCursor = null;
- try {
- instancesCursor = queryUpcomingEvents(context, context.getContentResolver(),
- currentMillis);
- if (instancesCursor != null) {
- queryNextReminderAndSchedule(instancesCursor, context,
- context.getContentResolver(), alarmManager, batchSize, currentMillis);
- }
- } finally {
- if (instancesCursor != null) {
- instancesCursor.close();
- }
- }
- }
-
- /**
- * Queries events starting within a fixed interval from now.
- */
- private static Cursor queryUpcomingEvents(Context context, ContentResolver contentResolver,
- long currentMillis) {
- Time time = new Time();
- time.normalize(false);
- long localOffset = time.gmtoff * 1000;
- final long localStartMin = currentMillis;
- final long localStartMax = localStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
- final long utcStartMin = localStartMin - localOffset;
- final long utcStartMax = utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
-
- // Expand Instances table range by a day on either end to account for
- // all-day events.
- Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon();
- ContentUris.appendId(uriBuilder, localStartMin - DateUtils.DAY_IN_MILLIS);
- ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS);
-
- // Build query for all events starting within the fixed interval.
- StringBuilder queryBuilder = new StringBuilder();
- queryBuilder.append("(");
- queryBuilder.append(INSTANCES_WHERE);
- queryBuilder.append(") OR (");
- queryBuilder.append(INSTANCES_WHERE);
- queryBuilder.append(")");
- String[] queryArgs = new String[] {
- // allday selection
- "1", /* visible = ? */
- String.valueOf(utcStartMin), /* begin >= ? */
- String.valueOf(utcStartMax), /* begin <= ? */
- "1", /* allDay = ? */
-
- // non-allday selection
- "1", /* visible = ? */
- String.valueOf(localStartMin), /* begin >= ? */
- String.valueOf(localStartMax), /* begin <= ? */
- "0" /* allDay = ? */
- };
-
- Cursor cursor = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION,
- queryBuilder.toString(), queryArgs, null);
- return cursor;
- }
-
- /**
- * Queries for all the reminders of the events in the instancesCursor, and schedules
- * the alarm for the next upcoming reminder.
- */
- private static void queryNextReminderAndSchedule(Cursor instancesCursor, Context context,
- ContentResolver contentResolver, AlarmManagerInterface alarmManager,
- int batchSize, long currentMillis) {
- if (AlertService.DEBUG) {
- int eventCount = instancesCursor.getCount();
- if (eventCount == 0) {
- Log.d(TAG, "No events found starting within 1 week.");
- } else {
- Log.d(TAG, "Query result count for events starting within 1 week: " + eventCount);
- }
- }
-
- // Put query results of all events starting within some interval into map of event ID to
- // local start time.
- Map<Integer, List<Long>> eventMap = new HashMap<Integer, List<Long>>();
- Time timeObj = new Time();
- long nextAlarmTime = Long.MAX_VALUE;
- int nextAlarmEventId = 0;
- instancesCursor.moveToPosition(-1);
- while (!instancesCursor.isAfterLast()) {
- int index = 0;
- eventMap.clear();
- StringBuilder eventIdsForQuery = new StringBuilder();
- eventIdsForQuery.append('(');
- while (index++ < batchSize && instancesCursor.moveToNext()) {
- int eventId = instancesCursor.getInt(INSTANCES_INDEX_EVENTID);
- long begin = instancesCursor.getLong(INSTANCES_INDEX_BEGIN);
- boolean allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0;
- long localStartTime;
- if (allday) {
- // Adjust allday to local time.
- localStartTime = Utils.convertAlldayUtcToLocal(timeObj, begin,
- Time.getCurrentTimezone());
- } else {
- localStartTime = begin;
- }
- List<Long> startTimes = eventMap.get(eventId);
- if (startTimes == null) {
- startTimes = new ArrayList<Long>();
- eventMap.put(eventId, startTimes);
- eventIdsForQuery.append(eventId);
- eventIdsForQuery.append(",");
- }
- startTimes.add(localStartTime);
-
- // Log for debugging.
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- timeObj.set(localStartTime);
- StringBuilder msg = new StringBuilder();
- msg.append("Events cursor result -- eventId:").append(eventId);
- msg.append(", allDay:").append(allday);
- msg.append(", start:").append(localStartTime);
- msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")");
- Log.d(TAG, msg.toString());
- }
- }
- if (eventIdsForQuery.charAt(eventIdsForQuery.length() - 1) == ',') {
- eventIdsForQuery.deleteCharAt(eventIdsForQuery.length() - 1);
- }
- eventIdsForQuery.append(')');
-
- // Query the reminders table for the events found.
- Cursor cursor = null;
- try {
- cursor = contentResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
- REMINDERS_WHERE + eventIdsForQuery, null, null);
-
- // Process the reminders query results to find the next reminder time.
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- int eventId = cursor.getInt(REMINDERS_INDEX_EVENT_ID);
- int reminderMinutes = cursor.getInt(REMINDERS_INDEX_MINUTES);
- List<Long> startTimes = eventMap.get(eventId);
- if (startTimes != null) {
- for (Long startTime : startTimes) {
- long alarmTime = startTime -
- reminderMinutes * DateUtils.MINUTE_IN_MILLIS;
- if (alarmTime > currentMillis && alarmTime < nextAlarmTime) {
- nextAlarmTime = alarmTime;
- nextAlarmEventId = eventId;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- timeObj.set(alarmTime);
- StringBuilder msg = new StringBuilder();
- msg.append("Reminders cursor result -- eventId:").append(eventId);
- msg.append(", startTime:").append(startTime);
- msg.append(", minutes:").append(reminderMinutes);
- msg.append(", alarmTime:").append(alarmTime);
- msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P"))
- .append(")");
- Log.d(TAG, msg.toString());
- }
- }
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- // Schedule the alarm for the next reminder time.
- if (nextAlarmTime < Long.MAX_VALUE) {
- scheduleAlarm(context, nextAlarmEventId, nextAlarmTime, currentMillis, alarmManager);
- }
- }
-
- /**
- * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified
- * alarm time with a slight delay (to account for the possible duplicate broadcast
- * from the provider).
- */
- private static void scheduleAlarm(Context context, long eventId, long alarmTime,
- long currentMillis, AlarmManagerInterface alarmManager) {
- // Max out the alarm time to 1 day out, so an alert for an event far in the future
- // (not present in our event query results for a limited range) can only be at
- // most 1 day late.
- long maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS;
- if (alarmTime > maxAlarmTime) {
- alarmTime = maxAlarmTime;
- }
-
- // Add a slight delay (see comments on the member var).
- alarmTime += ALARM_DELAY_MS;
-
- if (AlertService.DEBUG) {
- Time time = new Time();
- time.set(alarmTime);
- String schedTime = time.format("%a, %b %d, %Y %I:%M%P");
- Log.d(TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId
- + " at " + alarmTime + " (" + schedTime + ")");
- }
-
- // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager. The extra is
- // only used by AlertService for logging. It is ignored by Intent.filterEquals,
- // so this scheduling will still overwrite the alarm that was previously pending.
- // Note that the 'setClass' is required, because otherwise it seems the broadcast
- // can be eaten by other apps and we somehow may never receive it.
- Intent intent = new Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION);
- intent.setClass(context, AlertReceiver.class);
- intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
- }
-}
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.kt b/src/com/android/calendar/alerts/AlarmScheduler.kt
new file mode 100644
index 00000000..c93bbb04
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlarmScheduler.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.PendingIntent
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.database.Cursor
+import android.net.Uri
+import android.provider.CalendarContract
+import android.provider.CalendarContract.Events
+import android.provider.CalendarContract.Instances
+import android.provider.CalendarContract.Reminders
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import com.android.calendar.Utils
+import java.util.HashMap
+import java.util.List
+
+/**
+ * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events
+ * and reminders tables for the next upcoming alert.
+ */
+object AlarmScheduler {
+ private const val TAG = "AlarmScheduler"
+ private val INSTANCES_WHERE: String = (Events.VISIBLE.toString() + "=? AND " +
+ Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND " +
+ Events.ALL_DAY + "=?")
+ val INSTANCES_PROJECTION = arrayOf<String>(
+ Instances.EVENT_ID,
+ Instances.BEGIN,
+ Instances.ALL_DAY
+ )
+ private const val INSTANCES_INDEX_EVENTID = 0
+ private const val INSTANCES_INDEX_BEGIN = 1
+ private const val INSTANCES_INDEX_ALL_DAY = 2
+ private val REMINDERS_WHERE: String = (Reminders.METHOD.toString() + "=1 AND " +
+ Reminders.EVENT_ID + " IN ")
+ val REMINDERS_PROJECTION = arrayOf<String>(
+ Reminders.EVENT_ID,
+ Reminders.MINUTES,
+ Reminders.METHOD
+ )
+ private const val REMINDERS_INDEX_EVENT_ID = 0
+ private const val REMINDERS_INDEX_MINUTES = 1
+ private const val REMINDERS_INDEX_METHOD = 2
+
+ // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons:
+ // (1) so that the concurrent reminder broadcast from the provider doesn't result
+ // in a double ring, and (2) some OEMs modified the provider to not add an alert to
+ // the CalendarAlerts table until the alert time, so for the unbundled app's
+ // notifications to work on these devices, a delay ensures that AlertService won't
+ // read from the CalendarAlerts table until the alert is present.
+ const val ALARM_DELAY_MS = 1000
+
+ // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...". This
+ // sets the max # of events in the query before batching into multiple queries, to
+ // limit the SQL query length.
+ private const val REMINDER_QUERY_BATCH_SIZE = 50
+
+ // We really need to query for reminder times that fall in some interval, but
+ // the Reminders table only stores the reminder interval (10min, 15min, etc), and
+ // we cannot do the join with the Events table to calculate the actual alert time
+ // from outside of the provider. So the best we can do for now consider events
+ // whose start times begin within some interval (ie. 1 week out). This means
+ // reminders which are configured for more than 1 week out won't fire on time. We
+ // can minimize this to being only 1 day late by putting a 1 day max on the alarm time.
+ private val EVENT_LOOKAHEAD_WINDOW_MS: Long = DateUtils.WEEK_IN_MILLIS
+ private val MAX_ALARM_ELAPSED_MS: Long = DateUtils.DAY_IN_MILLIS
+
+ /**
+ * Schedules the nearest upcoming alarm, to refresh notifications.
+ *
+ * This is historically done in the provider but we dupe this here so the unbundled
+ * app will work on devices that have modified this portion of the provider. This
+ * has the limitation of querying events within some interval from now (ie. looks at
+ * reminders for all events occurring in the next week). This means for example,
+ * a 2 week notification will not fire on time.
+ */
+ @JvmStatic fun scheduleNextAlarm(context: Context) {
+ scheduleNextAlarm(
+ context, AlertUtils.createAlarmManager(context),
+ REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis()
+ )
+ }
+
+ // VisibleForTesting
+ @JvmStatic fun scheduleNextAlarm(
+ context: Context,
+ alarmManager: AlarmManagerInterface?,
+ batchSize: Int,
+ currentMillis: Long
+ ) {
+ var instancesCursor: Cursor? = null
+ try {
+ instancesCursor = queryUpcomingEvents(
+ context, context.getContentResolver(),
+ currentMillis
+ )
+ if (instancesCursor != null) {
+ queryNextReminderAndSchedule(
+ instancesCursor,
+ context,
+ context.getContentResolver(),
+ alarmManager as AlarmManagerInterface,
+ batchSize,
+ currentMillis
+ )
+ }
+ } finally {
+ if (instancesCursor != null) {
+ instancesCursor.close()
+ }
+ }
+ }
+
+ /**
+ * Queries events starting within a fixed interval from now.
+ */
+ @JvmStatic private fun queryUpcomingEvents(
+ context: Context,
+ contentResolver: ContentResolver,
+ currentMillis: Long
+ ): Cursor? {
+ val time = Time()
+ time.normalize(false)
+ val localOffset: Long = time.gmtoff * 1000
+ val localStartMax =
+ currentMillis + EVENT_LOOKAHEAD_WINDOW_MS
+ val utcStartMin = currentMillis - localOffset
+ val utcStartMax =
+ utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS
+
+ // Expand Instances table range by a day on either end to account for
+ // all-day events.
+ val uriBuilder: Uri.Builder = Instances.CONTENT_URI.buildUpon()
+ ContentUris.appendId(uriBuilder, currentMillis - DateUtils.DAY_IN_MILLIS)
+ ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS)
+
+ // Build query for all events starting within the fixed interval.
+ val queryBuilder = StringBuilder()
+ queryBuilder.append("(")
+ queryBuilder.append(INSTANCES_WHERE)
+ queryBuilder.append(") OR (")
+ queryBuilder.append(INSTANCES_WHERE)
+ queryBuilder.append(")")
+ val queryArgs = arrayOf(
+ // allday selection
+ "1", /* visible = ? */
+ utcStartMin.toString(), /* begin >= ? */
+ utcStartMax.toString(), /* begin <= ? */
+ "1", /* allDay = ? */ // non-allday selection
+ "1", /* visible = ? */
+ currentMillis.toString(), /* begin >= ? */
+ localStartMax.toString(), /* begin <= ? */
+ "0" /* allDay = ? */
+ )
+
+ val cursor: Cursor? = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION,
+ queryBuilder.toString(), queryArgs, null)
+ return cursor
+ }
+
+ /**
+ * Queries for all the reminders of the events in the instancesCursor, and schedules
+ * the alarm for the next upcoming reminder.
+ */
+ @JvmStatic private fun queryNextReminderAndSchedule(
+ instancesCursor: Cursor,
+ context: Context,
+ contentResolver: ContentResolver,
+ alarmManager: AlarmManagerInterface,
+ batchSize: Int,
+ currentMillis: Long
+ ) {
+ if (AlertService.DEBUG) {
+ val eventCount: Int = instancesCursor.getCount()
+ if (eventCount == 0) {
+ Log.d(TAG, "No events found starting within 1 week.")
+ } else {
+ Log.d(TAG, "Query result count for events starting within 1 week: $eventCount")
+ }
+ }
+
+ // Put query results of all events starting within some interval into map of event ID to
+ // local start time.
+ val eventMap: HashMap<Int?, List<Long>?> = HashMap<Int?, List<Long>?>()
+ val timeObj = Time()
+ var nextAlarmTime = Long.MAX_VALUE
+ var nextAlarmEventId = 0
+ instancesCursor.moveToPosition(-1)
+ while (!instancesCursor.isAfterLast()) {
+ var index = 0
+ eventMap.clear()
+ val eventIdsForQuery = StringBuilder()
+ eventIdsForQuery.append('(')
+ while (index++ < batchSize && instancesCursor.moveToNext()) {
+ val eventId: Int = instancesCursor.getInt(INSTANCES_INDEX_EVENTID)
+ val begin: Long = instancesCursor.getLong(INSTANCES_INDEX_BEGIN)
+ val allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0
+ var localStartTime: Long
+ localStartTime = if (allday) {
+ // Adjust allday to local time.
+ Utils.convertAlldayUtcToLocal(
+ timeObj, begin,
+ Time.getCurrentTimezone()
+ )
+ } else {
+ begin
+ }
+ var startTimes: List<Long>? = eventMap.get(eventId)
+ if (startTimes == null) {
+ startTimes = mutableListOf<Long>() as List<Long>
+ eventMap.put(eventId, startTimes)
+ eventIdsForQuery.append(eventId)
+ eventIdsForQuery.append(",")
+ }
+ startTimes.add(localStartTime)
+
+ // Log for debugging.
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ timeObj.set(localStartTime)
+ val msg = StringBuilder()
+ msg.append("Events cursor result -- eventId:").append(eventId)
+ msg.append(", allDay:").append(allday)
+ msg.append(", start:").append(localStartTime)
+ msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")")
+ Log.d(TAG, msg.toString())
+ }
+ }
+ if (eventIdsForQuery[eventIdsForQuery.length - 1] == ',') {
+ eventIdsForQuery.deleteCharAt(eventIdsForQuery.length - 1)
+ }
+ eventIdsForQuery.append(')')
+
+ // Query the reminders table for the events found.
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(
+ Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ REMINDERS_WHERE + eventIdsForQuery, null, null
+ )
+
+ // Process the reminders query results to find the next reminder time.
+ cursor?.moveToPosition(-1)
+ while (cursor!!.moveToNext()) {
+ val eventId: Int = cursor.getInt(REMINDERS_INDEX_EVENT_ID)
+ val reminderMinutes: Int = cursor.getInt(REMINDERS_INDEX_MINUTES)
+ val startTimes: List<Long>? = eventMap.get(eventId)
+ if (startTimes != null) {
+ for (startTime in startTimes) {
+ val alarmTime: Long = startTime -
+ reminderMinutes * DateUtils.MINUTE_IN_MILLIS
+ if (alarmTime > currentMillis && alarmTime < nextAlarmTime) {
+ nextAlarmTime = alarmTime
+ nextAlarmEventId = eventId
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ timeObj.set(alarmTime)
+ val msg = StringBuilder()
+ msg.append("Reminders cursor result -- eventId:").append(eventId)
+ msg.append(", startTime:").append(startTime)
+ msg.append(", minutes:").append(reminderMinutes)
+ msg.append(", alarmTime:").append(alarmTime)
+ msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P"))
+ .append(")")
+ Log.d(TAG, msg.toString())
+ }
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close()
+ }
+ }
+ }
+
+ // Schedule the alarm for the next reminder time.
+ if (nextAlarmTime < Long.MAX_VALUE) {
+ scheduleAlarm(
+ context,
+ nextAlarmEventId.toLong(),
+ nextAlarmTime,
+ currentMillis,
+ alarmManager
+ )
+ }
+ }
+
+ /**
+ * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified
+ * alarm time with a slight delay (to account for the possible duplicate broadcast
+ * from the provider).
+ */
+ @JvmStatic private fun scheduleAlarm(
+ context: Context,
+ eventId: Long,
+ alarmTimeInput: Long,
+ currentMillis: Long,
+ alarmManager: AlarmManagerInterface
+ ) {
+ // Max out the alarm time to 1 day out, so an alert for an event far in the future
+ // (not present in our event query results for a limited range) can only be at
+ // most 1 day late.
+ var alarmTime = alarmTimeInput
+ val maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS
+ if (alarmTime > maxAlarmTime) {
+ alarmTime = maxAlarmTime
+ }
+
+ // Add a slight delay (see comments on the member var).
+ alarmTime += ALARM_DELAY_MS.toLong()
+ if (AlertService.DEBUG) {
+ val time = Time()
+ time.set(alarmTime)
+ val schedTime: String = time.format("%a, %b %d, %Y %I:%M%P")
+ Log.d(
+ TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId +
+ " at " + alarmTime + " (" + schedTime + ")"
+ )
+ }
+
+ // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager. The extra is
+ // only used by AlertService for logging. It is ignored by Intent.filterEquals,
+ // so this scheduling will still overwrite the alarm that was previously pending.
+ // Note that the 'setClass' is required, because otherwise it seems the broadcast
+ // can be eaten by other apps and we somehow may never receive it.
+ val intent = Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION)
+ intent.setClass(context, AlertReceiver::class.java)
+ intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime)
+ val pi: PendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi)
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java
deleted file mode 100644
index ce80cae1..00000000
--- a/src/com/android/calendar/alerts/AlertReceiver.java
+++ /dev/null
@@ -1,123 +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.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.ContentUris;
-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.PowerManager;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.telephony.TelephonyManager;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.TextAppearanceSpan;
-import android.text.style.URLSpan;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-import com.android.calendar.alerts.AlertService.NotificationWrapper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Receives android.intent.action.EVENT_REMINDER intents and handles
- * event reminders. The intent URI specifies an alert id in the
- * CalendarAlerts database table. This class also receives the
- * BOOT_COMPLETED intent so that it can add a status bar notification
- * if there are Calendar event alarms that have not been dismissed.
- * It also receives the TIME_CHANGED action so that it can fire off
- * snoozed alarms that have become ready. The real work is done in
- * the AlertService class.
- *
- * To trigger this code after pushing the apk to device:
- * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
- * -n "com.android.calendar/.alerts.AlertReceiver"
- */
-public class AlertReceiver extends BroadcastReceiver {
- private static final String TAG = "AlertReceiver";
-
- // 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";
-
- public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders";
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (AlertService.DEBUG) {
- Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
- }
- closeNotificationShade(context);
- }
-
- public static NotificationWrapper makeBasicNotification(Context context, String title,
- String summaryText, long startMillis, long endMillis, long eventId,
- int notificationId, boolean doPopup, int priority) {
- Notification n = buildBasicNotification(new Notification.Builder(context),
- context, title, summaryText, startMillis, endMillis, eventId, notificationId,
- doPopup, priority, false);
- return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup);
- }
-
- private static Notification buildBasicNotification(Notification.Builder notificationBuilder,
- Context context, String title, String summaryText, long startMillis, long endMillis,
- long eventId, int notificationId, boolean doPopup, int priority,
- boolean addActionButtons) {
- Resources resources = context.getResources();
- if (title == null || title.length() == 0) {
- title = resources.getString(R.string.no_title_label);
- }
-
- // Create the base notification.
- notificationBuilder.setContentTitle(title);
- notificationBuilder.setContentText(summaryText);
- notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar);
- if (Utils.isJellybeanOrLater()) {
- // Turn off timestamp.
- notificationBuilder.setWhen(0);
-
- // 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);
- }
- return notificationBuilder.getNotification();
- }
-
- private void closeNotificationShade(Context context) {
- Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.sendBroadcast(closeNotificationShadeIntent);
- }
-}
diff --git a/src/com/android/calendar/alerts/AlertReceiver.kt b/src/com/android/calendar/alerts/AlertReceiver.kt
new file mode 100644
index 00000000..21afa90c
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertReceiver.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.Notification
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.util.Log
+import com.android.calendar.R
+import com.android.calendar.Utils
+import com.android.calendar.alerts.AlertService.NotificationWrapper
+
+/**
+ * Receives android.intent.action.EVENT_REMINDER intents and handles
+ * event reminders. The intent URI specifies an alert id in the
+ * CalendarAlerts database table. This class also receives the
+ * BOOT_COMPLETED intent so that it can add a status bar notification
+ * if there are Calendar event alarms that have not been dismissed.
+ * It also receives the TIME_CHANGED action so that it can fire off
+ * snoozed alarms that have become ready. The real work is done in
+ * the AlertService class.
+ *
+ * To trigger this code after pushing the apk to device:
+ * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
+ * -n "com.android.calendar/.alerts.AlertReceiver"
+ */
+class AlertReceiver : BroadcastReceiver() {
+ @Override
+ override fun onReceive(context: Context, intent: Intent) {
+ if (AlertService.DEBUG) {
+ Log.d(TAG, "onReceive: a=" + intent.getAction().toString() + " " + intent.toString())
+ }
+ }
+
+ companion object {
+ private const val TAG = "AlertReceiver"
+
+ // The broadcast for notification refreshes scheduled by the app. This is to
+ // distinguish the EVENT_REMINDER broadcast sent by the provider.
+ const val EVENT_REMINDER_APP_ACTION = "com.android.calendar.EVENT_REMINDER_APP"
+ const val ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"
+ fun makeBasicNotification(
+ context: Context,
+ title: String,
+ summaryText: String,
+ startMillis: Long,
+ endMillis: Long,
+ eventId: Long,
+ notificationId: Int,
+ doPopup: Boolean,
+ priority: Int
+ ): NotificationWrapper {
+ val n: Notification = buildBasicNotification(
+ Notification.Builder(context),
+ context,
+ title,
+ summaryText,
+ startMillis,
+ endMillis,
+ eventId,
+ notificationId,
+ doPopup,
+ priority, false
+ )
+ return NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup)
+ }
+
+ private fun buildBasicNotification(
+ notificationBuilder: Notification.Builder,
+ context: Context,
+ title: String,
+ summaryText: String,
+ startMillis: Long,
+ endMillis: Long,
+ eventId: Long,
+ notificationId: Int,
+ doPopup: Boolean,
+ priority: Int,
+ addActionButtons: Boolean
+ ): Notification {
+ var title: String? = title
+ val resources: Resources = context.getResources()
+ if (title == null || title.length == 0) {
+ title = resources.getString(R.string.no_title_label)
+ }
+
+ // Create the base notification.
+ notificationBuilder.setContentTitle(title)
+ notificationBuilder.setContentText(summaryText)
+ notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar)
+ if (Utils.isJellybeanOrLater()) {
+ // Turn off timestamp.
+ notificationBuilder.setWhen(0)
+
+ // 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)
+ }
+ return notificationBuilder.getNotification()
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java
deleted file mode 100644
index d2c994da..00000000
--- a/src/com/android/calendar/alerts/AlertService.java
+++ /dev/null
@@ -1,202 +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 android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * This service is used to handle calendar event reminders.
- */
-public class AlertService extends Service {
- static final boolean DEBUG = true;
- private static final String TAG = "AlertService";
-
- private volatile Looper mServiceLooper;
-
- static final String[] ALERT_PROJECTION = new String[] {
- CalendarAlerts._ID, // 0
- CalendarAlerts.EVENT_ID, // 1
- CalendarAlerts.STATE, // 2
- CalendarAlerts.TITLE, // 3
- CalendarAlerts.EVENT_LOCATION, // 4
- CalendarAlerts.SELF_ATTENDEE_STATUS, // 5
- CalendarAlerts.ALL_DAY, // 6
- CalendarAlerts.ALARM_TIME, // 7
- CalendarAlerts.MINUTES, // 8
- CalendarAlerts.BEGIN, // 9
- CalendarAlerts.END, // 10
- CalendarAlerts.DESCRIPTION, // 11
- };
-
- private static final int ALERT_INDEX_ID = 0;
- private static final int ALERT_INDEX_EVENT_ID = 1;
- private static final int ALERT_INDEX_STATE = 2;
- private static final int ALERT_INDEX_TITLE = 3;
- private static final int ALERT_INDEX_EVENT_LOCATION = 4;
- private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
- private static final int ALERT_INDEX_ALL_DAY = 6;
- private static final int ALERT_INDEX_ALARM_TIME = 7;
- private static final int ALERT_INDEX_MINUTES = 8;
- private static final int ALERT_INDEX_BEGIN = 9;
- private static final int ALERT_INDEX_END = 10;
- private static final int ALERT_INDEX_DESCRIPTION = 11;
-
- private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
- + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
-
- private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
- Integer.toString(CalendarAlerts.STATE_FIRED),
- Integer.toString(CalendarAlerts.STATE_SCHEDULED)
- };
-
- private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
-
- private static final String DISMISS_OLD_SELECTION = CalendarAlerts.END + "<? AND "
- + CalendarAlerts.STATE + "=?";
-
- private static final int MINUTE_MS = 60 * 1000;
-
- // The grace period before changing a notification's priority bucket.
- private static final int MIN_DEPRIORITIZE_GRACE_PERIOD_MS = 15 * MINUTE_MS;
-
- // Hard limit to the number of notifications displayed.
- public static final int MAX_NOTIFICATIONS = 20;
-
- // Added wrapper for testing
- public static class NotificationWrapper {
- Notification mNotification;
- long mEventId;
- long mBegin;
- long mEnd;
- ArrayList<NotificationWrapper> mNw;
-
- public NotificationWrapper(Notification n, int notificationId, long eventId,
- long startMillis, long endMillis, boolean doPopup) {
- mNotification = n;
- mEventId = eventId;
- mBegin = startMillis;
- mEnd = endMillis;
-
- // popup?
- // notification id?
- }
-
- public NotificationWrapper(Notification n) {
- mNotification = n;
- }
-
- public void add(NotificationWrapper nw) {
- if (mNw == null) {
- mNw = new ArrayList<NotificationWrapper>();
- }
- mNw.add(nw);
- }
- }
-
- // Added wrapper for testing
- public static class NotificationMgrWrapper extends NotificationMgr {
- NotificationManager mNm;
-
- public NotificationMgrWrapper(NotificationManager nm) {
- mNm = nm;
- }
-
- @Override
- public void cancel(int id) {
- mNm.cancel(id);
- }
-
- @Override
- public void notify(int id, NotificationWrapper nw) {
- mNm.notify(id, nw.mNotification);
- }
- }
-
- static class NotificationInfo {
- String eventName;
- String location;
- String description;
- long startMillis;
- long endMillis;
- long eventId;
- boolean allDay;
- boolean newAlert;
-
- NotificationInfo(String eventName, String location, String description, long startMillis,
- long endMillis, long eventId, boolean allDay, boolean newAlert) {
- this.eventName = eventName;
- this.location = location;
- this.description = description;
- this.startMillis = startMillis;
- this.endMillis = endMillis;
- this.eventId = eventId;
- this.newAlert = newAlert;
- this.allDay = allDay;
- }
- }
-
- @Override
- public void onCreate() {
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return START_REDELIVER_INTENT;
- }
-
- @Override
- public void onDestroy() {
- mServiceLooper.quit();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-}
diff --git a/src/com/android/calendar/alerts/AlertService.kt b/src/com/android/calendar/alerts/AlertService.kt
new file mode 100644
index 00000000..bc1b4e04
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertService.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.Notification
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.os.Looper
+import android.provider.CalendarContract.CalendarAlerts
+import java.util.ArrayList
+
+/**
+ * This service is used to handle calendar event reminders.
+ */
+class AlertService : Service() {
+ @Volatile
+ private var mServiceLooper: Looper? = null
+
+ // Added wrapper for testing
+ class NotificationWrapper {
+ var mNotification: Notification
+ var mEventId: Long = 0
+ var mBegin: Long = 0
+ var mEnd: Long = 0
+ var mNw: ArrayList<NotificationWrapper>? = null
+
+ constructor(
+ n: Notification,
+ notificationId: Int,
+ eventId: Long,
+ startMillis: Long,
+ endMillis: Long,
+ doPopup: Boolean
+ ) {
+ mNotification = n
+ mEventId = eventId
+ mBegin = startMillis
+ mEnd = endMillis
+
+ // popup?
+ // notification id?
+ }
+
+ constructor(n: Notification) {
+ mNotification = n
+ }
+
+ fun add(nw: NotificationWrapper?) {
+ val temp = mNw
+ if (temp == null) {
+ mNw = ArrayList<NotificationWrapper>()
+ }
+ mNw?.add(nw as AlertService.NotificationWrapper)
+ }
+ }
+
+ // Added wrapper for testing
+ class NotificationMgrWrapper(nm: NotificationManager) : NotificationMgr() {
+ var mNm: NotificationManager
+ @Override
+ override fun cancel(id: Int) {
+ mNm.cancel(id)
+ }
+
+ @Override
+ override fun notify(id: Int, nw: NotificationWrapper?) {
+ mNm.notify(id, nw?.mNotification)
+ }
+
+ init {
+ mNm = nm
+ }
+ }
+
+ internal class NotificationInfo(
+ var eventName: String,
+ var location: String,
+ var description: String,
+ var startMillis: Long,
+ var endMillis: Long,
+ var eventId: Long,
+ var allDay: Boolean,
+ var newAlert: Boolean
+ )
+
+ @Override
+ override fun onCreate() {
+ }
+
+ @Override
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ return START_REDELIVER_INTENT
+ }
+
+ @Override
+ override fun onDestroy() {
+ mServiceLooper?.quit()
+ }
+
+ @Override
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ companion object {
+ const val DEBUG = true
+ private const val TAG = "AlertService"
+ val ALERT_PROJECTION = arrayOf<String>(
+ CalendarAlerts._ID, // 0
+ CalendarAlerts.EVENT_ID, // 1
+ CalendarAlerts.STATE, // 2
+ CalendarAlerts.TITLE, // 3
+ CalendarAlerts.EVENT_LOCATION, // 4
+ CalendarAlerts.SELF_ATTENDEE_STATUS, // 5
+ CalendarAlerts.ALL_DAY, // 6
+ CalendarAlerts.ALARM_TIME, // 7
+ CalendarAlerts.MINUTES, // 8
+ CalendarAlerts.BEGIN, // 9
+ CalendarAlerts.END, // 10
+ CalendarAlerts.DESCRIPTION
+ )
+ private const val ALERT_INDEX_ID = 0
+ private const val ALERT_INDEX_EVENT_ID = 1
+ private const val ALERT_INDEX_STATE = 2
+ private const val ALERT_INDEX_TITLE = 3
+ private const val ALERT_INDEX_EVENT_LOCATION = 4
+ private const val ALERT_INDEX_SELF_ATTENDEE_STATUS = 5
+ private const val ALERT_INDEX_ALL_DAY = 6
+ private const val ALERT_INDEX_ALARM_TIME = 7
+ private const val ALERT_INDEX_MINUTES = 8
+ private const val ALERT_INDEX_BEGIN = 9
+ private const val ALERT_INDEX_END = 10
+ private const val ALERT_INDEX_DESCRIPTION = 11
+ private val ACTIVE_ALERTS_SELECTION = ("(" + CalendarAlerts.STATE.toString() + "=? OR " +
+ CalendarAlerts.STATE.toString() + "=?) AND " +
+ CalendarAlerts.ALARM_TIME.toString() + "<=")
+ private val ACTIVE_ALERTS_SELECTION_ARGS = arrayOf<String>(
+ Integer.toString(CalendarAlerts.STATE_FIRED),
+ Integer.toString(CalendarAlerts.STATE_SCHEDULED)
+ )
+ private const val ACTIVE_ALERTS_SORT = "begin DESC, end DESC"
+ private val DISMISS_OLD_SELECTION: String = (CalendarAlerts.END.toString() + "<? AND " +
+ CalendarAlerts.STATE + "=?")
+ private const val MINUTE_MS = 60 * 1000
+
+ // The grace period before changing a notification's priority bucket.
+ private const val MIN_DEPRIORITIZE_GRACE_PERIOD_MS = 15 * MINUTE_MS
+
+ // Hard limit to the number of notifications displayed.
+ const val MAX_NOTIFICATIONS = 20
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertUtils.java b/src/com/android/calendar/alerts/AlertUtils.java
deleted file mode 100644
index b9aaec29..00000000
--- a/src/com/android/calendar/alerts/AlertUtils.java
+++ /dev/null
@@ -1,108 +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.app.PendingIntent;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-public class AlertUtils {
- private static final String TAG = "AlertUtils";
- static final boolean DEBUG = true;
-
- public static final long SNOOZE_DELAY = 5 * 60 * 1000L;
-
- // We use one notification id for the expired events notification. All
- // other notifications (the 'active' future/concurrent ones) use a unique ID.
- public static final int EXPIRED_GROUP_NOTIFICATION_ID = 0;
-
- public static final String EVENT_ID_KEY = "eventid";
- public static final String EVENT_START_KEY = "eventstart";
- public static final String EVENT_END_KEY = "eventend";
- public static final String NOTIFICATION_ID_KEY = "notificationid";
- public static final String EVENT_IDS_KEY = "eventids";
- public static final String EVENT_STARTS_KEY = "starts";
-
- // A flag for using local storage to save alert state instead of the alerts DB table.
- // This allows the unbundled app to run alongside other calendar apps without eating
- // alerts from other apps.
- static boolean BYPASS_DB = true;
-
- /**
- * Creates an AlarmManagerInterface that wraps a real AlarmManager. The alarm code
- * was abstracted to an interface to make it testable.
- */
- public static AlarmManagerInterface createAlarmManager(Context context) {
- final AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- return new AlarmManagerInterface() {
- @Override
- public void set(int type, long triggerAtMillis, PendingIntent operation) {
- if (Utils.isKeyLimePieOrLater()) {
- mgr.setExact(type, triggerAtMillis, operation);
- } else {
- mgr.set(type, triggerAtMillis, operation);
- }
- }
- };
- }
-
- /**
- * Schedules an alarm intent with the system AlarmManager that will notify
- * listeners when a reminder should be fired. The provider will keep
- * scheduled reminders up to date but apps may use this to implement snooze
- * functionality without modifying the reminders table. Scheduled alarms
- * will generate an intent using AlertReceiver.EVENT_REMINDER_APP_ACTION.
- *
- * @param context A context for referencing system resources
- * @param manager The AlarmManager to use or null
- * @param alarmTime The time to fire the intent in UTC millis since epoch
- */
- public static void scheduleAlarm(Context context, AlarmManagerInterface manager,
- long alarmTime) {
- }
-
- public static Intent buildEventViewIntent(Context c, long eventId, long begin, long end) {
- Intent i = new Intent(Intent.ACTION_VIEW);
- Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
- builder.appendEncodedPath("events/" + eventId);
- i.setData(builder.build());
- i.setClass(c, EventInfoActivity.class);
- i.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin);
- i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
- return i;
- }
-}
diff --git a/src/com/android/calendar/alerts/AlertUtils.kt b/src/com/android/calendar/alerts/AlertUtils.kt
new file mode 100644
index 00000000..18b7e7d1
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertUtils.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.Utils
+
+object AlertUtils {
+ private const val TAG = "AlertUtils"
+ const val DEBUG = true
+ const val SNOOZE_DELAY = 5 * 60 * 1000L
+
+ // We use one notification id for the expired events notification. All
+ // other notifications (the 'active' future/concurrent ones) use a unique ID.
+ const val EXPIRED_GROUP_NOTIFICATION_ID = 0
+ const val EVENT_ID_KEY = "eventid"
+ const val EVENT_START_KEY = "eventstart"
+ const val EVENT_END_KEY = "eventend"
+ const val NOTIFICATION_ID_KEY = "notificationid"
+ const val EVENT_IDS_KEY = "eventids"
+ const val EVENT_STARTS_KEY = "starts"
+
+ // A flag for using local storage to save alert state instead of the alerts DB table.
+ // This allows the unbundled app to run alongside other calendar apps without eating
+ // alerts from other apps.
+ var BYPASS_DB = true
+
+ /**
+ * Creates an AlarmManagerInterface that wraps a real AlarmManager. The alarm code
+ * was abstracted to an interface to make it testable.
+ */
+ @JvmStatic fun createAlarmManager(context: Context): AlarmManagerInterface {
+ val mgr: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ return object : AlarmManagerInterface {
+ override operator fun set(type: Int, triggerAtMillis: Long, operation: PendingIntent?) {
+ if (com.android.calendar.Utils.isKeyLimePieOrLater()) {
+ mgr.setExact(type, triggerAtMillis, operation)
+ } else {
+ mgr.set(type, triggerAtMillis, operation)
+ }
+ }
+ }
+ }
+
+ /**
+ * Schedules an alarm intent with the system AlarmManager that will notify
+ * listeners when a reminder should be fired. The provider will keep
+ * scheduled reminders up to date but apps may use this to implement snooze
+ * functionality without modifying the reminders table. Scheduled alarms
+ * will generate an intent using AlertReceiver.EVENT_REMINDER_APP_ACTION.
+ *
+ * @param context A context for referencing system resources
+ * @param manager The AlarmManager to use or null
+ * @param alarmTime The time to fire the intent in UTC millis since epoch
+ */
+ @JvmStatic fun scheduleAlarm(
+ context: Context?,
+ manager: AlarmManagerInterface?,
+ alarmTime: Long
+ ) {
+ }
+
+ @JvmStatic fun buildEventViewIntent(c: Context, eventId: Long, begin: Long, end: Long): Intent {
+ val i = Intent(Intent.ACTION_VIEW)
+ val builder: Uri.Builder = CalendarContract.CONTENT_URI.buildUpon()
+ builder.appendEncodedPath("events/$eventId")
+ i.setData(builder.build())
+ i.setClass(c, EventInfoActivity::class.java)
+ i.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin)
+ i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end)
+ return i
+ }
+}
diff --git a/src/com/android/calendar/alerts/DismissAlarmsService.java b/src/com/android/calendar/alerts/DismissAlarmsService.java
deleted file mode 100644
index 1ec3c22d..00000000
--- a/src/com/android/calendar/alerts/DismissAlarmsService.java
+++ /dev/null
@@ -1,136 +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.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;
-import androidx.core.app.TaskStackBuilder;
-
-import android.util.Log;
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.alerts.GlobalDismissManager.AlarmId;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Service for asynchronously marking fired alarms as dismissed.
- */
-public class DismissAlarmsService extends IntentService {
- private static final String TAG = "DismissAlarmsService";
- public static final String SHOW_ACTION = "com.android.calendar.SHOW";
- public static final String DISMISS_ACTION = "com.android.calendar.DISMISS";
-
- private static final String[] PROJECTION = new String[] {
- CalendarAlerts.STATE,
- };
- private static final int COLUMN_INDEX_STATE = 0;
-
- public DismissAlarmsService() {
- super("DismissAlarmsService");
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onHandleIntent(Intent intent) {
- if (AlertService.DEBUG) {
- Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
- }
-
- 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);
- long[] eventIds = intent.getLongArrayExtra(AlertUtils.EVENT_IDS_KEY);
- long[] eventStarts = intent.getLongArrayExtra(AlertUtils.EVENT_STARTS_KEY);
- int notificationId = intent.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, -1);
- List<AlarmId> alarmIds = new LinkedList<AlarmId>();
-
- Uri uri = CalendarAlerts.CONTENT_URI;
- String selection;
-
- // Dismiss a specific fired alarm if id is present, otherwise, dismiss all alarms
- if (eventId != -1) {
- alarmIds.add(new AlarmId(eventId, eventStart));
- selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED + " AND " +
- CalendarAlerts.EVENT_ID + "=" + eventId;
- } else if (eventIds != null && eventIds.length > 0 &&
- eventStarts != null && eventIds.length == eventStarts.length) {
- selection = buildMultipleEventsQuery(eventIds);
- for (int i = 0; i < eventIds.length; i++) {
- alarmIds.add(new AlarmId(eventIds[i], eventStarts[i]));
- }
- } else {
- // NOTE: I don't believe that this ever happens.
- selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED;
- }
-
- GlobalDismissManager.dismissGlobally(getApplicationContext(), alarmIds);
-
- ContentResolver resolver = getContentResolver();
- ContentValues values = new ContentValues();
- values.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
- resolver.update(uri, values, selection, null);
-
- // Remove from notification bar.
- if (notificationId != -1) {
- NotificationManager nm =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(notificationId);
- }
-
- if (SHOW_ACTION.equals(intent.getAction())) {
- // Show event on Calendar app by building an intent and task stack to start
- // EventInfoActivity with AllInOneActivity as the parent activity rooted to home.
- Intent i = AlertUtils.buildEventViewIntent(this, eventId, eventStart, eventEnd);
-
- TaskStackBuilder.create(this)
- .addParentStack(EventInfoActivity.class).addNextIntent(i).startActivities();
- }
- }
-
- private String buildMultipleEventsQuery(long[] eventIds) {
- StringBuilder selection = new StringBuilder();
- selection.append(CalendarAlerts.STATE);
- selection.append("=");
- selection.append(CalendarAlerts.STATE_FIRED);
- if (eventIds.length > 0) {
- selection.append(" AND (");
- selection.append(CalendarAlerts.EVENT_ID);
- selection.append("=");
- selection.append(eventIds[0]);
- for (int i = 1; i < eventIds.length; i++) {
- selection.append(" OR ");
- selection.append(CalendarAlerts.EVENT_ID);
- selection.append("=");
- selection.append(eventIds[i]);
- }
- selection.append(")");
- }
- return selection.toString();
- }
-}
diff --git a/src/com/android/calendar/alerts/DismissAlarmsService.kt b/src/com/android/calendar/alerts/DismissAlarmsService.kt
new file mode 100644
index 00000000..88683d3a
--- /dev/null
+++ b/src/com/android/calendar/alerts/DismissAlarmsService.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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
+import androidx.core.app.TaskStackBuilder
+import android.util.Log
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.alerts.GlobalDismissManager.AlarmId
+import java.util.LinkedList
+import java.util.List
+
+/**
+ * Service for asynchronously marking fired alarms as dismissed.
+ */
+class DismissAlarmsService : IntentService("DismissAlarmsService") {
+ @Override
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ @Override
+ override fun onHandleIntent(intent: Intent?) {
+ if (AlertService.DEBUG) {
+ Log.d(TAG, "onReceive: a=" + intent?.getAction().toString() + " " + intent.toString())
+ }
+ val eventId = intent?.getLongExtra(AlertUtils.EVENT_ID_KEY, -1)
+ val eventStart = intent?.getLongExtra(AlertUtils.EVENT_START_KEY, -1)
+ val eventEnd = intent?.getLongExtra(AlertUtils.EVENT_END_KEY, -1)
+ val eventIds = intent?.getLongArrayExtra(AlertUtils.EVENT_IDS_KEY)
+ val eventStarts = intent?.getLongArrayExtra(AlertUtils.EVENT_STARTS_KEY)
+ val notificationId = intent?.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, -1)
+ val alarmIds = LinkedList<AlarmId>()
+ val uri: Uri = CalendarAlerts.CONTENT_URI
+ val selection: String
+
+ // Dismiss a specific fired alarm if id is present, otherwise, dismiss all alarms
+ if (eventId != -1L) {
+ alarmIds.add(AlarmId(eventId as Long, eventStart as Long))
+ selection =
+ CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED + " AND " +
+ CalendarAlerts.EVENT_ID + "=" + eventId
+ } else if (eventIds != null && eventIds.size > 0 && eventStarts != null &&
+ eventIds.size == eventStarts.size) {
+ selection = buildMultipleEventsQuery(eventIds)
+ for (i in eventIds.indices) {
+ alarmIds.add(AlarmId(eventIds[i], eventStarts[i]))
+ }
+ } else {
+ // NOTE: I don't believe that this ever happens.
+ selection = CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED
+ }
+ GlobalDismissManager.dismissGlobally(getApplicationContext(),
+ alarmIds as List<GlobalDismissManager.AlarmId>)
+ val resolver: ContentResolver = getContentResolver()
+ val values = ContentValues()
+ values.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED)
+ resolver.update(uri, values, selection, null)
+
+ // Remove from notification bar.
+ if (notificationId != -1) {
+ val nm: NotificationManager =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ nm.cancel(notificationId as Int)
+ }
+ if (SHOW_ACTION.equals(intent?.getAction())) {
+ // Show event on Calendar app by building an intent and task stack to start
+ // EventInfoActivity with AllInOneActivity as the parent activity rooted to home.
+ val i: Intent = AlertUtils.buildEventViewIntent(this, eventId as Long,
+ eventStart as Long, eventEnd as Long)
+ TaskStackBuilder.create(this)
+ .addParentStack(EventInfoActivity::class.java).addNextIntent(i).startActivities()
+ }
+ }
+
+ private fun buildMultipleEventsQuery(eventIds: LongArray): String {
+ val selection = StringBuilder()
+ selection.append(CalendarAlerts.STATE)
+ selection.append("=")
+ selection.append(CalendarAlerts.STATE_FIRED)
+ if (eventIds.size > 0) {
+ selection.append(" AND (")
+ selection.append(CalendarAlerts.EVENT_ID)
+ selection.append("=")
+ selection.append(eventIds[0])
+ for (i in 1 until eventIds.size) {
+ selection.append(" OR ")
+ selection.append(CalendarAlerts.EVENT_ID)
+ selection.append("=")
+ selection.append(eventIds[i])
+ }
+ selection.append(")")
+ }
+ return selection.toString()
+ }
+
+ companion object {
+ private const val TAG = "DismissAlarmsService"
+ const val SHOW_ACTION = "com.android.calendar.SHOW"
+ const val DISMISS_ACTION = "com.android.calendar.DISMISS"
+ private val PROJECTION = arrayOf<String>(
+ CalendarAlerts.STATE
+ )
+ private const val COLUMN_INDEX_STATE = 0
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.java b/src/com/android/calendar/alerts/GlobalDismissManager.java
deleted file mode 100644
index 27b3e162..00000000
--- a/src/com/android/calendar/alerts/GlobalDismissManager.java
+++ /dev/null
@@ -1,84 +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.alerts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.calendar.R;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Utilities for managing notification dismissal across devices.
- */
-public class GlobalDismissManager extends BroadcastReceiver {
- public static class AlarmId {
- public long mEventId;
- public long mStart;
-
- public AlarmId(long id, long start) {
- mEventId = id;
- mStart = start;
- }
- }
-
- /**
- * Globally dismiss notifications that are backed by the same events.
- *
- * @param context application context
- * @param alarmIds Unique identifiers for events that have been dismissed by the user.
- * @return true if notification_sender_id is available
- */
- public static void dismissGlobally(Context context, List<AlarmId> alarmIds) {
- Set<Long> eventIds = new HashSet<Long>(alarmIds.size());
- for (AlarmId alarmId: alarmIds) {
- eventIds.add(alarmId.mEventId);
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void onReceive(Context context, Intent intent) {
- new AsyncTask<Pair<Context, Intent>, Void, Void>() {
- @Override
- protected Void doInBackground(Pair<Context, Intent>... params) {
- return null;
- }
- }.execute(new Pair<Context, Intent>(context, intent));
- }
-}
diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.kt b/src/com/android/calendar/alerts/GlobalDismissManager.kt
new file mode 100644
index 00000000..4cf0bc0c
--- /dev/null
+++ b/src/com/android/calendar/alerts/GlobalDismissManager.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.AsyncTask
+import android.util.Pair
+import java.util.HashSet
+import java.util.List
+
+/**
+ * Utilities for managing notification dismissal across devices.
+ */
+class GlobalDismissManager : BroadcastReceiver() {
+ class AlarmId(var mEventId: Long, var mStart: Long)
+
+ @Override
+ @SuppressWarnings("unchecked")
+ override fun onReceive(context: Context?, intent: Intent?) {
+ object : AsyncTask<Pair<Context?, Intent?>?, Void?, Void?>() {
+ @Override
+ protected override fun doInBackground(vararg params: Pair<Context?, Intent?>?): Void? {
+ return null
+ }
+ }.execute(Pair<Context?, Intent?>(context, intent))
+ }
+
+ companion object {
+ /**
+ * Globally dismiss notifications that are backed by the same events.
+ *
+ * @param context application context
+ * @param alarmIds Unique identifiers for events that have been dismissed by the user.
+ * @return true if notification_sender_id is available
+ */
+ @JvmStatic fun dismissGlobally(context: Context?, alarmIds: List<AlarmId>) {
+ val eventIds: HashSet<Long> = HashSet<Long>(alarmIds.size)
+ for (alarmId in alarmIds) {
+ eventIds.add(alarmId.mEventId)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/InitAlarmsService.java b/src/com/android/calendar/alerts/InitAlarmsService.java
deleted file mode 100644
index 3a9b0b2c..00000000
--- a/src/com/android/calendar/alerts/InitAlarmsService.java
+++ /dev/null
@@ -1,62 +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.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.CalendarContract;
-import android.util.Log;
-
-/**
- * Service for clearing all scheduled alerts from the CalendarAlerts table and
- * rescheduling them. This is expected to be called only on boot up, to restore
- * the AlarmManager alarms that were lost on device restart.
- */
-public class InitAlarmsService extends IntentService {
- private static final String TAG = "InitAlarmsService";
- private static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
- private static final Uri SCHEDULE_ALARM_REMOVE_URI = Uri.withAppendedPath(
- CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
-
- // Delay for rescheduling the alarms must be great enough to minimize race
- // conditions with the provider's boot up actions.
- private static final long DELAY_MS = 30000;
-
- public InitAlarmsService() {
- super("InitAlarmsService");
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- // Delay to avoid race condition of in-progress alarm scheduling in provider.
- SystemClock.sleep(DELAY_MS);
- Log.d(TAG, "Clearing and rescheduling alarms.");
- try {
- getContentResolver().update(SCHEDULE_ALARM_REMOVE_URI, new ContentValues(), null,
- null);
- } catch (java.lang.IllegalArgumentException e) {
- // java.lang.IllegalArgumentException:
- // Unknown URI content://com.android.calendar/schedule_alarms_remove
-
- // Until b/7742576 is resolved, just catch the exception so the app won't crash
- Log.e(TAG, "update failed: " + e.toString());
- }
- }
-}
diff --git a/src/com/android/calendar/alerts/InitAlarmsService.kt b/src/com/android/calendar/alerts/InitAlarmsService.kt
new file mode 100644
index 00000000..0ac8a474
--- /dev/null
+++ b/src/com/android/calendar/alerts/InitAlarmsService.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.ContentValues
+import android.content.Intent
+import android.net.Uri
+import android.os.SystemClock
+import android.provider.CalendarContract
+import android.util.Log
+
+/**
+ * Service for clearing all scheduled alerts from the CalendarAlerts table and
+ * rescheduling them. This is expected to be called only on boot up, to restore
+ * the AlarmManager alarms that were lost on device restart.
+ */
+class InitAlarmsService : IntentService("InitAlarmsService") {
+ @Override
+ protected override fun onHandleIntent(intent: Intent?) {
+ // Delay to avoid race condition of in-progress alarm scheduling in provider.
+ SystemClock.sleep(DELAY_MS)
+ Log.d(TAG, "Clearing and rescheduling alarms.")
+ try {
+ getContentResolver().update(
+ SCHEDULE_ALARM_REMOVE_URI, ContentValues(), null,
+ null
+ )
+ } catch (e: java.lang.IllegalArgumentException) {
+ // java.lang.IllegalArgumentException:
+ // Unknown URI content://com.android.calendar/schedule_alarms_remove
+
+ // Until b/7742576 is resolved, just catch the exception so the app won't crash
+ Log.e(TAG, "update failed: " + e.toString())
+ }
+ }
+
+ companion object {
+ private const val TAG = "InitAlarmsService"
+ private const val SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove"
+ private val SCHEDULE_ALARM_REMOVE_URI: Uri = Uri.withAppendedPath(
+ CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH
+ )
+
+ // Delay for rescheduling the alarms must be great enough to minimize race
+ // conditions with the provider's boot up actions.
+ private const val DELAY_MS: Long = 30000
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/NotificationMgr.java b/src/com/android/calendar/alerts/NotificationMgr.kt
index 0ab475c3..609b8141 100644
--- a/src/com/android/calendar/alerts/NotificationMgr.java
+++ b/src/com/android/calendar/alerts/NotificationMgr.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,29 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.calendar.alerts
-package com.android.calendar.alerts;
+import com.android.calendar.alerts.AlertService.NotificationWrapper
-import com.android.calendar.alerts.AlertService.NotificationWrapper;
-
-public abstract class NotificationMgr {
- public abstract void notify(int id, NotificationWrapper notification);
- public abstract void cancel(int id);
+abstract class NotificationMgr {
+ abstract fun notify(id: Int, notification: NotificationWrapper?)
+ abstract fun cancel(id: Int)
/**
* Don't actually use the notification framework's cancelAll since the SyncAdapter
* might post notifications and we don't want to affect those.
*/
- public void cancelAll() {
- cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS);
+ fun cancelAll() {
+ cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS)
}
/**
* Cancels IDs between the specified bounds, inclusively.
*/
- public void cancelAllBetween(int from, int to) {
- for (int i = from; i <= to; i++) {
- cancel(i);
+ fun cancelAllBetween(from: Int, to: Int) {
+ for (i in from..to) {
+ cancel(i)
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/calendar/alerts/QuickResponseActivity.java b/src/com/android/calendar/alerts/QuickResponseActivity.java
deleted file mode 100644
index 3d291d02..00000000
--- a/src/com/android/calendar/alerts/QuickResponseActivity.java
+++ /dev/null
@@ -1,108 +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.ListActivity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.Toast;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.Arrays;
-
-/**
- * Activity which displays when the user wants to email guests from notifications.
- *
- * This presents the user with list if quick responses to be populated in an email
- * to minimize typing.
- *
- */
-public class QuickResponseActivity extends ListActivity implements OnItemClickListener {
- private static final String TAG = "QuickResponseActivity";
- public static final String EXTRA_EVENT_ID = "eventId";
-
- private String[] mResponses = null;
- static long mEventId;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- Intent intent = getIntent();
- if (intent == null) {
- finish();
- return;
- }
-
- mEventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
- if (mEventId == -1) {
- finish();
- return;
- }
-
- // Set listener
- getListView().setOnItemClickListener(QuickResponseActivity.this);
-
- // Populate responses
- String[] responses = Utils.getQuickResponses(this);
- Arrays.sort(responses);
-
- // Add "Custom response..."
- mResponses = new String[responses.length + 1];
- int i;
- for (i = 0; i < responses.length; i++) {
- mResponses[i] = responses[i];
- }
- mResponses[i] = getResources().getString(R.string.quick_response_custom_msg);
-
- setListAdapter(new ArrayAdapter<String>(this, R.layout.quick_response_item, mResponses));
- }
-
- // implements OnItemClickListener
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-
- String body = null;
- if (mResponses != null && position < mResponses.length - 1) {
- body = mResponses[position];
- }
-
- // Start thread to query provider and send mail
- new QueryThread(mEventId, body).start();
- }
-
- private class QueryThread extends Thread {
- long mEventId;
- String mBody;
-
- QueryThread(long eventId, String body) {
- mEventId = eventId;
- mBody = body;
- }
-
- @Override
- public void run() {
- }
- }
-}
diff --git a/src/com/android/calendar/alerts/QuickResponseActivity.kt b/src/com/android/calendar/alerts/QuickResponseActivity.kt
new file mode 100644
index 00000000..afccaffd
--- /dev/null
+++ b/src/com/android/calendar/alerts/QuickResponseActivity.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ListActivity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ArrayAdapter
+import com.android.calendar.R
+import com.android.calendar.Utils
+import java.util.Arrays
+
+/**
+ * Activity which displays when the user wants to email guests from notifications.
+ *
+ * This presents the user with list if quick responses to be populated in an email
+ * to minimize typing.
+ *
+ */
+class QuickResponseActivity : ListActivity(), OnItemClickListener {
+ private var mResponses: Array<String?>? = null
+ @Override
+ protected override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ val intent: Intent? = getIntent()
+ if (intent == null) {
+ finish()
+ return
+ }
+ mEventId = intent?.getLongExtra(EXTRA_EVENT_ID, -1) as Long
+ if (mEventId == -1L) {
+ finish()
+ return
+ }
+
+ // Set listener
+ getListView().setOnItemClickListener(this@QuickResponseActivity)
+
+ // Populate responses
+ val responses: Array<String> = Utils.getQuickResponses(this)
+ Arrays.sort(responses)
+
+ // Add "Custom response..."
+ mResponses = arrayOfNulls(responses.size + 1)
+ var i: Int
+ i = 0
+ while (i < responses.size) {
+ mResponses!![i] = responses[i]
+ i++
+ }
+ mResponses!![i] = getResources().getString(R.string.quick_response_custom_msg)
+ setListAdapter(ArrayAdapter<String>(this, R.layout.quick_response_item,
+ mResponses as Array<String?>))
+ }
+
+ // implements OnItemClickListener
+ @Override
+ override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ var body: String? = null
+ if (mResponses != null && position < mResponses!!.size - 1) {
+ body = mResponses!![position]
+ }
+
+ // Start thread to query provider and send mail
+ QueryThread(mEventId, body).start()
+ }
+
+ private inner class QueryThread internal constructor(var mEventId: Long, var mBody: String?) :
+ Thread() {
+ @Override
+ override fun run() {
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickResponseActivity"
+ const val EXTRA_EVENT_ID = "eventId"
+ var mEventId: Long = 0
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthByWeekAdapter.java b/src/com/android/calendar/month/MonthByWeekAdapter.java
deleted file mode 100644
index 45a1bea1..00000000
--- a/src/com/android/calendar/month/MonthByWeekAdapter.java
+++ /dev/null
@@ -1,406 +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.month;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.Message;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-
-import com.android.calendar.CalendarController;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class MonthByWeekAdapter extends SimpleWeeksAdapter {
- private static final String TAG = "MonthByWeekAdapter";
-
- public static final String WEEK_PARAMS_IS_MINI = "mini_month";
- protected static int DEFAULT_QUERY_DAYS = 7 * 8; // 8 weeks
- private static final long ANIMATE_TODAY_TIMEOUT = 1000;
-
- protected CalendarController mController;
- protected String mHomeTimeZone;
- protected Time mTempTime;
- protected Time mToday;
- protected int mFirstJulianDay;
- protected int mQueryDays;
- protected boolean mIsMiniMonth = true;
- protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
- private final boolean mShowAgendaWithMonth;
-
- protected ArrayList<ArrayList<Event>> mEventDayList = new ArrayList<ArrayList<Event>>();
- protected ArrayList<Event> mEvents = null;
-
- private boolean mAnimateToday = false;
- private long mAnimateTime = 0;
-
- private Handler mEventDialogHandler;
-
- MonthWeekEventsView mClickedView;
- MonthWeekEventsView mSingleTapUpView;
- MonthWeekEventsView mLongClickedView;
-
- float mClickedXLocation; // Used to find which day was clicked
- long mClickTime; // Used to calculate minimum click animation time
- // Used to insure minimal time for seeing the click animation before switching views
- private static final int mOnTapDelay = 100;
- // Minimal time for a down touch action before stating the click animation, this insures that
- // there is no click animation on flings
- private static int mOnDownDelay;
- private static int mTotalClickDelay;
- // 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) {
- super(context, params);
- if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
- mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0;
- }
- mShowAgendaWithMonth = Utils.getConfigBool(context, R.bool.show_agenda_with_month);
- ViewConfiguration vc = ViewConfiguration.get(context);
- mOnDownDelay = ViewConfiguration.getTapTimeout();
- mMovedPixelToCancel = vc.getScaledTouchSlop();
- mTotalClickDelay = mOnDownDelay + mOnTapDelay;
- }
-
- public void animateToday() {
- mAnimateToday = true;
- mAnimateTime = System.currentTimeMillis();
- }
-
- @Override
- protected void init() {
- super.init();
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- mController = CalendarController.getInstance(mContext);
- mHomeTimeZone = Utils.getTimeZone(mContext, null);
- mSelectedDay.switchTimezone(mHomeTimeZone);
- mToday = new Time(mHomeTimeZone);
- mToday.setToNow();
- mTempTime = new Time(mHomeTimeZone);
- }
-
- private void updateTimeZones() {
- mSelectedDay.timezone = mHomeTimeZone;
- mSelectedDay.normalize(true);
- mToday.timezone = mHomeTimeZone;
- mToday.setToNow();
- mTempTime.switchTimezone(mHomeTimeZone);
- }
-
- @Override
- public void setSelectedDay(Time selectedTime) {
- mSelectedDay.set(selectedTime);
- long millis = mSelectedDay.normalize(true);
- mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
- Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
- notifyDataSetChanged();
- }
-
- public void setEvents(int firstJulianDay, int numDays, ArrayList<Event> events) {
- if (mIsMiniMonth) {
- if (Log.isLoggable(TAG, Log.ERROR)) {
- Log.e(TAG, "Attempted to set events for mini view. Events only supported in full"
- + " view.");
- }
- return;
- }
- mEvents = events;
- mFirstJulianDay = firstJulianDay;
- mQueryDays = numDays;
- // Create a new list, this is necessary since the weeks are referencing
- // pieces of the old list
- ArrayList<ArrayList<Event>> eventDayList = new ArrayList<ArrayList<Event>>();
- for (int i = 0; i < numDays; i++) {
- eventDayList.add(new ArrayList<Event>());
- }
-
- if (events == null || events.size() == 0) {
- if(Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "No events. Returning early--go schedule something fun.");
- }
- mEventDayList = eventDayList;
- refresh();
- return;
- }
-
- // Compute the new set of days with events
- for (Event event : events) {
- int startDay = event.startDay - mFirstJulianDay;
- int endDay = event.endDay - mFirstJulianDay + 1;
- if (startDay < numDays || endDay >= 0) {
- if (startDay < 0) {
- startDay = 0;
- }
- if (startDay > numDays) {
- continue;
- }
- if (endDay < 0) {
- continue;
- }
- if (endDay > numDays) {
- endDay = numDays;
- }
- for (int j = startDay; j < endDay; j++) {
- eventDayList.get(j).add(event);
- }
- }
- }
- if(Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Processed " + events.size() + " events.");
- }
- mEventDayList = eventDayList;
- refresh();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (mIsMiniMonth) {
- return super.getView(position, convertView, parent);
- }
- MonthWeekEventsView v;
- LayoutParams params = new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- HashMap<String, Integer> drawingParams = null;
- boolean isAnimatingToday = false;
- if (convertView != null) {
- v = (MonthWeekEventsView) convertView;
- // Checking updateToday uses the current params instead of the new
- // params, so this is assuming the view is relatively stable
- if (mAnimateToday && v.updateToday(mSelectedDay.timezone)) {
- long currentTime = System.currentTimeMillis();
- // If it's been too long since we tried to start the animation
- // don't show it. This can happen if the user stops a scroll
- // before reaching today.
- if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
- mAnimateToday = false;
- mAnimateTime = 0;
- } else {
- isAnimatingToday = true;
- // There is a bug that causes invalidates to not work some
- // of the time unless we recreate the view.
- v = new MonthWeekEventsView(mContext);
- }
- } else {
- drawingParams = (HashMap<String, Integer>) v.getTag();
- }
- } else {
- v = new MonthWeekEventsView(mContext);
- }
- if (drawingParams == null) {
- drawingParams = new HashMap<String, Integer>();
- }
- drawingParams.clear();
-
- v.setLayoutParams(params);
- v.setClickable(true);
- v.setOnTouchListener(this);
-
- int selectedDay = -1;
- if (mSelectedWeek == position) {
- selectedDay = mSelectedDay.weekDay;
- }
-
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
- (parent.getHeight() + parent.getTop()) / mNumWeeks);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
- drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation);
-
- if (isAnimatingToday) {
- drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1);
- mAnimateToday = false;
- }
-
- v.setWeekParams(drawingParams, mSelectedDay.timezone);
- return v;
- }
-
- @Override
- protected void refresh() {
- mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
- mShowWeekNumber = Utils.getShowWeekNumber(mContext);
- mHomeTimeZone = Utils.getTimeZone(mContext, null);
- mOrientation = mContext.getResources().getConfiguration().orientation;
- updateTimeZones();
- notifyDataSetChanged();
- }
-
- @Override
- protected void onDayTapped(Time day) {
- setDayParameters(day);
- if (mShowAgendaWithMonth || mIsMiniMonth) {
- // If agenda view is visible with month view , refresh the views
- // with the selected day's info
- mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
- ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
- } else {
- // Else , switch to the detailed view
- mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
- ViewType.DETAIL,
- CalendarController.EXTRA_GOTO_DATE
- | CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null);
- }
- }
-
- private void setDayParameters(Time day) {
- day.timezone = mHomeTimeZone;
- Time currTime = new Time(mHomeTimeZone);
- currTime.set(mController.getTime());
- day.hour = currTime.hour;
- day.minute = currTime.minute;
- day.allDay = false;
- day.normalize(true);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (!(v instanceof MonthWeekEventsView)) {
- return super.onTouch(v, event);
- }
-
- int action = event.getAction();
-
- // Event was tapped - switch to the detailed view making sure the click animation
- // is done first.
- if (mGestureDetector.onTouchEvent(event)) {
- mSingleTapUpView = (MonthWeekEventsView) v;
- long delay = System.currentTimeMillis() - mClickTime;
- // Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
- mListView.postDelayed(mDoSingleTapUp,
- delay > mTotalClickDelay ? 0 : mTotalClickDelay - delay);
- return true;
- } else {
- // Animate a click - on down: show the selected day in the "clicked" color.
- // On Up/scroll/move/cancel: hide the "clicked" color.
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mClickedView = (MonthWeekEventsView)v;
- mClickedXLocation = event.getX();
- mClickTime = System.currentTimeMillis();
- mListView.postDelayed(mDoClick, mOnDownDelay);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_SCROLL:
- case MotionEvent.ACTION_CANCEL:
- clearClickedView((MonthWeekEventsView)v);
- break;
- case MotionEvent.ACTION_MOVE:
- // No need to cancel on vertical movement, ACTION_SCROLL will do that.
- if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
- clearClickedView((MonthWeekEventsView)v);
- }
- break;
- default:
- break;
- }
- }
- // Do not tell the frameworks we consumed the touch action so that fling actions can be
- // processed by the fragment.
- return false;
- }
-
- /**
- * This is here so we can identify events and process them
- */
- protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
-
- @Override
- public void onLongPress(MotionEvent e) {
- if (mLongClickedView != null) {
- Time day = mLongClickedView.getDayFromLocation(mClickedXLocation);
- if (day != null) {
- mLongClickedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- Message message = new Message();
- message.obj = day;
- }
- mLongClickedView.clearClickedDay();
- mLongClickedView = null;
- }
- }
- }
-
- // Clear the visual cues of the click animation and related running code.
- private void clearClickedView(MonthWeekEventsView v) {
- mListView.removeCallbacks(mDoClick);
- synchronized(v) {
- v.clearClickedDay();
- }
- mClickedView = null;
- }
-
- // Perform the tap animation in a runnable to allow a delay before showing the tap color.
- // This is done to prevent a click animation when a fling is done.
- private final Runnable mDoClick = new Runnable() {
- @Override
- public void run() {
- if (mClickedView != null) {
- synchronized(mClickedView) {
- mClickedView.setClickedDay(mClickedXLocation);
- }
- mLongClickedView = mClickedView;
- mClickedView = null;
- // This is a workaround , sometimes the top item on the listview doesn't refresh on
- // invalidate, so this forces a re-draw.
- mListView.invalidate();
- }
- }
- };
-
- // Performs the single tap operation: go to the tapped day.
- // This is done in a runnable to allow the click animation to finish before switching views
- private final Runnable mDoSingleTapUp = new Runnable() {
- @Override
- public void run() {
- if (mSingleTapUpView != null) {
- Time day = mSingleTapUpView.getDayFromLocation(mClickedXLocation);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Touched day at Row=" + mSingleTapUpView.mWeek + " day=" + day.toString());
- }
- if (day != null) {
- onDayTapped(day);
- }
- clearClickedView(mSingleTapUpView);
- mSingleTapUpView = null;
- }
- }
- };
-}
diff --git a/src/com/android/calendar/month/MonthByWeekAdapter.kt b/src/com/android/calendar/month/MonthByWeekAdapter.kt
new file mode 100644
index 00000000..c67b3562
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekAdapter.kt
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Handler
+import android.os.Message
+import android.text.format.Time
+import android.util.Log
+import android.view.GestureDetector
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.widget.AbsListView.LayoutParams
+import com.android.calendar.CalendarController
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+import java.util.ArrayList
+import java.util.HashMap
+
+class MonthByWeekAdapter(context: Context?, params: HashMap<String?, Int?>) :
+ SimpleWeeksAdapter(context as Context, params) {
+ protected var mController: CalendarController? = null
+ protected var mHomeTimeZone: String? = null
+ protected var mTempTime: Time? = null
+ protected var mToday: Time? = null
+ protected var mFirstJulianDay = 0
+ protected var mQueryDays = 0
+ protected var mIsMiniMonth = true
+ protected var mOrientation: Int = Configuration.ORIENTATION_LANDSCAPE
+ private val mShowAgendaWithMonth: Boolean
+ protected var mEventDayList: ArrayList<ArrayList<Event>> = ArrayList<ArrayList<Event>>()
+ protected var mEvents: ArrayList<Event>? = null
+ private var mAnimateToday = false
+ private var mAnimateTime: Long = 0
+ private val mEventDialogHandler: Handler? = null
+ var mClickedView: MonthWeekEventsView? = null
+ var mSingleTapUpView: MonthWeekEventsView? = null
+ var mLongClickedView: MonthWeekEventsView? = null
+ var mClickedXLocation = 0f // Used to find which day was clicked
+ var mClickTime: Long = 0 // Used to calculate minimum click animation time
+
+ fun animateToday() {
+ mAnimateToday = true
+ mAnimateTime = System.currentTimeMillis()
+ }
+
+ @Override
+ protected override fun init() {
+ super.init()
+ mGestureDetector = GestureDetector(mContext, CalendarGestureListener())
+ mController = CalendarController.getInstance(mContext)
+ mHomeTimeZone = Utils.getTimeZone(mContext, null)
+ mSelectedDay?.switchTimezone(mHomeTimeZone)
+ mToday = Time(mHomeTimeZone)
+ mToday?.setToNow()
+ mTempTime = Time(mHomeTimeZone)
+ }
+
+ private fun updateTimeZones() {
+ mSelectedDay!!.timezone = mHomeTimeZone
+ mSelectedDay?.normalize(true)
+ mToday!!.timezone = mHomeTimeZone
+ mToday?.setToNow()
+ mTempTime?.switchTimezone(mHomeTimeZone)
+ }
+
+ @Override
+ override fun setSelectedDay(selectedTime: Time?) {
+ mSelectedDay?.set(selectedTime)
+ val millis: Long = mSelectedDay!!.normalize(true)
+ mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
+ Time.getJulianDay(millis, mSelectedDay!!.gmtoff), mFirstDayOfWeek
+ )
+ notifyDataSetChanged()
+ }
+
+ fun setEvents(firstJulianDay: Int, numDays: Int, events: ArrayList<Event>?) {
+ if (mIsMiniMonth) {
+ if (Log.isLoggable(TAG, Log.ERROR)) {
+ Log.e(
+ TAG, "Attempted to set events for mini view. Events only supported in full" +
+ " view."
+ )
+ }
+ return
+ }
+ mEvents = events
+ mFirstJulianDay = firstJulianDay
+ mQueryDays = numDays
+ // Create a new list, this is necessary since the weeks are referencing
+ // pieces of the old list
+ val eventDayList: ArrayList<ArrayList<Event>> = ArrayList<ArrayList<Event>>()
+ for (i in 0 until numDays) {
+ eventDayList.add(ArrayList<Event>())
+ }
+ if (events == null || events.size == 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "No events. Returning early--go schedule something fun.")
+ }
+ mEventDayList = eventDayList
+ refresh()
+ return
+ }
+
+ // Compute the new set of days with events
+ for (event in events) {
+ var startDay: Int = event.startDay - mFirstJulianDay
+ var endDay: Int = event.endDay - mFirstJulianDay + 1
+ if (startDay < numDays || endDay >= 0) {
+ if (startDay < 0) {
+ startDay = 0
+ }
+ if (startDay > numDays) {
+ continue
+ }
+ if (endDay < 0) {
+ continue
+ }
+ if (endDay > numDays) {
+ endDay = numDays
+ }
+ for (j in startDay until endDay) {
+ eventDayList.get(j).add(event)
+ }
+ }
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Processed " + events.size.toString() + " events.")
+ }
+ mEventDayList = eventDayList
+ refresh()
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ if (mIsMiniMonth) {
+ return super.getView(position, convertView, parent)
+ }
+ var v: MonthWeekEventsView
+ val params = LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
+ )
+ var drawingParams: HashMap<String?, Int?>? = null
+ var isAnimatingToday = false
+ if (convertView != null) {
+ v = convertView as MonthWeekEventsView
+ // Checking updateToday uses the current params instead of the new
+ // params, so this is assuming the view is relatively stable
+ if (mAnimateToday && v.updateToday(mSelectedDay!!.timezone)) {
+ val currentTime: Long = System.currentTimeMillis()
+ // If it's been too long since we tried to start the animation
+ // don't show it. This can happen if the user stops a scroll
+ // before reaching today.
+ if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
+ mAnimateToday = false
+ mAnimateTime = 0
+ } else {
+ isAnimatingToday = true
+ // There is a bug that causes invalidates to not work some
+ // of the time unless we recreate the view.
+ v = MonthWeekEventsView(mContext)
+ }
+ } else {
+ drawingParams = v.getTag() as HashMap<String?, Int?>
+ }
+ } else {
+ v = MonthWeekEventsView(mContext)
+ }
+ if (drawingParams == null) {
+ drawingParams = HashMap<String?, Int?>()
+ }
+ drawingParams.clear()
+ v.setLayoutParams(params)
+ v.setClickable(true)
+ v.setOnTouchListener(this)
+ var selectedDay = -1
+ if (mSelectedWeek === position) {
+ selectedDay = mSelectedDay!!.weekDay
+ }
+ drawingParams.put(
+ SimpleWeekView.VIEW_PARAMS_HEIGHT,
+ (parent.getHeight() + parent.getTop()) / mNumWeeks
+ )
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, if (mShowWeekNumber) 1 else 0)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth)
+ drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation)
+ if (isAnimatingToday) {
+ drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1)
+ mAnimateToday = false
+ }
+ v.setWeekParams(drawingParams, mSelectedDay!!.timezone)
+ return v
+ }
+
+ @Override
+ internal override fun refresh() {
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+ mHomeTimeZone = Utils.getTimeZone(mContext, null)
+ mOrientation = mContext.getResources().getConfiguration().orientation
+ updateTimeZones()
+ notifyDataSetChanged()
+ }
+
+ @Override
+ protected override fun onDayTapped(day: Time) {
+ setDayParameters(day)
+ if (mShowAgendaWithMonth || mIsMiniMonth) {
+ // If agenda view is visible with month view , refresh the views
+ // with the selected day's info
+ mController?.sendEvent(
+ mContext as Object?, EventType.GO_TO, day, day, -1,
+ ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null
+ )
+ } else {
+ // Else , switch to the detailed view
+ mController?.sendEvent(
+ mContext as Object?, EventType.GO_TO, day, day, -1,
+ ViewType.DETAIL, CalendarController.EXTRA_GOTO_DATE
+ or CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null
+ )
+ }
+ }
+
+ private fun setDayParameters(day: Time) {
+ day.timezone = mHomeTimeZone
+ val currTime = Time(mHomeTimeZone)
+ currTime.set(mController!!.time as Long)
+ day.hour = currTime.hour
+ day.minute = currTime.minute
+ day.allDay = false
+ day.normalize(true)
+ }
+
+ @Override
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ if (v !is MonthWeekEventsView) {
+ return super.onTouch(v, event)
+ }
+ val action: Int = event!!.getAction()
+
+ // Event was tapped - switch to the detailed view making sure the click animation
+ // is done first.
+ if (mGestureDetector!!.onTouchEvent(event)) {
+ mSingleTapUpView = v as MonthWeekEventsView?
+ val delay: Long = System.currentTimeMillis() - mClickTime
+ // Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
+ mListView?.postDelayed(
+ mDoSingleTapUp,
+ if (delay > mTotalClickDelay) 0 else mTotalClickDelay - delay
+ )
+ return true
+ } else {
+ // Animate a click - on down: show the selected day in the "clicked" color.
+ // On Up/scroll/move/cancel: hide the "clicked" color.
+ when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ mClickedView = v as MonthWeekEventsView
+ mClickedXLocation = event.getX()
+ mClickTime = System.currentTimeMillis()
+ mListView?.postDelayed(mDoClick, mOnDownDelay.toLong())
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_SCROLL, MotionEvent.ACTION_CANCEL ->
+ clearClickedView(
+ v as MonthWeekEventsView?
+ )
+ MotionEvent.ACTION_MOVE -> // No need to cancel on vertical movement,
+ // ACTION_SCROLL will do that.
+ if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
+ clearClickedView(v as MonthWeekEventsView?)
+ }
+ else -> {
+ }
+ }
+ }
+ // Do not tell the frameworks we consumed the touch action so that fling actions can be
+ // processed by the fragment.
+ return false
+ }
+
+ /**
+ * This is here so we can identify events and process them
+ */
+ protected inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+ @Override
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ return true
+ }
+
+ @Override
+ override fun onLongPress(e: MotionEvent) {
+ if (mLongClickedView != null) {
+ val day: Time? = mLongClickedView?.getDayFromLocation(mClickedXLocation)
+ if (day != null) {
+ mLongClickedView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+ val message = Message()
+ message.obj = day
+ }
+ mLongClickedView?.clearClickedDay()
+ mLongClickedView = null
+ }
+ }
+ }
+
+ // Clear the visual cues of the click animation and related running code.
+ private fun clearClickedView(v: MonthWeekEventsView?) {
+ mListView?.removeCallbacks(mDoClick)
+ synchronized(v as Any) { v?.clearClickedDay() }
+ mClickedView = null
+ }
+
+ // Perform the tap animation in a runnable to allow a delay before showing the tap color.
+ // This is done to prevent a click animation when a fling is done.
+ private val mDoClick: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (mClickedView != null) {
+ synchronized(mClickedView as MonthWeekEventsView) {
+ mClickedView?.setClickedDay(mClickedXLocation) }
+ mLongClickedView = mClickedView
+ mClickedView = null
+ // This is a workaround , sometimes the top item on the listview doesn't refresh on
+ // invalidate, so this forces a re-draw.
+ mListView?.invalidate()
+ }
+ }
+ }
+
+ // Performs the single tap operation: go to the tapped day.
+ // This is done in a runnable to allow the click animation to finish before switching views
+ private val mDoSingleTapUp: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (mSingleTapUpView != null) {
+ val day: Time? = mSingleTapUpView?.getDayFromLocation(mClickedXLocation)
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(
+ TAG,
+ "Touched day at Row=" + mSingleTapUpView?.mWeek?.toString() +
+ " day=" + day?.toString()
+ )
+ }
+ if (day != null) {
+ onDayTapped(day)
+ }
+ clearClickedView(mSingleTapUpView)
+ mSingleTapUpView = null
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "MonthByWeekAdapter"
+ const val WEEK_PARAMS_IS_MINI = "mini_month"
+ protected var DEFAULT_QUERY_DAYS = 7 * 8 // 8 weeks
+ private const val ANIMATE_TODAY_TIMEOUT: Long = 1000
+
+ // Used to insure minimal time for seeing the click animation before switching views
+ private const val mOnTapDelay = 100
+
+ // Minimal time for a down touch action before stating the click animation, this ensures
+ // that there is no click animation on flings
+ private var mOnDownDelay: Int = 0
+ private var mTotalClickDelay: Int = 0
+
+ // Minimal distance to move the finger in order to cancel the click animation
+ private var mMovedPixelToCancel: Float = 0f
+ }
+
+ init {
+ if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
+ mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0
+ }
+ mShowAgendaWithMonth = Utils.getConfigBool(context as Context,
+ R.bool.show_agenda_with_month)
+ val vc: ViewConfiguration = ViewConfiguration.get(context)
+ mOnDownDelay = ViewConfiguration.getTapTimeout()
+ mMovedPixelToCancel = vc.getScaledTouchSlop().toFloat()
+ mTotalClickDelay = mOnDownDelay + mOnTapDelay
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthByWeekFragment.java b/src/com/android/calendar/month/MonthByWeekFragment.java
deleted file mode 100644
index f8a518d3..00000000
--- a/src/com/android/calendar/month/MonthByWeekFragment.java
+++ /dev/null
@@ -1,494 +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.month;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.LoaderManager;
-import android.content.ContentUris;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.drawable.StateListDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-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.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-
-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.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-
-public class MonthByWeekFragment extends SimpleDayPickerFragment implements
- CalendarController.EventHandler, LoaderManager.LoaderCallbacks<Cursor>, OnScrollListener,
- OnTouchListener {
- private static final String TAG = "MonthFragment";
- private static final String TAG_EVENT_DIALOG = "event_dialog";
-
- // 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 + ","
- + Instances.START_MINUTE + "," + Instances.TITLE;
- protected static boolean mShowDetailsInMonth = false;
-
- protected float mMinimumTwoMonthFlingVelocity;
- protected boolean mIsMiniMonth;
- protected boolean mHideDeclined;
-
- protected int mFirstLoadedJulianDay;
- protected int mLastLoadedJulianDay;
-
- private static final int WEEKS_BUFFER = 1;
- // How long to wait after scroll stops before starting the loader
- // Using scroll duration because scroll state changes don't update
- // correctly when a scroll is triggered programmatically.
- private static final int LOADER_DELAY = 200;
- // The minimum time between requeries of the data if the db is
- // changing
- private static final int LOADER_THROTTLE_DELAY = 500;
-
- private CursorLoader mLoader;
- private Uri mEventUri;
- private final Time mDesiredDay = new Time();
-
- private volatile boolean mShouldLoad = true;
- private boolean mUserScrolled = false;
-
- private int mEventsLoadingDelay;
- private boolean mShowCalendarControls;
- private boolean mIsDetached;
-
- private final Runnable mTZUpdater = new Runnable() {
- @Override
- public void run() {
- String tz = Utils.getTimeZone(mContext, mTZUpdater);
- mSelectedDay.timezone = tz;
- mSelectedDay.normalize(true);
- mTempTime.timezone = tz;
- mFirstDayOfMonth.timezone = tz;
- mFirstDayOfMonth.normalize(true);
- mFirstVisibleDay.timezone = tz;
- mFirstVisibleDay.normalize(true);
- if (mAdapter != null) {
- mAdapter.refresh();
- }
- }
- };
-
-
- private final Runnable mUpdateLoader = new Runnable() {
- @Override
- public void run() {
- synchronized (this) {
- if (!mShouldLoad || mLoader == null) {
- return;
- }
- // Stop any previous loads while we update the uri
- stopLoader();
-
- // Start the loader again
- mEventUri = updateUri();
-
- mLoader.setUri(mEventUri);
- mLoader.startLoading();
- mLoader.onContentChanged();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Started loader with uri: " + mEventUri);
- }
- }
- }
- };
- // Used to load the events when a delay is needed
- Runnable mLoadingRunnable = new Runnable() {
- @Override
- public void run() {
- if (!mIsDetached) {
- mLoader = (CursorLoader) getLoaderManager().initLoader(0, null,
- MonthByWeekFragment.this);
- }
- }
- };
-
-
- /**
- * Updates the uri used by the loader according to the current position of
- * the listview.
- *
- * @return The new Uri to use
- */
- private Uri updateUri() {
- SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
- if (child != null) {
- int julianDay = child.getFirstJulianDay();
- mFirstLoadedJulianDay = julianDay;
- }
- // -1 to ensure we get all day events from any time zone
- mTempTime.setJulianDay(mFirstLoadedJulianDay - 1);
- long start = mTempTime.toMillis(true);
- mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7;
- // +1 to ensure we get all day events from any time zone
- mTempTime.setJulianDay(mLastLoadedJulianDay + 1);
- long end = mTempTime.toMillis(true);
-
- // Create a new uri with the updated times
- Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
- ContentUris.appendId(builder, start);
- ContentUris.appendId(builder, end);
- return builder.build();
- }
-
- // Extract range of julian days from URI
- private void updateLoadedDays() {
- List<String> pathSegments = mEventUri.getPathSegments();
- int size = pathSegments.size();
- if (size <= 2) {
- return;
- }
- long first = Long.parseLong(pathSegments.get(size - 2));
- long last = Long.parseLong(pathSegments.get(size - 1));
- mTempTime.set(first);
- mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff);
- mTempTime.set(last);
- mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff);
- }
-
- protected String updateWhere() {
- // TODO fix selection/selection args after b/3206641 is fixed
- String where = WHERE_CALENDARS_VISIBLE;
- if (mHideDeclined || !mShowDetailsInMonth) {
- where += " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
- + Attendees.ATTENDEE_STATUS_DECLINED;
- }
- return where;
- }
-
- private void stopLoader() {
- synchronized (mUpdateLoader) {
- mHandler.removeCallbacks(mUpdateLoader);
- if (mLoader != null) {
- mLoader.stopLoading();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Stopped loader from loading");
- }
- }
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mTZUpdater.run();
- if (mAdapter != null) {
- mAdapter.setSelectedDay(mSelectedDay);
- }
- mIsDetached = false;
-
- ViewConfiguration viewConfig = ViewConfiguration.get(activity);
- mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity() / 2;
- Resources res = activity.getResources();
- mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls);
- // Synchronized the loading time of the month's events with the animation of the
- // calendar controls.
- if (mShowCalendarControls) {
- mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time);
- }
- mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month);
- }
-
- @Override
- public void onDetach() {
- mIsDetached = true;
- super.onDetach();
- if (mShowCalendarControls) {
- if (mListView != null) {
- mListView.removeCallbacks(mLoadingRunnable);
- }
- }
- }
-
- @Override
- protected void setUpAdapter() {
- mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
- mShowWeekNumber = Utils.getShowWeekNumber(mContext);
-
- HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
- weekParams.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, mIsMiniMonth ? 1 : 0);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
- Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff));
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek);
- if (mAdapter == null) {
- mAdapter = new MonthByWeekAdapter(getActivity(), weekParams);
- mAdapter.registerDataSetObserver(mObserver);
- } else {
- mAdapter.updateParams(weekParams);
- }
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v;
- if (mIsMiniMonth) {
- v = inflater.inflate(R.layout.month_by_week, container, false);
- } else {
- v = inflater.inflate(R.layout.full_month_by_week, container, false);
- }
- mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
- return v;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mListView.setSelector(new StateListDrawable());
- mListView.setOnTouchListener(this);
-
- if (!mIsMiniMonth) {
- mListView.setBackgroundColor(getResources().getColor(R.color.month_bgcolor));
- }
-
- // To get a smoother transition when showing this fragment, delay loading of events until
- // the fragment is expended fully and the calendar controls are gone.
- if (mShowCalendarControls) {
- mListView.postDelayed(mLoadingRunnable, mEventsLoadingDelay);
- } else {
- mLoader = (CursorLoader) getLoaderManager().initLoader(0, null, this);
- }
- mAdapter.setListView(mListView);
- }
-
- public MonthByWeekFragment() {
- this(System.currentTimeMillis(), true);
- }
-
- public MonthByWeekFragment(long initialTime, boolean isMiniMonth) {
- super(initialTime);
- mIsMiniMonth = isMiniMonth;
- }
-
- @Override
- protected void setUpHeader() {
- if (mIsMiniMonth) {
- super.setUpHeader();
- return;
- }
-
- mDayLabels = new String[7];
- for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
- mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
- DateUtils.LENGTH_MEDIUM).toUpperCase();
- }
- }
-
- // TODO
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (mIsMiniMonth) {
- return null;
- }
- CursorLoader loader;
- synchronized (mUpdateLoader) {
- mFirstLoadedJulianDay =
- Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
- - (mNumWeeks * 7 / 2);
- mEventUri = updateUri();
- String where = updateWhere();
-
- loader = new CursorLoader(
- getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
- null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER);
- loader.setUpdateThrottle(LOADER_THROTTLE_DELAY);
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Returning new loader with uri: " + mEventUri);
- }
- return loader;
- }
-
- @Override
- public void doResumeUpdates() {
- mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
- mShowWeekNumber = Utils.getShowWeekNumber(mContext);
- boolean prevHideDeclined = mHideDeclined;
- mHideDeclined = Utils.getHideDeclinedEvents(mContext);
- if (prevHideDeclined != mHideDeclined && mLoader != null) {
- mLoader.setSelection(updateWhere());
- }
- mDaysPerWeek = Utils.getDaysPerWeek(mContext);
- updateHeader();
- mAdapter.setSelectedDay(mSelectedDay);
- mTZUpdater.run();
- mTodayUpdater.run();
- goTo(mSelectedDay.toMillis(true), false, true, false);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- synchronized (mUpdateLoader) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri);
- }
- CursorLoader cLoader = (CursorLoader) loader;
- if (mEventUri == null) {
- mEventUri = cLoader.getUri();
- updateLoadedDays();
- }
- if (cLoader.getUri().compareTo(mEventUri) != 0) {
- // We've started a new query since this loader ran so ignore the
- // result
- return;
- }
- ArrayList<Event> events = new ArrayList<Event>();
- Event.buildEventsFromCursor(
- events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay);
- ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay,
- mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- }
-
- @Override
- public void eventsChanged() {
- // TODO remove this after b/3387924 is resolved
- if (mLoader != null) {
- mLoader.forceLoad();
- }
- }
-
- @Override
- public long getSupportedEventTypes() {
- return EventType.GO_TO | EventType.EVENTS_CHANGED;
- }
-
- @Override
- public void handleEvent(EventInfo event) {
- if (event.eventType == EventType.GO_TO) {
- boolean animate = true;
- if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
- Time.getJulianDay(event.selectedTime.toMillis(true), event.selectedTime.gmtoff)
- - Time.getJulianDay(mFirstVisibleDay.toMillis(true), mFirstVisibleDay.gmtoff)
- - mDaysPerWeek * mNumWeeks / 2)) {
- animate = false;
- }
- mDesiredDay.set(event.selectedTime);
- mDesiredDay.normalize(true);
- boolean animateToday = (event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0;
- boolean delayAnimation = goTo(event.selectedTime.toMillis(true), animate, true, false);
- if (animateToday) {
- // If we need to flash today start the animation after any
- // movement from listView has ended.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- ((MonthByWeekAdapter) mAdapter).animateToday();
- mAdapter.notifyDataSetChanged();
- }
- }, delayAnimation ? GOTO_SCROLL_DURATION : 0);
- }
- } else if (event.eventType == EventType.EVENTS_CHANGED) {
- eventsChanged();
- }
- }
-
- @Override
- protected void setMonthDisplayed(Time time, boolean updateHighlight) {
- super.setMonthDisplayed(time, updateHighlight);
- if (!mIsMiniMonth) {
- boolean useSelected = false;
- if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
- mSelectedDay.set(mDesiredDay);
- mAdapter.setSelectedDay(mDesiredDay);
- useSelected = true;
- } else {
- mSelectedDay.set(time);
- mAdapter.setSelectedDay(time);
- }
- CalendarController controller = CalendarController.getInstance(mContext);
- if (mSelectedDay.minute >= 30) {
- mSelectedDay.minute = 30;
- } else {
- mSelectedDay.minute = 0;
- }
- long newTime = mSelectedDay.normalize(true);
- if (newTime != controller.getTime() && mUserScrolled) {
- long offset = useSelected ? 0 : DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3;
- controller.setTime(newTime + offset);
- }
- controller.sendEvent(this, EventType.UPDATE_TITLE, time, time, time, -1,
- ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR, null, null);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
-
- synchronized (mUpdateLoader) {
- if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- mShouldLoad = false;
- stopLoader();
- mDesiredDay.setToNow();
- } else {
- mHandler.removeCallbacks(mUpdateLoader);
- mShouldLoad = true;
- mHandler.postDelayed(mUpdateLoader, LOADER_DELAY);
- }
- }
- if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
- mUserScrolled = true;
- }
-
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- mDesiredDay.setToNow();
- return false;
- }
-}
diff --git a/src/com/android/calendar/month/MonthByWeekFragment.kt b/src/com/android/calendar/month/MonthByWeekFragment.kt
new file mode 100644
index 00000000..9fe9fe49
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekFragment.kt
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.app.Activity
+import android.app.LoaderManager
+import android.content.ContentUris
+import android.content.CursorLoader
+import android.content.Loader
+import android.content.res.Resources
+import android.database.Cursor
+import android.graphics.drawable.StateListDrawable
+import android.net.Uri
+import android.os.Bundle
+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.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+
+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.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+
+import java.util.ArrayList
+import java.util.Calendar
+import java.util.HashMap
+
+class MonthByWeekFragment @JvmOverloads constructor(
+ initialTime: Long = System.currentTimeMillis(),
+ protected var mIsMiniMonth: Boolean = true
+) : SimpleDayPickerFragment(initialTime), CalendarController.EventHandler,
+ LoaderManager.LoaderCallbacks<Cursor?>, OnScrollListener, OnTouchListener {
+ protected var mMinimumTwoMonthFlingVelocity = 0f
+ protected var mHideDeclined = false
+ protected var mFirstLoadedJulianDay = 0
+ protected var mLastLoadedJulianDay = 0
+ private var mLoader: CursorLoader? = null
+ private var mEventUri: Uri? = null
+ private val mDesiredDay: Time = Time()
+
+ @Volatile
+ private var mShouldLoad = true
+ private var mUserScrolled = false
+ private var mEventsLoadingDelay = 0
+ private var mShowCalendarControls = false
+ private var mIsDetached = false
+ private val mTZUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ val tz: String? = Utils.getTimeZone(mContext, this)
+ mSelectedDay.timezone = tz
+ mSelectedDay.normalize(true)
+ mTempTime.timezone = tz
+ mFirstDayOfMonth.timezone = tz
+ mFirstDayOfMonth.normalize(true)
+ mFirstVisibleDay.timezone = tz
+ mFirstVisibleDay.normalize(true)
+ if (mAdapter != null) {
+ mAdapter?.refresh()
+ }
+ }
+ }
+ private val mUpdateLoader: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ synchronized(this) {
+ if (!mShouldLoad || mLoader == null) {
+ return
+ }
+ // Stop any previous loads while we update the uri
+ stopLoader()
+
+ // Start the loader again
+ mEventUri = updateUri()
+ mLoader?.setUri(mEventUri)
+ mLoader?.startLoading()
+ mLoader?.onContentChanged()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Started loader with uri: $mEventUri")
+ }
+ }
+ }
+ }
+
+ // Used to load the events when a delay is needed
+ var mLoadingRunnable: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (!mIsDetached) {
+ mLoader = getLoaderManager().initLoader(
+ 0, null,
+ this@MonthByWeekFragment
+ ) as? CursorLoader
+ }
+ }
+ }
+
+ /**
+ * Updates the uri used by the loader according to the current position of
+ * the listview.
+ *
+ * @return The new Uri to use
+ */
+ private fun updateUri(): Uri {
+ val child: SimpleWeekView? = mListView?.getChildAt(0) as? SimpleWeekView
+ if (child != null) {
+ val julianDay: Int = child?.getFirstJulianDay()
+ mFirstLoadedJulianDay = julianDay
+ }
+ // -1 to ensure we get all day events from any time zone
+ mTempTime.setJulianDay(mFirstLoadedJulianDay - 1)
+ val start: Long = mTempTime.toMillis(true)
+ mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7
+ // +1 to ensure we get all day events from any time zone
+ mTempTime.setJulianDay(mLastLoadedJulianDay + 1)
+ val end: Long = mTempTime.toMillis(true)
+
+ // Create a new uri with the updated times
+ val builder: Uri.Builder = Instances.CONTENT_URI.buildUpon()
+ ContentUris.appendId(builder, start)
+ ContentUris.appendId(builder, end)
+ return builder.build()
+ }
+
+ // Extract range of julian days from URI
+ private fun updateLoadedDays() {
+ val pathSegments = mEventUri?.getPathSegments()
+ val size: Int = pathSegments?.size as Int
+ if (size <= 2) {
+ return
+ }
+ val first: Long = (pathSegments!![size - 2])?.toLong() as Long
+ val last: Long = (pathSegments!![size - 1])?.toLong() as Long
+ mTempTime.set(first)
+ mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff)
+ mTempTime.set(last)
+ mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff)
+ }
+
+ protected fun updateWhere(): String {
+ // TODO fix selection/selection args after b/3206641 is fixed
+ var where = WHERE_CALENDARS_VISIBLE
+ if (mHideDeclined || !mShowDetailsInMonth) {
+ where += (" AND " + Instances.SELF_ATTENDEE_STATUS.toString() + "!=" +
+ Attendees.ATTENDEE_STATUS_DECLINED)
+ }
+ return where
+ }
+
+ private fun stopLoader() {
+ synchronized(mUpdateLoader) {
+ mHandler.removeCallbacks(mUpdateLoader)
+ if (mLoader != null) {
+ mLoader?.stopLoading()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Stopped loader from loading")
+ }
+ }
+ }
+ }
+
+ @Override
+ override fun onAttach(activity: Activity) {
+ super.onAttach(activity)
+ mTZUpdater.run()
+ if (mAdapter != null) {
+ mAdapter?.setSelectedDay(mSelectedDay)
+ }
+ mIsDetached = false
+ val viewConfig: ViewConfiguration = ViewConfiguration.get(activity)
+ mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity().toFloat() / 2f
+ val res: Resources = activity.getResources()
+ mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls)
+ // Synchronized the loading time of the month's events with the animation of the
+ // calendar controls.
+ if (mShowCalendarControls) {
+ mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time)
+ }
+ mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month)
+ }
+
+ @Override
+ override fun onDetach() {
+ mIsDetached = true
+ super.onDetach()
+ if (mShowCalendarControls) {
+ if (mListView != null) {
+ mListView?.removeCallbacks(mLoadingRunnable)
+ }
+ }
+ }
+
+ @Override
+ protected override fun setUpAdapter() {
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+ val weekParams = HashMap<String?, Int?>()
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, if (mShowWeekNumber) 1 else 0)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek)
+ weekParams?.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, if (mIsMiniMonth) 1 else 0)
+ weekParams?.put(
+ SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
+ Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
+ )
+ weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek)
+ if (mAdapter == null) {
+ mAdapter = MonthByWeekAdapter(getActivity(), weekParams) as SimpleWeeksAdapter?
+ mAdapter?.registerDataSetObserver(mObserver)
+ } else {
+ mAdapter?.updateParams(weekParams)
+ }
+ mAdapter?.notifyDataSetChanged()
+ }
+
+ @Override
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val v: View
+ v = if (mIsMiniMonth) {
+ inflater.inflate(R.layout.month_by_week, container, false)
+ } else {
+ inflater.inflate(R.layout.full_month_by_week, container, false)
+ }
+ mDayNamesHeader = v.findViewById(R.id.day_names) as? ViewGroup
+ return v
+ }
+
+ @Override
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ mListView?.setSelector(StateListDrawable())
+ mListView?.setOnTouchListener(this)
+ if (!mIsMiniMonth) {
+ mListView?.setBackgroundColor(getResources().getColor(R.color.month_bgcolor))
+ }
+
+ // To get a smoother transition when showing this fragment, delay loading of events until
+ // the fragment is expended fully and the calendar controls are gone.
+ if (mShowCalendarControls) {
+ mListView?.postDelayed(mLoadingRunnable, mEventsLoadingDelay.toLong())
+ } else {
+ mLoader = getLoaderManager().initLoader(0, null, this) as? CursorLoader
+ }
+ mAdapter?.setListView(mListView)
+ }
+
+ @Override
+ protected override fun setUpHeader() {
+ if (mIsMiniMonth) {
+ super.setUpHeader()
+ return
+ }
+ mDayLabels = arrayOfNulls<String>(7)
+ for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+ mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(
+ i,
+ DateUtils.LENGTH_MEDIUM
+ ).toUpperCase()
+ }
+ }
+
+ // TODO
+ @Override
+ override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?>? {
+ if (mIsMiniMonth) {
+ return null
+ }
+ var loader: CursorLoader?
+ synchronized(mUpdateLoader) {
+ mFirstLoadedJulianDay =
+ (Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff) -
+ mNumWeeks * 7 / 2)
+ mEventUri = updateUri()
+ val where = updateWhere()
+ loader = CursorLoader(
+ getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
+ null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER
+ )
+ loader?.setUpdateThrottle(LOADER_THROTTLE_DELAY.toLong())
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Returning new loader with uri: $mEventUri")
+ }
+ return loader
+ }
+
+ @Override
+ override fun doResumeUpdates() {
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+ val prevHideDeclined = mHideDeclined
+ mHideDeclined = Utils.getHideDeclinedEvents(mContext)
+ if (prevHideDeclined != mHideDeclined && mLoader != null) {
+ mLoader?.setSelection(updateWhere())
+ }
+ mDaysPerWeek = Utils.getDaysPerWeek(mContext)
+ updateHeader()
+ mAdapter?.setSelectedDay(mSelectedDay)
+ mTZUpdater.run()
+ mTodayUpdater.run()
+ goTo(mSelectedDay.toMillis(true), false, true, false)
+ }
+
+ @Override
+ override fun onLoadFinished(loader: Loader<Cursor?>?, data: Cursor?) {
+ synchronized(mUpdateLoader) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(
+ TAG,
+ "Found " + data?.getCount()?.toString() + " cursor entries for uri " +
+ mEventUri
+ )
+ }
+ val cLoader: CursorLoader = loader as CursorLoader
+ if (mEventUri == null) {
+ mEventUri = cLoader.getUri()
+ updateLoadedDays()
+ }
+ if (cLoader.getUri().compareTo(mEventUri) !== 0) {
+ // We've started a new query since this loader ran so ignore the
+ // result
+ return
+ }
+ val events: ArrayList<Event?>? = ArrayList<Event?>()
+ Event.buildEventsFromCursor(
+ events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay
+ )
+ (mAdapter as MonthByWeekAdapter).setEvents(
+ mFirstLoadedJulianDay,
+ mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events as ArrayList<Event>?
+ )
+ }
+ }
+
+ @Override
+ override fun onLoaderReset(loader: Loader<Cursor?>?) {
+ }
+
+ @Override
+ override fun eventsChanged() {
+ // TODO remove this after b/3387924 is resolved
+ if (mLoader != null) {
+ mLoader?.forceLoad()
+ }
+ }
+
+ @get:Override override val supportedEventTypes: Long
+ get() = EventType.GO_TO or EventType.EVENTS_CHANGED
+
+ @Override
+ override fun handleEvent(event: CalendarController.EventInfo?) {
+ if (event?.eventType === EventType.GO_TO) {
+ var animate = true
+ if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
+ Time.getJulianDay(event?.selectedTime?.toMillis(true) as Long,
+ event?.selectedTime?.gmtoff as Long) -
+ Time.getJulianDay(mFirstVisibleDay?.toMillis(true) as Long,
+ mFirstVisibleDay?.gmtoff as Long) -
+ mDaysPerWeek * mNumWeeks / 2L
+ )
+ ) {
+ animate = false
+ }
+ mDesiredDay.set(event?.selectedTime)
+ mDesiredDay.normalize(true)
+ val animateToday = event?.extraLong and
+ CalendarController.EXTRA_GOTO_TODAY.toLong() != 0L
+ val delayAnimation: Boolean =
+ goTo(event?.selectedTime?.toMillis(true)?.toLong() as Long,
+ animate, true, false)
+ if (animateToday) {
+ // If we need to flash today start the animation after any
+ // movement from listView has ended.
+ mHandler.postDelayed(object : Runnable {
+ @Override
+ override fun run() {
+ (mAdapter as? MonthByWeekAdapter)?.animateToday()
+ mAdapter?.notifyDataSetChanged()
+ }
+ }, if (delayAnimation) GOTO_SCROLL_DURATION.toLong() else 0L)
+ }
+ } else if (event?.eventType == EventType.EVENTS_CHANGED) {
+ eventsChanged()
+ }
+ }
+
+ @Override
+ protected override fun setMonthDisplayed(time: Time, updateHighlight: Boolean) {
+ super.setMonthDisplayed(time, updateHighlight)
+ if (!mIsMiniMonth) {
+ var useSelected = false
+ if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
+ mSelectedDay.set(mDesiredDay)
+ mAdapter?.setSelectedDay(mDesiredDay)
+ useSelected = true
+ } else {
+ mSelectedDay.set(time)
+ mAdapter?.setSelectedDay(time)
+ }
+ val controller: CalendarController? = CalendarController.getInstance(mContext)
+ if (mSelectedDay.minute >= 30) {
+ mSelectedDay.minute = 30
+ } else {
+ mSelectedDay.minute = 0
+ }
+ val newTime: Long = mSelectedDay.normalize(true)
+ if (newTime != controller?.time && mUserScrolled) {
+ val offset: Long =
+ if (useSelected) 0 else DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3.toLong()
+ controller?.time = (newTime + offset)
+ }
+ controller?.sendEvent(
+ this as Object?, EventType.UPDATE_TITLE, time, time, time, -1,
+ ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE.toLong() or
+ DateUtils.FORMAT_NO_MONTH_DAY.toLong() or
+ DateUtils.FORMAT_SHOW_YEAR.toLong(), null, null
+ )
+ }
+ }
+
+ @Override
+ override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+ synchronized(mUpdateLoader) {
+ if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mShouldLoad = false
+ stopLoader()
+ mDesiredDay.setToNow()
+ } else {
+ mHandler.removeCallbacks(mUpdateLoader)
+ mShouldLoad = true
+ mHandler.postDelayed(mUpdateLoader, LOADER_DELAY.toLong())
+ }
+ }
+ if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mUserScrolled = true
+ }
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState)
+ }
+
+ @Override
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ mDesiredDay.setToNow()
+ return false
+ }
+
+ companion object {
+ private const val TAG = "MonthFragment"
+ private const val TAG_EVENT_DIALOG = "event_dialog"
+
+ // Selection and selection args for adding event queries
+ private val WHERE_CALENDARS_VISIBLE: String = Calendars.VISIBLE.toString() + "=1"
+ private val INSTANCES_SORT_ORDER: String = (Instances.START_DAY.toString() + "," +
+ Instances.START_MINUTE + "," + Instances.TITLE)
+ protected var mShowDetailsInMonth = false
+ private const val WEEKS_BUFFER = 1
+
+ // How long to wait after scroll stops before starting the loader
+ // Using scroll duration because scroll state changes don't update
+ // correctly when a scroll is triggered programmatically.
+ private const val LOADER_DELAY = 200
+
+ // The minimum time between requeries of the data if the db is
+ // changing
+ private const val LOADER_THROTTLE_DELAY = 500
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthListView.java b/src/com/android/calendar/month/MonthListView.java
deleted file mode 100644
index f2621ccb..00000000
--- a/src/com/android/calendar/month/MonthListView.java
+++ /dev/null
@@ -1,51 +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.month;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.ListView;
-
-import com.android.calendar.Utils;
-
-public class MonthListView extends ListView {
-
- private static final String TAG = "MonthListView";
-
- public MonthListView(Context context) {
- super(context);
- }
-
- public MonthListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public MonthListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return super.onTouchEvent(ev);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return super.onInterceptTouchEvent(ev);
- }
-}
diff --git a/src/com/android/calendar/month/MonthListView.kt b/src/com/android/calendar/month/MonthListView.kt
new file mode 100644
index 00000000..1facb4c0
--- /dev/null
+++ b/src/com/android/calendar/month/MonthListView.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.ListView
+import com.android.calendar.Utils
+
+class MonthListView : ListView {
+ constructor(context: Context?) : super(context) {}
+ constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) :
+ super(context, attrs, defStyle) {}
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
+
+ @Override
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ return super.onTouchEvent(ev)
+ }
+
+ @Override
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ return super.onInterceptTouchEvent(ev)
+ }
+
+ companion object {
+ private const val TAG = "MonthListView"
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthWeekEventsView.java b/src/com/android/calendar/month/MonthWeekEventsView.java
deleted file mode 100644
index e1c78c67..00000000
--- a/src/com/android/calendar/month/MonthWeekEventsView.java
+++ /dev/null
@@ -1,1110 +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.month;
-
-import com.android.calendar.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.app.Service;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.provider.CalendarContract.Attendees;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-
-public class MonthWeekEventsView extends SimpleWeekView {
-
- private static final String TAG = "MonthView";
-
- private static final boolean DEBUG_LAYOUT = false;
-
- public static final String VIEW_PARAMS_ORIENTATION = "orientation";
- public static final String VIEW_PARAMS_ANIMATE_TODAY = "animate_today";
-
- /* NOTE: these are not constants, and may be multiplied by a scale factor */
- private static int TEXT_SIZE_MONTH_NUMBER = 32;
- private static int TEXT_SIZE_EVENT = 12;
- private static int TEXT_SIZE_EVENT_TITLE = 14;
- private static int TEXT_SIZE_MORE_EVENTS = 12;
- private static int TEXT_SIZE_MONTH_NAME = 14;
- private static int TEXT_SIZE_WEEK_NUM = 12;
-
- private static int DNA_MARGIN = 4;
- private static int DNA_ALL_DAY_HEIGHT = 4;
- private static int DNA_MIN_SEGMENT_HEIGHT = 4;
- private static int DNA_WIDTH = 8;
- private static int DNA_ALL_DAY_WIDTH = 32;
- private static int DNA_SIDE_PADDING = 6;
- private static int CONFLICT_COLOR = Color.BLACK;
- private static int EVENT_TEXT_COLOR = Color.WHITE;
-
- private static int DEFAULT_EDGE_SPACING = 0;
- private static int SIDE_PADDING_MONTH_NUMBER = 4;
- private static int TOP_PADDING_MONTH_NUMBER = 4;
- private static int TOP_PADDING_WEEK_NUMBER = 4;
- private static int SIDE_PADDING_WEEK_NUMBER = 20;
- private static int DAY_SEPARATOR_OUTER_WIDTH = 0;
- private static int DAY_SEPARATOR_INNER_WIDTH = 1;
- private static int DAY_SEPARATOR_VERTICAL_LENGTH = 53;
- private static int DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT = 64;
- private static int MIN_WEEK_WIDTH = 50;
-
- private static int EVENT_X_OFFSET_LANDSCAPE = 38;
- private static int EVENT_Y_OFFSET_LANDSCAPE = 8;
- private static int EVENT_Y_OFFSET_PORTRAIT = 7;
- private static int EVENT_SQUARE_WIDTH = 10;
- private static int EVENT_SQUARE_BORDER = 2;
- private static int EVENT_LINE_PADDING = 2;
- private static int EVENT_RIGHT_PADDING = 4;
- private static int EVENT_BOTTOM_PADDING = 3;
-
- private static int TODAY_HIGHLIGHT_WIDTH = 2;
-
- private static int SPACING_WEEK_NUMBER = 24;
- private static boolean mInitialized = false;
- private static boolean mShowDetailsInMonth;
-
- protected Time mToday = new Time();
- protected boolean mHasToday = false;
- protected int mTodayIndex = -1;
- protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
- protected List<ArrayList<Event>> mEvents = null;
- protected ArrayList<Event> mUnsortedEvents = null;
- HashMap<Integer, Utils.DNAStrand> mDna = null;
- // This is for drawing the outlines around event chips and supports up to 10
- // events being drawn on each day. The code will expand this if necessary.
- protected FloatRef mEventOutlines = new FloatRef(10 * 4 * 4 * 7);
-
-
-
- protected static StringBuilder mStringBuilder = new StringBuilder(50);
- // TODO recreate formatter when locale changes
- protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
- protected Paint mMonthNamePaint;
- protected TextPaint mEventPaint;
- protected TextPaint mSolidBackgroundEventPaint;
- protected TextPaint mFramedEventPaint;
- protected TextPaint mDeclinedEventPaint;
- protected TextPaint mEventExtrasPaint;
- protected TextPaint mEventDeclinedExtrasPaint;
- protected Paint mWeekNumPaint;
- protected Paint mDNAAllDayPaint;
- protected Paint mDNATimePaint;
- protected Paint mEventSquarePaint;
-
-
- protected Drawable mTodayDrawable;
-
- protected int mMonthNumHeight;
- protected int mMonthNumAscentHeight;
- protected int mEventHeight;
- protected int mEventAscentHeight;
- protected int mExtrasHeight;
- protected int mExtrasAscentHeight;
- protected int mExtrasDescent;
- protected int mWeekNumAscentHeight;
-
- protected int mMonthBGColor;
- protected int mMonthBGOtherColor;
- protected int mMonthBGTodayColor;
- protected int mMonthNumColor;
- protected int mMonthNumOtherColor;
- protected int mMonthNumTodayColor;
- protected int mMonthNameColor;
- protected int mMonthNameOtherColor;
- protected int mMonthEventColor;
- protected int mMonthDeclinedEventColor;
- protected int mMonthDeclinedExtrasColor;
- protected int mMonthEventExtraColor;
- protected int mMonthEventOtherColor;
- protected int mMonthEventExtraOtherColor;
- protected int mMonthWeekNumColor;
- protected int mMonthBusyBitsBgColor;
- protected int mMonthBusyBitsBusyTimeColor;
- protected int mMonthBusyBitsConflictTimeColor;
- private int mClickedDayIndex = -1;
- private int mClickedDayColor;
- private static final int mClickedAlpha = 128;
-
- protected int mEventChipOutlineColor = 0xFFFFFFFF;
- protected int mDaySeparatorInnerColor;
- protected int mTodayAnimateColor;
-
- private boolean mAnimateToday;
- private int mAnimateTodayAlpha = 0;
- private ObjectAnimator mTodayAnimator = null;
-
- private final TodayAnimatorListener mAnimatorListener = new TodayAnimatorListener();
-
- class TodayAnimatorListener extends AnimatorListenerAdapter {
- private volatile Animator mAnimator = null;
- private volatile boolean mFadingIn = false;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- synchronized (this) {
- if (mAnimator != animation) {
- animation.removeAllListeners();
- animation.cancel();
- return;
- }
- if (mFadingIn) {
- if (mTodayAnimator != null) {
- mTodayAnimator.removeAllListeners();
- mTodayAnimator.cancel();
- }
- mTodayAnimator = ObjectAnimator.ofInt(MonthWeekEventsView.this,
- "animateTodayAlpha", 255, 0);
- mAnimator = mTodayAnimator;
- mFadingIn = false;
- mTodayAnimator.addListener(this);
- mTodayAnimator.setDuration(600);
- mTodayAnimator.start();
- } else {
- mAnimateToday = false;
- mAnimateTodayAlpha = 0;
- mAnimator.removeAllListeners();
- mAnimator = null;
- mTodayAnimator = null;
- invalidate();
- }
- }
- }
-
- public void setAnimator(Animator animation) {
- mAnimator = animation;
- }
-
- public void setFadingIn(boolean fadingIn) {
- mFadingIn = fadingIn;
- }
-
- }
-
- private int[] mDayXs;
-
- /**
- * This provides a reference to a float array which allows for easy size
- * checking and reallocation. Used for drawing lines.
- */
- private class FloatRef {
- float[] array;
-
- public FloatRef(int size) {
- array = new float[size];
- }
-
- public void ensureSize(int newSize) {
- if (newSize >= array.length) {
- // Add enough space for 7 more boxes to be drawn
- array = Arrays.copyOf(array, newSize + 16 * 7);
- }
- }
- }
-
- /**
- * Shows up as an error if we don't include this.
- */
- public MonthWeekEventsView(Context context) {
- super(context);
- }
-
- // Sets the list of events for this week. Takes a sorted list of arrays
- // divided up by day for generating the large month version and the full
- // arraylist sorted by start time to generate the dna version.
- public void setEvents(List<ArrayList<Event>> sortedEvents, ArrayList<Event> unsortedEvents) {
- setEvents(sortedEvents);
- // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to
- // generate dna bits before its width has been fixed.
- createDna(unsortedEvents);
- }
-
- /**
- * Sets up the dna bits for the view. This will return early if the view
- * isn't in a state that will create a valid set of dna yet (such as the
- * views width not being set correctly yet).
- */
- public void createDna(ArrayList<Event> unsortedEvents) {
- if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) {
- // Stash the list of events for use when this view is ready, or
- // just clear it if a null set has been passed to this view
- mUnsortedEvents = unsortedEvents;
- mDna = null;
- return;
- } else {
- // clear the cached set of events since we're ready to build it now
- mUnsortedEvents = null;
- }
- // Create the drawing coordinates for dna
- if (!mShowDetailsInMonth) {
- int numDays = mEvents.size();
- int effectiveWidth = mWidth - mPadding * 2;
- if (mShowWeekNum) {
- effectiveWidth -= SPACING_WEEK_NUMBER;
- }
- DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING;
- mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH);
- mDayXs = new int[numDays];
- for (int day = 0; day < numDays; day++) {
- mDayXs[day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING;
-
- }
-
- int top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1;
- int bottom = mHeight - DNA_MARGIN;
- mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom,
- DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext());
- }
- }
-
- public void setEvents(List<ArrayList<Event>> sortedEvents) {
- mEvents = sortedEvents;
- if (sortedEvents == null) {
- return;
- }
- if (sortedEvents.size() != mNumDays) {
- if (Log.isLoggable(TAG, Log.ERROR)) {
- Log.wtf(TAG, "Events size must be same as days displayed: size="
- + sortedEvents.size() + " days=" + mNumDays);
- }
- mEvents = null;
- return;
- }
- }
-
- protected void loadColors(Context context) {
- Resources res = context.getResources();
- mMonthWeekNumColor = res.getColor(R.color.month_week_num_color);
- mMonthNumColor = res.getColor(R.color.month_day_number);
- mMonthNumOtherColor = res.getColor(R.color.month_day_number_other);
- mMonthNumTodayColor = res.getColor(R.color.month_today_number);
- mMonthNameColor = mMonthNumColor;
- mMonthNameOtherColor = mMonthNumOtherColor;
- mMonthEventColor = res.getColor(R.color.month_event_color);
- mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color);
- mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color);
- mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color);
- mMonthEventOtherColor = res.getColor(R.color.month_event_other_color);
- mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color);
- mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor);
- mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor);
- mMonthBGColor = res.getColor(R.color.month_bgcolor);
- mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines);
- mTodayAnimateColor = res.getColor(R.color.today_highlight_color);
- mClickedDayColor = res.getColor(R.color.day_clicked_background_color);
- mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light);
- }
-
- /**
- * Sets up the text and style properties for painting. Override this if you
- * want to use a different paint.
- */
- @Override
- protected void initView() {
- super.initView();
-
- if (!mInitialized) {
- Resources resources = getContext().getResources();
- mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month);
- TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title);
- TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number);
- SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin);
- CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color);
- EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color);
- if (mScale != 1) {
- TOP_PADDING_MONTH_NUMBER *= mScale;
- TOP_PADDING_WEEK_NUMBER *= mScale;
- SIDE_PADDING_MONTH_NUMBER *= mScale;
- SIDE_PADDING_WEEK_NUMBER *= mScale;
- SPACING_WEEK_NUMBER *= mScale;
- TEXT_SIZE_MONTH_NUMBER *= mScale;
- TEXT_SIZE_EVENT *= mScale;
- TEXT_SIZE_EVENT_TITLE *= mScale;
- TEXT_SIZE_MORE_EVENTS *= mScale;
- TEXT_SIZE_MONTH_NAME *= mScale;
- TEXT_SIZE_WEEK_NUM *= mScale;
- DAY_SEPARATOR_OUTER_WIDTH *= mScale;
- DAY_SEPARATOR_INNER_WIDTH *= mScale;
- DAY_SEPARATOR_VERTICAL_LENGTH *= mScale;
- DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT *= mScale;
- EVENT_X_OFFSET_LANDSCAPE *= mScale;
- EVENT_Y_OFFSET_LANDSCAPE *= mScale;
- EVENT_Y_OFFSET_PORTRAIT *= mScale;
- EVENT_SQUARE_WIDTH *= mScale;
- EVENT_SQUARE_BORDER *= mScale;
- EVENT_LINE_PADDING *= mScale;
- EVENT_BOTTOM_PADDING *= mScale;
- EVENT_RIGHT_PADDING *= mScale;
- DNA_MARGIN *= mScale;
- DNA_WIDTH *= mScale;
- DNA_ALL_DAY_HEIGHT *= mScale;
- DNA_MIN_SEGMENT_HEIGHT *= mScale;
- DNA_SIDE_PADDING *= mScale;
- DEFAULT_EDGE_SPACING *= mScale;
- DNA_ALL_DAY_WIDTH *= mScale;
- TODAY_HIGHLIGHT_WIDTH *= mScale;
- }
- if (!mShowDetailsInMonth) {
- TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN;
- }
- mInitialized = true;
- }
- mPadding = DEFAULT_EDGE_SPACING;
- loadColors(getContext());
- // TODO modify paint properties depending on isMini
-
- mMonthNumPaint = new Paint();
- mMonthNumPaint.setFakeBoldText(false);
- mMonthNumPaint.setAntiAlias(true);
- mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER);
- mMonthNumPaint.setColor(mMonthNumColor);
- mMonthNumPaint.setStyle(Style.FILL);
- mMonthNumPaint.setTextAlign(Align.RIGHT);
- mMonthNumPaint.setTypeface(Typeface.DEFAULT);
-
- mMonthNumAscentHeight = (int) (-mMonthNumPaint.ascent() + 0.5f);
- mMonthNumHeight = (int) (mMonthNumPaint.descent() - mMonthNumPaint.ascent() + 0.5f);
-
- mEventPaint = new TextPaint();
- mEventPaint.setFakeBoldText(true);
- mEventPaint.setAntiAlias(true);
- mEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE);
- mEventPaint.setColor(mMonthEventColor);
-
- mSolidBackgroundEventPaint = new TextPaint(mEventPaint);
- mSolidBackgroundEventPaint.setColor(EVENT_TEXT_COLOR);
- mFramedEventPaint = new TextPaint(mSolidBackgroundEventPaint);
-
- mDeclinedEventPaint = new TextPaint();
- mDeclinedEventPaint.setFakeBoldText(true);
- mDeclinedEventPaint.setAntiAlias(true);
- mDeclinedEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE);
- mDeclinedEventPaint.setColor(mMonthDeclinedEventColor);
-
- mEventAscentHeight = (int) (-mEventPaint.ascent() + 0.5f);
- mEventHeight = (int) (mEventPaint.descent() - mEventPaint.ascent() + 0.5f);
-
- mEventExtrasPaint = new TextPaint();
- mEventExtrasPaint.setFakeBoldText(false);
- mEventExtrasPaint.setAntiAlias(true);
- mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER);
- mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT);
- mEventExtrasPaint.setColor(mMonthEventExtraColor);
- mEventExtrasPaint.setStyle(Style.FILL);
- mEventExtrasPaint.setTextAlign(Align.LEFT);
- mExtrasHeight = (int)(mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f);
- mExtrasAscentHeight = (int)(-mEventExtrasPaint.ascent() + 0.5f);
- mExtrasDescent = (int)(mEventExtrasPaint.descent() + 0.5f);
-
- mEventDeclinedExtrasPaint = new TextPaint();
- mEventDeclinedExtrasPaint.setFakeBoldText(false);
- mEventDeclinedExtrasPaint.setAntiAlias(true);
- mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER);
- mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT);
- mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor);
- mEventDeclinedExtrasPaint.setStyle(Style.FILL);
- mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT);
-
- mWeekNumPaint = new Paint();
- mWeekNumPaint.setFakeBoldText(false);
- mWeekNumPaint.setAntiAlias(true);
- mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM);
- mWeekNumPaint.setColor(mWeekNumColor);
- mWeekNumPaint.setStyle(Style.FILL);
- mWeekNumPaint.setTextAlign(Align.RIGHT);
-
- mWeekNumAscentHeight = (int) (-mWeekNumPaint.ascent() + 0.5f);
-
- mDNAAllDayPaint = new Paint();
- mDNATimePaint = new Paint();
- mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor);
- mDNATimePaint.setStyle(Style.FILL_AND_STROKE);
- mDNATimePaint.setStrokeWidth(DNA_WIDTH);
- mDNATimePaint.setAntiAlias(false);
- mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor);
- mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE);
- mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH);
- mDNAAllDayPaint.setAntiAlias(false);
-
- mEventSquarePaint = new Paint();
- mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER);
- mEventSquarePaint.setAntiAlias(false);
-
- if (DEBUG_LAYOUT) {
- Log.d("EXTRA", "mScale=" + mScale);
- Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint.ascent()
- + " descent=" + mMonthNumPaint.descent() + " int height=" + mMonthNumHeight);
- Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint.ascent()
- + " descent=" + mEventPaint.descent() + " int height=" + mEventHeight
- + " int ascent=" + mEventAscentHeight);
- Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent()
- + " descent=" + mEventExtrasPaint.descent() + " int height=" + mExtrasHeight);
- Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent()
- + " descent=" + mWeekNumPaint.descent());
- }
- }
-
- @Override
- public void setWeekParams(HashMap<String, Integer> params, String tz) {
- super.setWeekParams(params, tz);
-
- if (params.containsKey(VIEW_PARAMS_ORIENTATION)) {
- mOrientation = params.get(VIEW_PARAMS_ORIENTATION);
- }
-
- updateToday(tz);
- mNumCells = mNumDays + 1;
-
- if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) {
- synchronized (mAnimatorListener) {
- if (mTodayAnimator != null) {
- mTodayAnimator.removeAllListeners();
- mTodayAnimator.cancel();
- }
- mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
- Math.max(mAnimateTodayAlpha, 80), 255);
- mTodayAnimator.setDuration(150);
- mAnimatorListener.setAnimator(mTodayAnimator);
- mAnimatorListener.setFadingIn(true);
- mTodayAnimator.addListener(mAnimatorListener);
- mAnimateToday = true;
- mTodayAnimator.start();
- }
- }
- }
-
- /**
- * @param tz
- */
- public boolean updateToday(String tz) {
- mToday.timezone = tz;
- mToday.setToNow();
- mToday.normalize(true);
- int julianToday = Time.getJulianDay(mToday.toMillis(false), mToday.gmtoff);
- if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) {
- mHasToday = true;
- mTodayIndex = julianToday - mFirstJulianDay;
- } else {
- mHasToday = false;
- mTodayIndex = -1;
- }
- return mHasToday;
- }
-
- public void setAnimateTodayAlpha(int alpha) {
- mAnimateTodayAlpha = alpha;
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNums(canvas);
- drawDaySeparators(canvas);
- if (mHasToday && mAnimateToday) {
- drawToday(canvas);
- }
- if (mShowDetailsInMonth) {
- drawEvents(canvas);
- } else {
- if (mDna == null && mUnsortedEvents != null) {
- createDna(mUnsortedEvents);
- }
- drawDNA(canvas);
- }
- drawClick(canvas);
- }
-
- protected void drawToday(Canvas canvas) {
- r.top = DAY_SEPARATOR_INNER_WIDTH + (TODAY_HIGHLIGHT_WIDTH / 2);
- r.bottom = mHeight - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f);
- p.setStyle(Style.STROKE);
- p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH);
- r.left = computeDayLeftPosition(mTodayIndex) + (TODAY_HIGHLIGHT_WIDTH / 2);
- r.right = computeDayLeftPosition(mTodayIndex + 1)
- - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f);
- p.setColor(mTodayAnimateColor | (mAnimateTodayAlpha << 24));
- canvas.drawRect(r, p);
- p.setStyle(Style.FILL);
- }
-
- // TODO move into SimpleWeekView
- // Computes the x position for the left side of the given day
- private int computeDayLeftPosition(int day) {
- int effectiveWidth = mWidth;
- int x = 0;
- int xOffset = 0;
- if (mShowWeekNum) {
- xOffset = SPACING_WEEK_NUMBER + mPadding;
- effectiveWidth -= xOffset;
- }
- x = day * effectiveWidth / mNumDays + xOffset;
- return x;
- }
-
- @Override
- protected void drawDaySeparators(Canvas canvas) {
- float lines[] = new float[8 * 4];
- int count = 6 * 4;
- int wkNumOffset = 0;
- int i = 0;
- if (mShowWeekNum) {
- // This adds the first line separating the week number
- int xOffset = SPACING_WEEK_NUMBER + mPadding;
- count += 4;
- lines[i++] = xOffset;
- lines[i++] = 0;
- lines[i++] = xOffset;
- lines[i++] = mHeight;
- wkNumOffset++;
- }
- count += 4;
- lines[i++] = 0;
- lines[i++] = 0;
- lines[i++] = mWidth;
- lines[i++] = 0;
- int y0 = 0;
- int y1 = mHeight;
-
- while (i < count) {
- int x = computeDayLeftPosition(i / 4 - wkNumOffset);
- lines[i++] = x;
- lines[i++] = y0;
- lines[i++] = x;
- lines[i++] = y1;
- }
- p.setColor(mDaySeparatorInnerColor);
- p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH);
- canvas.drawLines(lines, 0, count, p);
- }
-
- @Override
- protected void drawBackground(Canvas canvas) {
- int i = 0;
- int offset = 0;
- r.top = DAY_SEPARATOR_INNER_WIDTH;
- r.bottom = mHeight;
- if (mShowWeekNum) {
- i++;
- offset++;
- }
- if (!mOddMonth[i]) {
- while (++i < mOddMonth.length && !mOddMonth[i])
- ;
- r.right = computeDayLeftPosition(i - offset);
- r.left = 0;
- p.setColor(mMonthBGOtherColor);
- canvas.drawRect(r, p);
- // compute left edge for i, set up r, draw
- } else if (!mOddMonth[(i = mOddMonth.length - 1)]) {
- while (--i >= offset && !mOddMonth[i])
- ;
- i++;
- // compute left edge for i, set up r, draw
- r.right = mWidth;
- r.left = computeDayLeftPosition(i - offset);
- p.setColor(mMonthBGOtherColor);
- canvas.drawRect(r, p);
- }
- if (mHasToday) {
- p.setColor(mMonthBGTodayColor);
- r.left = computeDayLeftPosition(mTodayIndex);
- r.right = computeDayLeftPosition(mTodayIndex + 1);
- canvas.drawRect(r, p);
- }
- }
-
- // Draw the "clicked" color on the tapped day
- private void drawClick(Canvas canvas) {
- if (mClickedDayIndex != -1) {
- int alpha = p.getAlpha();
- p.setColor(mClickedDayColor);
- p.setAlpha(mClickedAlpha);
- r.left = computeDayLeftPosition(mClickedDayIndex);
- r.right = computeDayLeftPosition(mClickedDayIndex + 1);
- r.top = DAY_SEPARATOR_INNER_WIDTH;
- r.bottom = mHeight;
- canvas.drawRect(r, p);
- p.setAlpha(alpha);
- }
- }
-
- @Override
- protected void drawWeekNums(Canvas canvas) {
- int y;
-
- int i = 0;
- int offset = -1;
- int todayIndex = mTodayIndex;
- int x = 0;
- int numCount = mNumDays;
- if (mShowWeekNum) {
- x = SIDE_PADDING_WEEK_NUMBER + mPadding;
- y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER;
- canvas.drawText(mDayNumbers[0], x, y, mWeekNumPaint);
- numCount++;
- i++;
- todayIndex++;
- offset++;
-
- }
-
- y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER;
-
- boolean isFocusMonth = mFocusDay[i];
- boolean isBold = false;
- mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
- for (; i < numCount; i++) {
- if (mHasToday && todayIndex == i) {
- mMonthNumPaint.setColor(mMonthNumTodayColor);
- mMonthNumPaint.setFakeBoldText(isBold = true);
- if (i + 1 < numCount) {
- // Make sure the color will be set back on the next
- // iteration
- isFocusMonth = !mFocusDay[i + 1];
- }
- } else if (mFocusDay[i] != isFocusMonth) {
- isFocusMonth = mFocusDay[i];
- mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
- }
- x = computeDayLeftPosition(i - offset) - (SIDE_PADDING_MONTH_NUMBER);
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
- if (isBold) {
- mMonthNumPaint.setFakeBoldText(isBold = false);
- }
- }
- }
-
- protected void drawEvents(Canvas canvas) {
- if (mEvents == null) {
- return;
- }
-
- int day = -1;
- for (ArrayList<Event> eventDay : mEvents) {
- day++;
- if (eventDay == null || eventDay.size() == 0) {
- continue;
- }
- int ySquare;
- int xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1;
- int rightEdge = computeDayLeftPosition(day + 1);
-
- if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
- ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER;
- rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1;
- } else {
- ySquare = EVENT_Y_OFFSET_LANDSCAPE;
- rightEdge -= EVENT_X_OFFSET_LANDSCAPE;
- }
-
- // Determine if everything will fit when time ranges are shown.
- boolean showTimes = true;
- Iterator<Event> iter = eventDay.iterator();
- int yTest = ySquare;
- while (iter.hasNext()) {
- Event event = iter.next();
- int newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(),
- showTimes, /*doDraw*/ false);
- if (newY == yTest) {
- showTimes = false;
- break;
- }
- yTest = newY;
- }
-
- int eventCount = 0;
- iter = eventDay.iterator();
- while (iter.hasNext()) {
- Event event = iter.next();
- int newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(),
- showTimes, /*doDraw*/ true);
- if (newY == ySquare) {
- break;
- }
- eventCount++;
- ySquare = newY;
- }
-
- int remaining = eventDay.size() - eventCount;
- if (remaining > 0) {
- drawMoreEvents(canvas, remaining, xSquare);
- }
- }
- }
-
- protected int addChipOutline(FloatRef lines, int count, int x, int y) {
- lines.ensureSize(count + 16);
- // top of box
- lines.array[count++] = x;
- lines.array[count++] = y;
- lines.array[count++] = x + EVENT_SQUARE_WIDTH;
- lines.array[count++] = y;
- // right side of box
- lines.array[count++] = x + EVENT_SQUARE_WIDTH;
- lines.array[count++] = y;
- lines.array[count++] = x + EVENT_SQUARE_WIDTH;
- lines.array[count++] = y + EVENT_SQUARE_WIDTH;
- // left side of box
- lines.array[count++] = x;
- lines.array[count++] = y;
- lines.array[count++] = x;
- lines.array[count++] = y + EVENT_SQUARE_WIDTH + 1;
- // bottom of box
- lines.array[count++] = x;
- lines.array[count++] = y + EVENT_SQUARE_WIDTH;
- lines.array[count++] = x + EVENT_SQUARE_WIDTH + 1;
- lines.array[count++] = y + EVENT_SQUARE_WIDTH;
-
- return count;
- }
-
- /**
- * Attempts to draw the given event. Returns the y for the next event or the
- * original y if the event will not fit. An event is considered to not fit
- * if the event and its extras won't fit or if there are more events and the
- * more events line would not fit after drawing this event.
- *
- * @param canvas the canvas to draw on
- * @param event the event to draw
- * @param x the top left corner for this event's color chip
- * @param y the top left corner for this event's color chip
- * @param rightEdge the rightmost point we're allowed to draw on (exclusive)
- * @param moreEvents indicates whether additional events will follow this one
- * @param showTimes if set, a second line with a time range will be displayed for non-all-day
- * events
- * @param doDraw if set, do the actual drawing; otherwise this just computes the height
- * and returns
- * @return the y for the next event or the original y if it won't fit
- */
- protected int drawEvent(Canvas canvas, Event event, int x, int y, int rightEdge,
- boolean moreEvents, boolean showTimes, boolean doDraw) {
- /*
- * Vertical layout:
- * (top of box)
- * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent
- * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event
- * c. [optional] Time range (mExtrasHeight)
- * d. EVENT_LINE_PADDING
- *
- * Repeat (b,c,d) as needed and space allows. If we have more events than fit, we need
- * to leave room for something like "+2" at the bottom:
- *
- * e. "+ more" line (mExtrasHeight)
- *
- * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING)
- * (bottom of box)
- */
- final int BORDER_SPACE = EVENT_SQUARE_BORDER + 1; // want a 1-pixel gap inside border
- final int STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2; // adjust bounds for stroke width
- boolean allDay = event.allDay;
- int eventRequiredSpace = mEventHeight;
- if (allDay) {
- // Add a few pixels for the box we draw around all-day events.
- eventRequiredSpace += BORDER_SPACE * 2;
- } else if (showTimes) {
- // Need room for the "1pm - 2pm" line.
- eventRequiredSpace += mExtrasHeight;
- }
- int reservedSpace = EVENT_BOTTOM_PADDING; // leave a bit of room at the bottom
- if (moreEvents) {
- // More events follow. Leave a bit of space between events.
- eventRequiredSpace += EVENT_LINE_PADDING;
-
- // Make sure we have room for the "+ more" line. (The "+ more" line is expected
- // to be <= the height of an event line, so we won't show "+1" when we could be
- // showing the event.)
- reservedSpace += mExtrasHeight;
- }
-
- if (y + eventRequiredSpace + reservedSpace > mHeight) {
- // Not enough space, return original y
- return y;
- } else if (!doDraw) {
- return y + eventRequiredSpace;
- }
-
- boolean isDeclined = event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED;
- int color = event.color;
- if (isDeclined) {
- color = Utils.getDeclinedColorFromColor(color);
- }
-
- int textX, textY, textRightEdge;
-
- if (allDay) {
- // We shift the render offset "inward", because drawRect with a stroke width greater
- // than 1 draws outside the specified bounds. (We don't adjust the left edge, since
- // we want to match the existing appearance of the "event square".)
- r.left = x;
- r.right = rightEdge - STROKE_WIDTH_ADJ;
- r.top = y + STROKE_WIDTH_ADJ;
- r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ;
- textX = x + BORDER_SPACE;
- textY = y + mEventAscentHeight + BORDER_SPACE;
- textRightEdge = rightEdge - BORDER_SPACE;
- } else {
- r.left = x;
- r.right = x + EVENT_SQUARE_WIDTH;
- r.bottom = y + mEventAscentHeight;
- r.top = r.bottom - EVENT_SQUARE_WIDTH;
- textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING;
- textY = y + mEventAscentHeight;
- textRightEdge = rightEdge;
- }
-
- Style boxStyle = Style.STROKE;
- boolean solidBackground = false;
- if (event.selfAttendeeStatus != Attendees.ATTENDEE_STATUS_INVITED) {
- boxStyle = Style.FILL_AND_STROKE;
- if (allDay) {
- solidBackground = true;
- }
- }
- mEventSquarePaint.setStyle(boxStyle);
- mEventSquarePaint.setColor(color);
- canvas.drawRect(r, mEventSquarePaint);
-
- float avail = textRightEdge - textX;
- CharSequence text = TextUtils.ellipsize(
- event.title, mEventPaint, avail, TextUtils.TruncateAt.END);
- Paint textPaint;
- if (solidBackground) {
- // Text color needs to contrast with solid background.
- textPaint = mSolidBackgroundEventPaint;
- } else if (isDeclined) {
- // Use "declined event" color.
- textPaint = mDeclinedEventPaint;
- } else if (allDay) {
- // Text inside frame is same color as frame.
- mFramedEventPaint.setColor(color);
- textPaint = mFramedEventPaint;
- } else {
- // Use generic event text color.
- textPaint = mEventPaint;
- }
- canvas.drawText(text.toString(), textX, textY, textPaint);
- y += mEventHeight;
- if (allDay) {
- y += BORDER_SPACE * 2;
- }
-
- if (showTimes && !allDay) {
- // show start/end time, e.g. "1pm - 2pm"
- textY = y + mExtrasAscentHeight;
- mStringBuilder.setLength(0);
- text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis,
- event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
- Utils.getTimeZone(getContext(), null)).toString();
- text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END);
- canvas.drawText(text.toString(), textX, textY, isDeclined ? mEventDeclinedExtrasPaint
- : mEventExtrasPaint);
- y += mExtrasHeight;
- }
-
- y += EVENT_LINE_PADDING;
-
- return y;
- }
-
- protected void drawMoreEvents(Canvas canvas, int remainingEvents, int x) {
- int y = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING);
- String text = getContext().getResources().getQuantityString(
- R.plurals.month_more_events, remainingEvents);
- mEventExtrasPaint.setAntiAlias(true);
- mEventExtrasPaint.setFakeBoldText(true);
- canvas.drawText(String.format(text, remainingEvents), x, y, mEventExtrasPaint);
- mEventExtrasPaint.setFakeBoldText(false);
- }
-
- /**
- * Draws a line showing busy times in each day of week The method draws
- * non-conflicting times in the event color and times with conflicting
- * events in the dna conflict color defined in colors.
- *
- * @param canvas
- */
- protected void drawDNA(Canvas canvas) {
- // Draw event and conflict times
- if (mDna != null) {
- for (Utils.DNAStrand strand : mDna.values()) {
- if (strand.color == CONFLICT_COLOR || strand.points == null
- || strand.points.length == 0) {
- continue;
- }
- mDNATimePaint.setColor(strand.color);
- canvas.drawLines(strand.points, mDNATimePaint);
- }
- // Draw black last to make sure it's on top
- Utils.DNAStrand strand = mDna.get(CONFLICT_COLOR);
- if (strand != null && strand.points != null && strand.points.length != 0) {
- mDNATimePaint.setColor(strand.color);
- canvas.drawLines(strand.points, mDNATimePaint);
- }
- if (mDayXs == null) {
- return;
- }
- int numDays = mDayXs.length;
- int xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2;
- if (strand != null && strand.allDays != null && strand.allDays.length == numDays) {
- for (int i = 0; i < numDays; i++) {
- // this adds at most 7 draws. We could sort it by color and
- // build an array instead but this is easier.
- if (strand.allDays[i] != 0) {
- mDNAAllDayPaint.setColor(strand.allDays[i]);
- canvas.drawLine(mDayXs[i] + xOffset, DNA_MARGIN, mDayXs[i] + xOffset,
- DNA_MARGIN + DNA_ALL_DAY_HEIGHT, mDNAAllDayPaint);
- }
- }
- }
- }
- }
-
- @Override
- protected void updateSelectionPositions() {
- if (mHasSelectedDay) {
- int selectedPosition = mSelectedDay - mWeekStart;
- if (selectedPosition < 0) {
- selectedPosition += 7;
- }
- int effectiveWidth = mWidth - mPadding * 2;
- effectiveWidth -= SPACING_WEEK_NUMBER;
- mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding;
- mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding;
- mSelectedLeft += SPACING_WEEK_NUMBER;
- mSelectedRight += SPACING_WEEK_NUMBER;
- }
- }
-
- public int getDayIndexFromLocation(float x) {
- int dayStart = mShowWeekNum ? SPACING_WEEK_NUMBER + mPadding : mPadding;
- if (x < dayStart || x > mWidth - mPadding) {
- return -1;
- }
- // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- return ((int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)));
- }
-
- @Override
- public Time getDayFromLocation(float x) {
- int dayPosition = getDayIndexFromLocation(x);
- if (dayPosition == -1) {
- return null;
- }
- int day = mFirstJulianDay + dayPosition;
-
- Time time = new Time(mTimeZone);
- if (mWeek == 0) {
- // This week is weird...
- if (day < Time.EPOCH_JULIAN_DAY) {
- day++;
- } else if (day == Time.EPOCH_JULIAN_DAY) {
- time.set(1, 0, 1970);
- time.normalize(true);
- return time;
- }
- }
-
- time.setJulianDay(day);
- return time;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- Context context = getContext();
- // only send accessibility events if accessibility and exploration are
- // on.
- AccessibilityManager am = (AccessibilityManager) context
- .getSystemService(Service.ACCESSIBILITY_SERVICE);
- if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
- return super.onHoverEvent(event);
- }
- if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
- Time hover = getDayFromLocation(event.getX());
- if (hover != null
- && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) {
- Long millis = hover.toMillis(true);
- String date = Utils.formatDateRange(context, millis, millis,
- DateUtils.FORMAT_SHOW_DATE);
- AccessibilityEvent accessEvent = AccessibilityEvent
- .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
- accessEvent.getText().add(date);
- if (mShowDetailsInMonth && mEvents != null) {
- int dayStart = SPACING_WEEK_NUMBER + mPadding;
- int dayPosition = (int) ((event.getX() - dayStart) * mNumDays / (mWidth
- - dayStart - mPadding));
- ArrayList<Event> events = mEvents.get(dayPosition);
- List<CharSequence> text = accessEvent.getText();
- for (Event e : events) {
- text.add(e.getTitleAndLocation() + ". ");
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
- if (!e.allDay) {
- flags |= DateUtils.FORMAT_SHOW_TIME;
- if (DateFormat.is24HourFormat(context)) {
- flags |= DateUtils.FORMAT_24HOUR;
- }
- } else {
- flags |= DateUtils.FORMAT_UTC;
- }
- text.add(Utils.formatDateRange(context, e.startMillis, e.endMillis,
- flags) + ". ");
- }
- }
- sendAccessibilityEventUnchecked(accessEvent);
- mLastHoverTime = hover;
- }
- }
- return true;
- }
-
- public void setClickedDay(float xLocation) {
- mClickedDayIndex = getDayIndexFromLocation(xLocation);
- invalidate();
- }
- public void clearClickedDay() {
- mClickedDayIndex = -1;
- invalidate();
- }
-}
diff --git a/src/com/android/calendar/month/MonthWeekEventsView.kt b/src/com/android/calendar/month/MonthWeekEventsView.kt
new file mode 100644
index 00000000..e4b15494
--- /dev/null
+++ b/src/com/android/calendar/month/MonthWeekEventsView.kt
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.Service
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.provider.CalendarContract.Attendees
+import android.text.TextPaint
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.MotionEvent
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Formatter
+import java.util.HashMap
+import java.util.Iterator
+import java.util.List
+import java.util.Locale
+
+class MonthWeekEventsView
+/**
+ * Shows up as an error if we don't include this.
+ */
+(context: Context) : SimpleWeekView(context) {
+ // Renamed to avoid override modifier and type mismatch error
+ protected val mTodayTime: Time = Time()
+ override protected var mHasToday = false
+ protected var mTodayIndex = -1
+ protected var mOrientation: Int = Configuration.ORIENTATION_LANDSCAPE
+ protected var mEvents: List<ArrayList<Event?>>? = null
+ protected var mUnsortedEvents: ArrayList<Event?>? = null
+ var mDna: HashMap<Int, Utils.DNAStrand>? = null
+
+ // This is for drawing the outlines around event chips and supports up to 10
+ // events being drawn on each day. The code will expand this if necessary.
+ protected var mEventOutlines: FloatRef = FloatRef(10 * 4 * 4 * 7)
+ protected var mMonthNamePaint: Paint? = null
+ protected var mEventPaint: TextPaint = TextPaint()
+ protected var mSolidBackgroundEventPaint: TextPaint? = null
+ protected var mFramedEventPaint: TextPaint? = null
+ protected var mDeclinedEventPaint: TextPaint? = null
+ protected var mEventExtrasPaint: TextPaint = TextPaint()
+ protected var mEventDeclinedExtrasPaint: TextPaint = TextPaint()
+ protected var mWeekNumPaint: Paint = Paint()
+ protected var mDNAAllDayPaint: Paint = Paint()
+ protected var mDNATimePaint: Paint = Paint()
+ protected var mEventSquarePaint: Paint = Paint()
+ protected var mTodayDrawable: Drawable? = null
+ protected var mMonthNumHeight = 0
+ protected var mMonthNumAscentHeight = 0
+ protected var mEventHeight = 0
+ protected var mEventAscentHeight = 0
+ protected var mExtrasHeight = 0
+ protected var mExtrasAscentHeight = 0
+ protected var mExtrasDescent = 0
+ protected var mWeekNumAscentHeight = 0
+ protected var mMonthBGColor = 0
+ protected var mMonthBGOtherColor = 0
+ protected var mMonthBGTodayColor = 0
+ protected var mMonthNumColor = 0
+ protected var mMonthNumOtherColor = 0
+ protected var mMonthNumTodayColor = 0
+ protected var mMonthNameColor = 0
+ protected var mMonthNameOtherColor = 0
+ protected var mMonthEventColor = 0
+ protected var mMonthDeclinedEventColor = 0
+ protected var mMonthDeclinedExtrasColor = 0
+ protected var mMonthEventExtraColor = 0
+ protected var mMonthEventOtherColor = 0
+ protected var mMonthEventExtraOtherColor = 0
+ protected var mMonthWeekNumColor = 0
+ protected var mMonthBusyBitsBgColor = 0
+ protected var mMonthBusyBitsBusyTimeColor = 0
+ protected var mMonthBusyBitsConflictTimeColor = 0
+ private var mClickedDayIndex = -1
+ private var mClickedDayColor = 0
+ protected var mEventChipOutlineColor = -0x1
+ protected var mDaySeparatorInnerColor = 0
+ protected var mTodayAnimateColor = 0
+ private var mAnimateToday = false
+ private var mAnimateTodayAlpha = 0
+ private var mTodayAnimator: ObjectAnimator? = null
+ private val mAnimatorListener: TodayAnimatorListener = TodayAnimatorListener()
+
+ internal inner class TodayAnimatorListener : AnimatorListenerAdapter() {
+ @Volatile
+ private var mAnimator: Animator? = null
+
+ @Volatile
+ private var mFadingIn = false
+ @Override
+ override fun onAnimationEnd(animation: Animator) {
+ synchronized(this) {
+ if (mAnimator !== animation) {
+ animation.removeAllListeners()
+ animation.cancel()
+ return
+ }
+ if (mFadingIn) {
+ if (mTodayAnimator != null) {
+ mTodayAnimator?.removeAllListeners()
+ mTodayAnimator?.cancel()
+ }
+ mTodayAnimator = ObjectAnimator.ofInt(this@MonthWeekEventsView,
+ "animateTodayAlpha", 255, 0)
+ mAnimator = mTodayAnimator
+ mFadingIn = false
+ mTodayAnimator?.addListener(this)
+ mTodayAnimator?.setDuration(600)
+ mTodayAnimator?.start()
+ } else {
+ mAnimateToday = false
+ mAnimateTodayAlpha = 0
+ mAnimator?.removeAllListeners()
+ mAnimator = null
+ mTodayAnimator = null
+ invalidate()
+ }
+ }
+ }
+
+ fun setAnimator(animation: Animator?) {
+ mAnimator = animation
+ }
+
+ fun setFadingIn(fadingIn: Boolean) {
+ mFadingIn = fadingIn
+ }
+ }
+
+ private var mDayXs: IntArray? = null
+
+ /**
+ * This provides a reference to a float array which allows for easy size
+ * checking and reallocation. Used for drawing lines.
+ */
+ inner class FloatRef(size: Int) {
+ var array: FloatArray
+ fun ensureSize(newSize: Int) {
+ if (newSize >= array.size) {
+ // Add enough space for 7 more boxes to be drawn
+ array = Arrays.copyOf(array, newSize + 16 * 7)
+ }
+ }
+
+ init {
+ array = FloatArray(size)
+ }
+ }
+
+ // Sets the list of events for this week. Takes a sorted list of arrays
+ // divided up by day for generating the large month version and the full
+ // arraylist sorted by start time to generate the dna version.
+ fun setEvents(sortedEvents: List<ArrayList<Event?>>?, unsortedEvents: ArrayList<Event?>?) {
+ setEvents(sortedEvents)
+ // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to
+ // generate dna bits before its width has been fixed.
+ createDna(unsortedEvents)
+ }
+
+ /**
+ * Sets up the dna bits for the view. This will return early if the view
+ * isn't in a state that will create a valid set of dna yet (such as the
+ * views width not being set correctly yet).
+ */
+ fun createDna(unsortedEvents: ArrayList<Event?>?) {
+ if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) {
+ // Stash the list of events for use when this view is ready, or
+ // just clear it if a null set has been passed to this view
+ mUnsortedEvents = unsortedEvents
+ mDna = null
+ return
+ } else {
+ // clear the cached set of events since we're ready to build it now
+ mUnsortedEvents = null
+ }
+ // Create the drawing coordinates for dna
+ if (!mShowDetailsInMonth) {
+ val numDays: Int = mEvents!!.size
+ var effectiveWidth: Int = mWidth - mPadding * 2
+ if (mShowWeekNum) {
+ effectiveWidth -= SPACING_WEEK_NUMBER
+ }
+ DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING
+ mDNAAllDayPaint?.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat())
+ mDayXs = IntArray(numDays)
+ for (day in 0 until numDays) {
+ mDayXs!![day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING
+ }
+ val top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1
+ val bottom: Int = mHeight - DNA_MARGIN
+ mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom,
+ DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext())
+ }
+ }
+
+ fun setEvents(sortedEvents: List<ArrayList<Event?>>?) {
+ mEvents = sortedEvents
+ if (sortedEvents == null) {
+ return
+ }
+ if (sortedEvents.size !== mNumDays) {
+ if (Log.isLoggable(TAG, Log.ERROR)) {
+ Log.wtf(TAG, ("Events size must be same as days displayed: size="
+ + sortedEvents.size) + " days=" + mNumDays)
+ }
+ mEvents = null
+ return
+ }
+ }
+
+ protected fun loadColors(context: Context) {
+ val res: Resources = context.getResources()
+ mMonthWeekNumColor = res.getColor(R.color.month_week_num_color)
+ mMonthNumColor = res.getColor(R.color.month_day_number)
+ mMonthNumOtherColor = res.getColor(R.color.month_day_number_other)
+ mMonthNumTodayColor = res.getColor(R.color.month_today_number)
+ mMonthNameColor = mMonthNumColor
+ mMonthNameOtherColor = mMonthNumOtherColor
+ mMonthEventColor = res.getColor(R.color.month_event_color)
+ mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color)
+ mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color)
+ mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color)
+ mMonthEventOtherColor = res.getColor(R.color.month_event_other_color)
+ mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color)
+ mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor)
+ mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor)
+ mMonthBGColor = res.getColor(R.color.month_bgcolor)
+ mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines)
+ mTodayAnimateColor = res.getColor(R.color.today_highlight_color)
+ mClickedDayColor = res.getColor(R.color.day_clicked_background_color)
+ mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light)
+ }
+
+ /**
+ * Sets up the text and style properties for painting. Override this if you
+ * want to use a different paint.
+ */
+ @Override
+ protected override fun initView() {
+ super.initView()
+ if (!mInitialized) {
+ val resources: Resources = getContext().getResources()
+ mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month)
+ TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title)
+ TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number)
+ SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin)
+ CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color)
+ EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color)
+ if (mScale != 1f) {
+ TOP_PADDING_MONTH_NUMBER *= mScale.toInt()
+ TOP_PADDING_WEEK_NUMBER *= mScale.toInt()
+ SIDE_PADDING_MONTH_NUMBER *= mScale.toInt()
+ SIDE_PADDING_WEEK_NUMBER *= mScale.toInt()
+ SPACING_WEEK_NUMBER *= mScale.toInt()
+ TEXT_SIZE_MONTH_NUMBER *= mScale.toInt()
+ TEXT_SIZE_EVENT *= mScale.toInt()
+ TEXT_SIZE_EVENT_TITLE *= mScale.toInt()
+ TEXT_SIZE_MORE_EVENTS *= mScale.toInt()
+ TEXT_SIZE_MONTH_NAME *= mScale.toInt()
+ TEXT_SIZE_WEEK_NUM *= mScale.toInt()
+ DAY_SEPARATOR_OUTER_WIDTH *= mScale.toInt()
+ DAY_SEPARATOR_INNER_WIDTH *= mScale.toInt()
+ DAY_SEPARATOR_VERTICAL_LENGTH *= mScale.toInt()
+ DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT *= mScale.toInt()
+ EVENT_X_OFFSET_LANDSCAPE *= mScale.toInt()
+ EVENT_Y_OFFSET_LANDSCAPE *= mScale.toInt()
+ EVENT_Y_OFFSET_PORTRAIT *= mScale.toInt()
+ EVENT_SQUARE_WIDTH *= mScale.toInt()
+ EVENT_SQUARE_BORDER *= mScale.toInt()
+ EVENT_LINE_PADDING *= mScale.toInt()
+ EVENT_BOTTOM_PADDING *= mScale.toInt()
+ EVENT_RIGHT_PADDING *= mScale.toInt()
+ DNA_MARGIN *= mScale.toInt()
+ DNA_WIDTH *= mScale.toInt()
+ DNA_ALL_DAY_HEIGHT *= mScale.toInt()
+ DNA_MIN_SEGMENT_HEIGHT *= mScale.toInt()
+ DNA_SIDE_PADDING *= mScale.toInt()
+ DEFAULT_EDGE_SPACING *= mScale.toInt()
+ DNA_ALL_DAY_WIDTH *= mScale.toInt()
+ TODAY_HIGHLIGHT_WIDTH *= mScale.toInt()
+ }
+ if (!mShowDetailsInMonth) {
+ TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN
+ }
+ mInitialized = true
+ }
+ mPadding = DEFAULT_EDGE_SPACING
+ loadColors(getContext())
+ // TODO modify paint properties depending on isMini
+ mMonthNumPaint = Paint()
+ mMonthNumPaint?.setFakeBoldText(false)
+ mMonthNumPaint?.setAntiAlias(true)
+ mMonthNumPaint?.setTextSize(TEXT_SIZE_MONTH_NUMBER.toFloat())
+ mMonthNumPaint?.setColor(mMonthNumColor)
+ mMonthNumPaint?.setStyle(Style.FILL)
+ mMonthNumPaint?.setTextAlign(Align.RIGHT)
+ mMonthNumPaint?.setTypeface(Typeface.DEFAULT)
+ mMonthNumAscentHeight = (-mMonthNumPaint!!.ascent() + 0.5f).toInt()
+ mMonthNumHeight = (mMonthNumPaint!!.descent() - mMonthNumPaint!!.ascent() + 0.5f).toInt()
+ mEventPaint = TextPaint()
+ mEventPaint?.setFakeBoldText(true)
+ mEventPaint?.setAntiAlias(true)
+ mEventPaint?.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat())
+ mEventPaint?.setColor(mMonthEventColor)
+ mSolidBackgroundEventPaint = TextPaint(mEventPaint)
+ mSolidBackgroundEventPaint?.setColor(EVENT_TEXT_COLOR)
+ mFramedEventPaint = TextPaint(mSolidBackgroundEventPaint)
+ mDeclinedEventPaint = TextPaint()
+ mDeclinedEventPaint?.setFakeBoldText(true)
+ mDeclinedEventPaint?.setAntiAlias(true)
+ mDeclinedEventPaint?.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat())
+ mDeclinedEventPaint?.setColor(mMonthDeclinedEventColor)
+ mEventAscentHeight = (-mEventPaint.ascent() + 0.5f).toInt()
+ mEventHeight = (mEventPaint.descent() - mEventPaint.ascent() + 0.5f).toInt()
+ mEventExtrasPaint = TextPaint()
+ mEventExtrasPaint?.setFakeBoldText(false)
+ mEventExtrasPaint?.setAntiAlias(true)
+ mEventExtrasPaint?.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+ mEventExtrasPaint?.setTextSize(TEXT_SIZE_EVENT.toFloat())
+ mEventExtrasPaint?.setColor(mMonthEventExtraColor)
+ mEventExtrasPaint?.setStyle(Style.FILL)
+ mEventExtrasPaint?.setTextAlign(Align.LEFT)
+ mExtrasHeight = (mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f).toInt()
+ mExtrasAscentHeight = (-mEventExtrasPaint.ascent() + 0.5f).toInt()
+ mExtrasDescent = (mEventExtrasPaint.descent() + 0.5f).toInt()
+ mEventDeclinedExtrasPaint = TextPaint()
+ mEventDeclinedExtrasPaint.setFakeBoldText(false)
+ mEventDeclinedExtrasPaint.setAntiAlias(true)
+ mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+ mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT.toFloat())
+ mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor)
+ mEventDeclinedExtrasPaint.setStyle(Style.FILL)
+ mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT)
+ mWeekNumPaint = Paint()
+ mWeekNumPaint.setFakeBoldText(false)
+ mWeekNumPaint.setAntiAlias(true)
+ mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM.toFloat())
+ mWeekNumPaint.setColor(mWeekNumColor)
+ mWeekNumPaint.setStyle(Style.FILL)
+ mWeekNumPaint.setTextAlign(Align.RIGHT)
+ mWeekNumAscentHeight = (-mWeekNumPaint.ascent() + 0.5f).toInt()
+ mDNAAllDayPaint = Paint()
+ mDNATimePaint = Paint()
+ mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor)
+ mDNATimePaint.setStyle(Style.FILL_AND_STROKE)
+ mDNATimePaint.setStrokeWidth(DNA_WIDTH.toFloat())
+ mDNATimePaint.setAntiAlias(false)
+ mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor)
+ mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE)
+ mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat())
+ mDNAAllDayPaint.setAntiAlias(false)
+ mEventSquarePaint = Paint()
+ mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+ mEventSquarePaint.setAntiAlias(false)
+ if (DEBUG_LAYOUT) {
+ Log.d("EXTRA", "mScale=$mScale")
+ Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint?.ascent()
+ ?.toString() + " descent=" + mMonthNumPaint?.descent()?.toString() +
+ " int height=" + mMonthNumHeight)
+ Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint?.ascent()
+ ?.toString() + " descent=" + mEventPaint.descent().toString() +
+ " int height=" + mEventHeight
+ .toString() + " int ascent=" + mEventAscentHeight)
+ Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent()
+ .toString() + " descent=" + mEventExtrasPaint.descent().toString() +
+ " int height=" + mExtrasHeight)
+ Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent()
+ .toString() + " descent=" + mWeekNumPaint.descent())
+ }
+ }
+
+ @Override
+ override fun setWeekParams(params: HashMap<String?, Int?>, tz: String) {
+ super.setWeekParams(params, tz)
+ if (params.containsKey(VIEW_PARAMS_ORIENTATION)) {
+ mOrientation = params.get(VIEW_PARAMS_ORIENTATION) ?:
+ Configuration.ORIENTATION_LANDSCAPE
+ }
+ updateToday(tz)
+ mNumCells = mNumDays + 1
+ if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) {
+ synchronized(mAnimatorListener) {
+ if (mTodayAnimator != null) {
+ mTodayAnimator?.removeAllListeners()
+ mTodayAnimator?.cancel()
+ }
+ mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
+ Math.max(mAnimateTodayAlpha, 80), 255)
+ mTodayAnimator?.setDuration(150)
+ mAnimatorListener.setAnimator(mTodayAnimator)
+ mAnimatorListener.setFadingIn(true)
+ mTodayAnimator?.addListener(mAnimatorListener)
+ mAnimateToday = true
+ mTodayAnimator?.start()
+ }
+ }
+ }
+
+ /**
+ * @param tz
+ */
+ fun updateToday(tz: String): Boolean {
+ mTodayTime.timezone = tz
+ mTodayTime.setToNow()
+ mTodayTime.normalize(true)
+ val julianToday: Int = Time.getJulianDay(mTodayTime.toMillis(false), mTodayTime.gmtoff)
+ if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) {
+ mHasToday = true
+ mTodayIndex = julianToday - mFirstJulianDay
+ } else {
+ mHasToday = false
+ mTodayIndex = -1
+ }
+ return mHasToday
+ }
+
+ fun setAnimateTodayAlpha(alpha: Int) {
+ mAnimateTodayAlpha = alpha
+ invalidate()
+ }
+
+ @Override
+ protected override fun onDraw(canvas: Canvas) {
+ drawBackground(canvas)
+ drawWeekNums(canvas)
+ drawDaySeparators(canvas)
+ if (mHasToday && mAnimateToday) {
+ drawToday(canvas)
+ }
+ if (mShowDetailsInMonth) {
+ drawEvents(canvas)
+ } else {
+ if (mDna == null && mUnsortedEvents != null) {
+ createDna(mUnsortedEvents)
+ }
+ drawDNA(canvas)
+ }
+ drawClick(canvas)
+ }
+
+ protected fun drawToday(canvas: Canvas) {
+ r.top = DAY_SEPARATOR_INNER_WIDTH + TODAY_HIGHLIGHT_WIDTH / 2
+ r.bottom = mHeight - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt()
+ p.setStyle(Style.STROKE)
+ p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH.toFloat())
+ r.left = computeDayLeftPosition(mTodayIndex) + TODAY_HIGHLIGHT_WIDTH / 2
+ r.right = (computeDayLeftPosition(mTodayIndex + 1)
+ - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt())
+ p.setColor(mTodayAnimateColor or (mAnimateTodayAlpha shl 24))
+ canvas.drawRect(r, p)
+ p.setStyle(Style.FILL)
+ }
+
+ // TODO move into SimpleWeekView
+ // Computes the x position for the left side of the given day
+ private fun computeDayLeftPosition(day: Int): Int {
+ var effectiveWidth: Int = mWidth
+ var x = 0
+ var xOffset = 0
+ if (mShowWeekNum) {
+ xOffset = SPACING_WEEK_NUMBER + mPadding
+ effectiveWidth -= xOffset
+ }
+ x = day * effectiveWidth / mNumDays + xOffset
+ return x
+ }
+
+ @Override
+ protected override fun drawDaySeparators(canvas: Canvas) {
+ val lines = FloatArray(8 * 4)
+ var count = 6 * 4
+ var wkNumOffset = 0
+ var i = 0
+ if (mShowWeekNum) {
+ // This adds the first line separating the week number
+ val xOffset: Int = SPACING_WEEK_NUMBER + mPadding
+ count += 4
+ lines[i++] = xOffset.toFloat()
+ lines[i++] = 0f
+ lines[i++] = xOffset.toFloat()
+ lines[i++] = mHeight.toFloat()
+ wkNumOffset++
+ }
+ count += 4
+ lines[i++] = 0f
+ lines[i++] = 0f
+ lines[i++] = mWidth.toFloat()
+ lines[i++] = 0f
+ val y0 = 0
+ val y1: Int = mHeight
+ while (i < count) {
+ val x = computeDayLeftPosition(i / 4 - wkNumOffset)
+ lines[i++] = x.toFloat()
+ lines[i++] = y0.toFloat()
+ lines[i++] = x.toFloat()
+ lines[i++] = y1.toFloat()
+ }
+ p.setColor(mDaySeparatorInnerColor)
+ p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH.toFloat())
+ canvas.drawLines(lines, 0, count, p)
+ }
+
+ @Override
+ protected override fun drawBackground(canvas: Canvas) {
+ var i = 0
+ var offset = 0
+ r.top = DAY_SEPARATOR_INNER_WIDTH
+ r.bottom = mHeight
+ if (mShowWeekNum) {
+ i++
+ offset++
+ }
+ if (!mOddMonth!!.get(i)) {
+ while (++i < mOddMonth!!.size && !mOddMonth!!.get(i));
+ r.right = computeDayLeftPosition(i - offset)
+ r.left = 0
+ p.setColor(mMonthBGOtherColor)
+ canvas.drawRect(r, p)
+ // compute left edge for i, set up r, draw
+ } else if (!mOddMonth!!.get(mOddMonth!!.size - 1.also { i = it })) {
+ while (--i >= offset && !mOddMonth!!.get(i));
+ i++
+ // compute left edge for i, set up r, draw
+ r.right = mWidth
+ r.left = computeDayLeftPosition(i - offset)
+ p.setColor(mMonthBGOtherColor)
+ canvas.drawRect(r, p)
+ }
+ if (mHasToday) {
+ p.setColor(mMonthBGTodayColor)
+ r.left = computeDayLeftPosition(mTodayIndex)
+ r.right = computeDayLeftPosition(mTodayIndex + 1)
+ canvas.drawRect(r, p)
+ }
+ }
+
+ // Draw the "clicked" color on the tapped day
+ private fun drawClick(canvas: Canvas) {
+ if (mClickedDayIndex != -1) {
+ val alpha: Int = p.getAlpha()
+ p.setColor(mClickedDayColor)
+ p.setAlpha(mClickedAlpha)
+ r.left = computeDayLeftPosition(mClickedDayIndex)
+ r.right = computeDayLeftPosition(mClickedDayIndex + 1)
+ r.top = DAY_SEPARATOR_INNER_WIDTH
+ r.bottom = mHeight
+ canvas.drawRect(r, p)
+ p.setAlpha(alpha)
+ }
+ }
+
+ @Override
+ protected override fun drawWeekNums(canvas: Canvas) {
+ var y: Int
+ var i = 0
+ var offset = -1
+ var todayIndex = mTodayIndex
+ var x = 0
+ var numCount: Int = mNumDays
+ if (mShowWeekNum) {
+ x = SIDE_PADDING_WEEK_NUMBER + mPadding
+ y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER
+ canvas.drawText(mDayNumbers!!.get(0) as String, x.toFloat(), y.toFloat(), mWeekNumPaint)
+ numCount++
+ i++
+ todayIndex++
+ offset++
+ }
+ y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER
+ var isFocusMonth: Boolean = mFocusDay!!.get(i)
+ var isBold = false
+ mMonthNumPaint?.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor)
+ while (i < numCount) {
+ if (mHasToday && todayIndex == i) {
+ mMonthNumPaint?.setColor(mMonthNumTodayColor)
+ mMonthNumPaint?.setFakeBoldText(true.also { isBold = it })
+ if (i + 1 < numCount) {
+ // Make sure the color will be set back on the next
+ // iteration
+ isFocusMonth = !mFocusDay!!.get(i + 1)
+ }
+ } else if (mFocusDay?.get(i) !== isFocusMonth) {
+ isFocusMonth = mFocusDay!!.get(i)
+ mMonthNumPaint?.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor)
+ }
+ x = computeDayLeftPosition(i - offset) - SIDE_PADDING_MONTH_NUMBER
+ canvas.drawText(mDayNumbers!!.get(i) as String, x.toFloat(), y.toFloat(),
+ mMonthNumPaint as Paint)
+ if (isBold) {
+ mMonthNumPaint?.setFakeBoldText(false.also { isBold = it })
+ }
+ i++
+ }
+ }
+
+ protected fun drawEvents(canvas: Canvas) {
+ if (mEvents == null) {
+ return
+ }
+ var day = -1
+ for (eventDay in mEvents!!) {
+ day++
+ if (eventDay == null || eventDay.size === 0) {
+ continue
+ }
+ var ySquare: Int
+ val xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1
+ var rightEdge = computeDayLeftPosition(day + 1)
+ if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER
+ rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1
+ } else {
+ ySquare = EVENT_Y_OFFSET_LANDSCAPE
+ rightEdge -= EVENT_X_OFFSET_LANDSCAPE
+ }
+
+ // Determine if everything will fit when time ranges are shown.
+ var showTimes = true
+ var iter: Iterator<Event> = eventDay.iterator() as Iterator<Event>
+ var yTest = ySquare
+ while (iter.hasNext()) {
+ val event: Event = iter.next()
+ val newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(),
+ showTimes, /*doDraw*/false)
+ if (newY == yTest) {
+ showTimes = false
+ break
+ }
+ yTest = newY
+ }
+ var eventCount = 0
+ iter = eventDay.iterator() as Iterator<Event>
+ while (iter.hasNext()) {
+ val event: Event = iter.next()
+ val newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(),
+ showTimes, /*doDraw*/true)
+ if (newY == ySquare) {
+ break
+ }
+ eventCount++
+ ySquare = newY
+ }
+ val remaining: Int = eventDay.size- eventCount
+ if (remaining > 0) {
+ drawMoreEvents(canvas, remaining, xSquare)
+ }
+ }
+ }
+
+ protected fun addChipOutline(lines: FloatRef, count: Int, x: Int, y: Int): Int {
+ var count = count
+ lines.ensureSize(count + 16)
+ // top of box
+ lines.array[count++] = x.toFloat()
+ lines.array[count++] = y.toFloat()
+ lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+ lines.array[count++] = y.toFloat()
+ // right side of box
+ lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+ lines.array[count++] = y.toFloat()
+ lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+ lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+ // left side of box
+ lines.array[count++] = x.toFloat()
+ lines.array[count++] = y.toFloat()
+ lines.array[count++] = x.toFloat()
+ lines.array[count++] = (y + EVENT_SQUARE_WIDTH + 1).toFloat()
+ // bottom of box
+ lines.array[count++] = x.toFloat()
+ lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+ lines.array[count++] = (x + EVENT_SQUARE_WIDTH + 1).toFloat()
+ lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+ return count
+ }
+
+ /**
+ * Attempts to draw the given event. Returns the y for the next event or the
+ * original y if the event will not fit. An event is considered to not fit
+ * if the event and its extras won't fit or if there are more events and the
+ * more events line would not fit after drawing this event.
+ *
+ * @param canvas the canvas to draw on
+ * @param event the event to draw
+ * @param x the top left corner for this event's color chip
+ * @param y the top left corner for this event's color chip
+ * @param rightEdge the rightmost point we're allowed to draw on (exclusive)
+ * @param moreEvents indicates whether additional events will follow this one
+ * @param showTimes if set, a second line with a time range will be displayed for non-all-day
+ * events
+ * @param doDraw if set, do the actual drawing; otherwise this just computes the height
+ * and returns
+ * @return the y for the next event or the original y if it won't fit
+ */
+ protected fun drawEvent(canvas: Canvas, event: Event, x: Int, y: Int, rightEdge: Int,
+ moreEvents: Boolean, showTimes: Boolean, doDraw: Boolean): Int {
+ /*
+ * Vertical layout:
+ * (top of box)
+ * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent
+ * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event
+ * c. [optional] Time range (mExtrasHeight)
+ * d. EVENT_LINE_PADDING
+ *
+ * Repeat (b,c,d) as needed and space allows. If we have more events than fit, we need
+ * to leave room for something like "+2" at the bottom:
+ *
+ * e. "+ more" line (mExtrasHeight)
+ *
+ * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING)
+ * (bottom of box)
+ */
+ var y = y
+ val BORDER_SPACE = EVENT_SQUARE_BORDER + 1 // want a 1-pixel gap inside border
+ val STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2 // adjust bounds for stroke width
+ val allDay: Boolean = event.allDay
+ var eventRequiredSpace = mEventHeight
+ if (allDay) {
+ // Add a few pixels for the box we draw around all-day events.
+ eventRequiredSpace += BORDER_SPACE * 2
+ } else if (showTimes) {
+ // Need room for the "1pm - 2pm" line.
+ eventRequiredSpace += mExtrasHeight
+ }
+ var reservedSpace = EVENT_BOTTOM_PADDING // leave a bit of room at the bottom
+ if (moreEvents) {
+ // More events follow. Leave a bit of space between events.
+ eventRequiredSpace += EVENT_LINE_PADDING
+
+ // Make sure we have room for the "+ more" line. (The "+ more" line is expected
+ // to be <= the height of an event line, so we won't show "+1" when we could be
+ // showing the event.)
+ reservedSpace += mExtrasHeight
+ }
+ if (y + eventRequiredSpace + reservedSpace > mHeight) {
+ // Not enough space, return original y
+ return y
+ } else if (!doDraw) {
+ return y + eventRequiredSpace
+ }
+ val isDeclined = event.selfAttendeeStatus === Attendees.ATTENDEE_STATUS_DECLINED
+ var color: Int = event.color
+ if (isDeclined) {
+ color = Utils.getDeclinedColorFromColor(color)
+ }
+ val textX: Int
+ var textY: Int
+ val textRightEdge: Int
+ if (allDay) {
+ // We shift the render offset "inward", because drawRect with a stroke width greater
+ // than 1 draws outside the specified bounds. (We don't adjust the left edge, since
+ // we want to match the existing appearance of the "event square".)
+ r.left = x
+ r.right = rightEdge - STROKE_WIDTH_ADJ
+ r.top = y + STROKE_WIDTH_ADJ
+ r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ
+ textX = x + BORDER_SPACE
+ textY = y + mEventAscentHeight + BORDER_SPACE
+ textRightEdge = rightEdge - BORDER_SPACE
+ } else {
+ r.left = x
+ r.right = x + EVENT_SQUARE_WIDTH
+ r.bottom = y + mEventAscentHeight
+ r.top = r.bottom - EVENT_SQUARE_WIDTH
+ textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING
+ textY = y + mEventAscentHeight
+ textRightEdge = rightEdge
+ }
+ var boxStyle: Style = Style.STROKE
+ var solidBackground = false
+ if (event.selfAttendeeStatus !== Attendees.ATTENDEE_STATUS_INVITED) {
+ boxStyle = Style.FILL_AND_STROKE
+ if (allDay) {
+ solidBackground = true
+ }
+ }
+ mEventSquarePaint.setStyle(boxStyle)
+ mEventSquarePaint.setColor(color)
+ canvas.drawRect(r, mEventSquarePaint)
+ val avail = (textRightEdge - textX).toFloat()
+ var text: CharSequence = TextUtils.ellipsize(
+ event.title, mEventPaint, avail, TextUtils.TruncateAt.END)
+ val textPaint: TextPaint?
+ textPaint = if (solidBackground) {
+ // Text color needs to contrast with solid background.
+ mSolidBackgroundEventPaint
+ } else if (isDeclined) {
+ // Use "declined event" color.
+ mDeclinedEventPaint
+ } else if (allDay) {
+ // Text inside frame is same color as frame.
+ mFramedEventPaint?.setColor(color)
+ mFramedEventPaint
+ } else {
+ // Use generic event text color.
+ mEventPaint
+ }
+ canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(), textPaint as Paint)
+ y += mEventHeight
+ if (allDay) {
+ y += BORDER_SPACE * 2
+ }
+ if (showTimes && !allDay) {
+ // show start/end time, e.g. "1pm - 2pm"
+ textY = y + mExtrasAscentHeight
+ mStringBuilder.setLength(0)
+ text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis,
+ event.endMillis, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL,
+ Utils.getTimeZone(getContext(), null)).toString()
+ text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END)
+ canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(),
+ if (isDeclined) mEventDeclinedExtrasPaint else mEventExtrasPaint)
+ y += mExtrasHeight
+ }
+ y += EVENT_LINE_PADDING
+ return y
+ }
+
+ protected fun drawMoreEvents(canvas: Canvas, remainingEvents: Int, x: Int) {
+ val y: Int = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING)
+ val text: String = getContext().getResources().getQuantityString(
+ R.plurals.month_more_events, remainingEvents)
+ mEventExtrasPaint.setAntiAlias(true)
+ mEventExtrasPaint.setFakeBoldText(true)
+ canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(),
+ mEventExtrasPaint as Paint)
+ mEventExtrasPaint!!.setFakeBoldText(false)
+ }
+
+ /**
+ * Draws a line showing busy times in each day of week The method draws
+ * non-conflicting times in the event color and times with conflicting
+ * events in the dna conflict color defined in colors.
+ *
+ * @param canvas
+ */
+ protected fun drawDNA(canvas: Canvas) {
+ // Draw event and conflict times
+ if (mDna != null) {
+ for (strand in mDna!!.values) {
+ if (strand.color === CONFLICT_COLOR || strand.points == null ||
+ (strand.points as FloatArray).size === 0) {
+ continue
+ }
+ mDNATimePaint!!.setColor(strand.color)
+ canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint)
+ }
+ // Draw black last to make sure it's on top
+ val strand: Utils.DNAStrand? = mDna?.get(CONFLICT_COLOR)
+ if (strand != null && strand!!.points != null && strand!!.points?.size !== 0) {
+ mDNATimePaint!!.setColor(strand.color)
+ canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint)
+ }
+ if (mDayXs == null) {
+ return
+ }
+ val numDays = mDayXs!!.size
+ val xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2
+ if (strand != null && strand!!.allDays != null && strand!!.allDays?.size === numDays) {
+ for (i in 0 until numDays) {
+ // this adds at most 7 draws. We could sort it by color and
+ // build an array instead but this is easier.
+ if (strand!!.allDays?.get(i) !== 0) {
+ mDNAAllDayPaint!!.setColor(strand!!.allDays!!.get(i))
+ canvas.drawLine(mDayXs!![i].toFloat() + xOffset.toFloat(),
+ DNA_MARGIN.toFloat(), mDayXs!![i].toFloat() + xOffset.toFloat(),
+ DNA_MARGIN.toFloat() + DNA_ALL_DAY_HEIGHT.toFloat(),
+ mDNAAllDayPaint as Paint)
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected override fun updateSelectionPositions() {
+ if (mHasSelectedDay) {
+ var selectedPosition: Int = mSelectedDay - mWeekStart
+ if (selectedPosition < 0) {
+ selectedPosition += 7
+ }
+ var effectiveWidth: Int = mWidth - mPadding * 2
+ effectiveWidth -= SPACING_WEEK_NUMBER
+ mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding
+ mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding
+ mSelectedLeft += SPACING_WEEK_NUMBER
+ mSelectedRight += SPACING_WEEK_NUMBER
+ }
+ }
+
+ fun getDayIndexFromLocation(x: Float): Int {
+ val dayStart: Int = if (mShowWeekNum) SPACING_WEEK_NUMBER + mPadding else mPadding
+ return if (x < dayStart || x > mWidth - mPadding) {
+ -1
+ } else (((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)).toInt())
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ }
+
+ @Override
+ override fun getDayFromLocation(x: Float): Time? {
+ val dayPosition = getDayIndexFromLocation(x)
+ if (dayPosition == -1) {
+ return null
+ }
+ var day: Int = mFirstJulianDay + dayPosition
+ val time = Time(mTimeZone)
+ if (mWeek === 0) {
+ // This week is weird...
+ if (day < Time.EPOCH_JULIAN_DAY) {
+ day++
+ } else if (day == Time.EPOCH_JULIAN_DAY) {
+ time.set(1, 0, 1970)
+ time.normalize(true)
+ return time
+ }
+ }
+ time.setJulianDay(day)
+ return time
+ }
+
+ @Override
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ val context: Context = getContext()
+ // only send accessibility events if accessibility and exploration are
+ // on.
+ val am: AccessibilityManager = context
+ .getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
+ return super.onHoverEvent(event)
+ }
+ if (event.getAction() !== MotionEvent.ACTION_HOVER_EXIT) {
+ val hover: Time? = getDayFromLocation(event.getX())
+ if (hover != null
+ && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) !== 0)) {
+ val millis: Long = hover.toMillis(true)
+ val date: String = Utils!!.formatDateRange(context, millis, millis,
+ DateUtils.FORMAT_SHOW_DATE) as String
+ val accessEvent: AccessibilityEvent = AccessibilityEvent
+ .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
+ accessEvent.getText().add(date)
+ if (mShowDetailsInMonth && mEvents != null) {
+ val dayStart: Int = SPACING_WEEK_NUMBER + mPadding
+ val dayPosition = ((event.getX() - dayStart) * mNumDays / (mWidth
+ - dayStart - mPadding)).toInt()
+ val events: ArrayList<Event?> = mEvents!![dayPosition]
+ val text: List<CharSequence> = accessEvent.getText() as List<CharSequence>
+ for (e in events) {
+ text.add(e!!.titleAndLocation.toString() + ". ")
+ var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
+ if (!e!!.allDay) {
+ flags = flags or DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(context)) {
+ flags = flags or DateUtils.FORMAT_24HOUR
+ }
+ } else {
+ flags = flags or DateUtils.FORMAT_UTC
+ }
+ text.add(Utils.formatDateRange(context, e!!.startMillis, e!!.endMillis,
+ flags).toString() + ". ")
+ }
+ }
+ sendAccessibilityEventUnchecked(accessEvent)
+ mLastHoverTime = hover
+ }
+ }
+ return true
+ }
+
+ fun setClickedDay(xLocation: Float) {
+ mClickedDayIndex = getDayIndexFromLocation(xLocation)
+ invalidate()
+ }
+
+ fun clearClickedDay() {
+ mClickedDayIndex = -1
+ invalidate()
+ }
+
+ companion object {
+ private const val TAG = "MonthView"
+ private const val DEBUG_LAYOUT = false
+ const val VIEW_PARAMS_ORIENTATION = "orientation"
+ const val VIEW_PARAMS_ANIMATE_TODAY = "animate_today"
+
+ /* NOTE: these are not constants, and may be multiplied by a scale factor */
+ private var TEXT_SIZE_MONTH_NUMBER = 32
+ private var TEXT_SIZE_EVENT = 12
+ private var TEXT_SIZE_EVENT_TITLE = 14
+ private var TEXT_SIZE_MORE_EVENTS = 12
+ private var TEXT_SIZE_MONTH_NAME = 14
+ private var TEXT_SIZE_WEEK_NUM = 12
+ private var DNA_MARGIN = 4
+ private var DNA_ALL_DAY_HEIGHT = 4
+ private var DNA_MIN_SEGMENT_HEIGHT = 4
+ private var DNA_WIDTH = 8
+ private var DNA_ALL_DAY_WIDTH = 32
+ private var DNA_SIDE_PADDING = 6
+ private var CONFLICT_COLOR: Int = Color.BLACK
+ private var EVENT_TEXT_COLOR: Int = Color.WHITE
+ private var DEFAULT_EDGE_SPACING = 0
+ private var SIDE_PADDING_MONTH_NUMBER = 4
+ private var TOP_PADDING_MONTH_NUMBER = 4
+ private var TOP_PADDING_WEEK_NUMBER = 4
+ private var SIDE_PADDING_WEEK_NUMBER = 20
+ private var DAY_SEPARATOR_OUTER_WIDTH = 0
+ private var DAY_SEPARATOR_INNER_WIDTH = 1
+ private var DAY_SEPARATOR_VERTICAL_LENGTH = 53
+ private var DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT = 64
+ private const val MIN_WEEK_WIDTH = 50
+ private var EVENT_X_OFFSET_LANDSCAPE = 38
+ private var EVENT_Y_OFFSET_LANDSCAPE = 8
+ private var EVENT_Y_OFFSET_PORTRAIT = 7
+ private var EVENT_SQUARE_WIDTH = 10
+ private var EVENT_SQUARE_BORDER = 2
+ private var EVENT_LINE_PADDING = 2
+ private var EVENT_RIGHT_PADDING = 4
+ private var EVENT_BOTTOM_PADDING = 3
+ private var TODAY_HIGHLIGHT_WIDTH = 2
+ private var SPACING_WEEK_NUMBER = 24
+ private var mInitialized = false
+ private var mShowDetailsInMonth = false
+ protected var mStringBuilder: StringBuilder = StringBuilder(50)
+
+ // TODO recreate formatter when locale changes
+ protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault())
+ private const val mClickedAlpha = 128
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.java b/src/com/android/calendar/month/SimpleDayPickerFragment.java
deleted file mode 100644
index 2efae6a9..00000000
--- a/src/com/android/calendar/month/SimpleDayPickerFragment.java
+++ /dev/null
@@ -1,612 +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.month;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.app.Activity;
-import android.app.ListFragment;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * <p>
- * This displays a titled list of weeks with selectable days. It can be
- * configured to display the week number, start the week on a given day, show a
- * reduced number of days, or display an arbitrary number of weeks at a time. By
- * overriding methods and changing variables this fragment can be customized to
- * easily display a month selection component in a given style.
- * </p>
- */
-public class SimpleDayPickerFragment extends ListFragment implements OnScrollListener {
-
- private static final String TAG = "MonthFragment";
- private static final String KEY_CURRENT_TIME = "current_time";
-
- // Affects when the month selection will change while scrolling up
- protected static final int SCROLL_HYST_WEEKS = 2;
- // How long the GoTo fling animation should last
- protected static final int GOTO_SCROLL_DURATION = 500;
- // How long to wait after receiving an onScrollStateChanged notification
- // before acting on it
- protected static final int SCROLL_CHANGE_DELAY = 40;
- // The number of days to display in each week
- public static final int DAYS_PER_WEEK = 7;
- // The size of the month name displayed above the week list
- protected static final int MINI_MONTH_NAME_TEXT_SIZE = 18;
- public static int LIST_TOP_OFFSET = -1; // so that the top line will be under the separator
- protected int WEEK_MIN_VISIBLE_HEIGHT = 12;
- protected int BOTTOM_BUFFER = 20;
- protected int mSaturdayColor = 0;
- protected int mSundayColor = 0;
- protected int mDayNameColor = 0;
-
- // You can override these numbers to get a different appearance
- protected int mNumWeeks = 6;
- protected boolean mShowWeekNumber = false;
- protected int mDaysPerWeek = 7;
-
- // These affect the scroll speed and feel
- protected float mFriction = 1.0f;
-
- protected Context mContext;
- protected Handler mHandler;
-
- protected float mMinimumFlingVelocity;
-
- // highlighted time
- protected Time mSelectedDay = new Time();
- protected SimpleWeeksAdapter mAdapter;
- protected ListView mListView;
- protected ViewGroup mDayNamesHeader;
- protected String[] mDayLabels;
-
- // disposable variable used for time calculations
- protected Time mTempTime = new Time();
-
- private static float mScale = 0;
- // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
- protected int mFirstDayOfWeek;
- // The first day of the focus month
- protected Time mFirstDayOfMonth = new Time();
- // The first day that is visible in the view
- protected Time mFirstVisibleDay = new Time();
- // The name of the month to display
- protected TextView mMonthName;
- // The last name announced by accessibility
- protected CharSequence mPrevMonthName;
- // which month should be displayed/highlighted [0-11]
- protected int mCurrentMonthDisplayed;
- // used for tracking during a scroll
- protected long mPreviousScrollPosition;
- // used for tracking which direction the view is scrolling
- protected boolean mIsScrollingUp = false;
- // used for tracking what state listview is in
- protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
- // used for tracking what state listview is in
- protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- // This causes an update of the view at midnight
- protected Runnable mTodayUpdater = new Runnable() {
- @Override
- public void run() {
- Time midnight = new Time(mFirstVisibleDay.timezone);
- midnight.setToNow();
- long currentMillis = midnight.toMillis(true);
-
- midnight.hour = 0;
- midnight.minute = 0;
- midnight.second = 0;
- midnight.monthDay++;
- long millisToMidnight = midnight.normalize(true) - currentMillis;
- mHandler.postDelayed(this, millisToMidnight);
-
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
- };
-
- // This allows us to update our position when a day is tapped
- protected DataSetObserver mObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- Time day = mAdapter.getSelectedDay();
- if (day.year != mSelectedDay.year || day.yearDay != mSelectedDay.yearDay) {
- goTo(day.toMillis(true), true, true, false);
- }
- }
- };
-
- public SimpleDayPickerFragment(long initialTime) {
- goTo(initialTime, false, true, true);
- mHandler = new Handler();
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mContext = activity;
- String tz = Time.getCurrentTimezone();
- ViewConfiguration viewConfig = ViewConfiguration.get(activity);
- mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity();
-
- // Ensure we're in the correct time zone
- mSelectedDay.switchTimezone(tz);
- mSelectedDay.normalize(true);
- mFirstDayOfMonth.timezone = tz;
- mFirstDayOfMonth.normalize(true);
- mFirstVisibleDay.timezone = tz;
- mFirstVisibleDay.normalize(true);
- mTempTime.timezone = tz;
-
- Resources res = activity.getResources();
- mSaturdayColor = res.getColor(R.color.month_saturday);
- mSundayColor = res.getColor(R.color.month_sunday);
- mDayNameColor = res.getColor(R.color.month_day_names_color);
-
- // Adjust sizes for screen density
- if (mScale == 0) {
- mScale = activity.getResources().getDisplayMetrics().density;
- if (mScale != 1) {
- WEEK_MIN_VISIBLE_HEIGHT *= mScale;
- BOTTOM_BUFFER *= mScale;
- LIST_TOP_OFFSET *= mScale;
- }
- }
- setUpAdapter();
- setListAdapter(mAdapter);
- }
-
- /**
- * Creates a new adapter if necessary and sets up its parameters. Override
- * this method to provide a custom adapter.
- */
- protected void setUpAdapter() {
- HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
- weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
- Time.getJulianDay(mSelectedDay.toMillis(false), mSelectedDay.gmtoff));
- if (mAdapter == null) {
- mAdapter = new SimpleWeeksAdapter(getActivity(), weekParams);
- mAdapter.registerDataSetObserver(mObserver);
- } else {
- mAdapter.updateParams(weekParams);
- }
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- setUpListView();
- setUpHeader();
-
- mMonthName = (TextView) getView().findViewById(R.id.month_name);
- SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
- if (child == null) {
- return;
- }
- int julianDay = child.getFirstJulianDay();
- mFirstVisibleDay.setJulianDay(julianDay);
- // set the title to the month of the second week
- mTempTime.setJulianDay(julianDay + DAYS_PER_WEEK);
- setMonthDisplayed(mTempTime, true);
- }
-
- /**
- * Sets up the strings to be used by the header. Override this method to use
- * different strings or modify the view params.
- */
- protected void setUpHeader() {
- mDayLabels = new String[7];
- for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
- mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
- DateUtils.LENGTH_SHORTEST).toUpperCase();
- }
- }
-
- /**
- * Sets all the required fields for the list view. Override this method to
- * set a different list view behavior.
- */
- protected void setUpListView() {
- // Configure the listview
- mListView = getListView();
- // Transparent background on scroll
- mListView.setCacheColorHint(0);
- // No dividers
- mListView.setDivider(null);
- // Items are clickable
- mListView.setItemsCanFocus(true);
- // The thumb gets in the way, so disable it
- mListView.setFastScrollEnabled(false);
- mListView.setVerticalScrollBarEnabled(false);
- mListView.setOnScrollListener(this);
- mListView.setFadingEdgeLength(0);
- // Make the scrolling behavior nicer
- mListView.setFriction(ViewConfiguration.getScrollFriction() * mFriction);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- setUpAdapter();
- doResumeUpdates();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mHandler.removeCallbacks(mTodayUpdater);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true));
- }
-
- /**
- * Updates the user preference fields. Override this to use a different
- * preference space.
- */
- protected void doResumeUpdates() {
- // Get default week start based on locale, subtracting one for use with android Time.
- Calendar cal = Calendar.getInstance(Locale.getDefault());
- mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
-
- mShowWeekNumber = false;
-
- updateHeader();
- goTo(mSelectedDay.toMillis(true), false, false, false);
- mAdapter.setSelectedDay(mSelectedDay);
- mTodayUpdater.run();
- }
-
- /**
- * Fixes the day names header to provide correct spacing and updates the
- * label text. Override this to set up a custom header.
- */
- protected void updateHeader() {
- TextView label = (TextView) mDayNamesHeader.findViewById(R.id.wk_label);
- if (mShowWeekNumber) {
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
- }
- int offset = mFirstDayOfWeek - 1;
- for (int i = 1; i < 8; i++) {
- label = (TextView) mDayNamesHeader.getChildAt(i);
- if (i < mDaysPerWeek + 1) {
- int position = (offset + i) % 7;
- label.setText(mDayLabels[position]);
- label.setVisibility(View.VISIBLE);
- if (position == Time.SATURDAY) {
- label.setTextColor(mSaturdayColor);
- } else if (position == Time.SUNDAY) {
- label.setTextColor(mSundayColor);
- } else {
- label.setTextColor(mDayNameColor);
- }
- } else {
- label.setVisibility(View.GONE);
- }
- }
- mDayNamesHeader.invalidate();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.month_by_week,
- container, false);
- mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
- return v;
- }
-
- /**
- * Returns the UTC millis since epoch representation of the currently
- * selected time.
- *
- * @return
- */
- public long getSelectedTime() {
- return mSelectedDay.toMillis(true);
- }
-
- /**
- * This moves to the specified time in the view. If the time is not already
- * in range it will move the list so that the first of the month containing
- * the time is at the top of the view. If the new time is already in view
- * the list will not be scrolled unless forceScroll is true. This time may
- * optionally be highlighted as selected as well.
- *
- * @param time The time to move to
- * @param animate Whether to scroll to the given time or just redraw at the
- * new location
- * @param setSelected Whether to set the given time as selected
- * @param forceScroll Whether to recenter even if the time is already
- * visible
- * @return Whether or not the view animated to the new location
- */
- public boolean goTo(long time, boolean animate, boolean setSelected, boolean forceScroll) {
- if (time == -1) {
- Log.e(TAG, "time is invalid");
- return false;
- }
-
- // Set the selected day
- if (setSelected) {
- mSelectedDay.set(time);
- mSelectedDay.normalize(true);
- }
-
- // If this view isn't returned yet we won't be able to load the lists
- // current position, so return after setting the selected day.
- if (!isResumed()) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "We're not visible yet");
- }
- return false;
- }
-
- mTempTime.set(time);
- long millis = mTempTime.normalize(true);
- // Get the week we're going to
- // TODO push Util function into Calendar public api.
- int position = Utils.getWeeksSinceEpochFromJulianDay(
- Time.getJulianDay(millis, mTempTime.gmtoff), mFirstDayOfWeek);
-
- View child;
- int i = 0;
- int top = 0;
- // Find a child that's completely in the view
- do {
- child = mListView.getChildAt(i++);
- if (child == null) {
- break;
- }
- top = child.getTop();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "child at " + (i-1) + " has top " + top);
- }
- } while (top < 0);
-
- // Compute the first and last position visible
- int firstPosition;
- if (child != null) {
- firstPosition = mListView.getPositionForView(child);
- } else {
- firstPosition = 0;
- }
- int lastPosition = firstPosition + mNumWeeks - 1;
- if (top > BOTTOM_BUFFER) {
- lastPosition--;
- }
-
- if (setSelected) {
- mAdapter.setSelectedDay(mSelectedDay);
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "GoTo position " + position);
- }
- // Check if the selected day is now outside of our visible range
- // and if so scroll to the month that contains it
- if (position < firstPosition || position > lastPosition || forceScroll) {
- mFirstDayOfMonth.set(mTempTime);
- mFirstDayOfMonth.monthDay = 1;
- millis = mFirstDayOfMonth.normalize(true);
- setMonthDisplayed(mFirstDayOfMonth, true);
- position = Utils.getWeeksSinceEpochFromJulianDay(
- Time.getJulianDay(millis, mFirstDayOfMonth.gmtoff), mFirstDayOfWeek);
-
- mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
- if (animate) {
- mListView.smoothScrollToPositionFromTop(
- position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
- return true;
- } else {
- mListView.setSelectionFromTop(position, LIST_TOP_OFFSET);
- // Perform any after scroll operations that are needed
- onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
- }
- } else if (setSelected) {
- // Otherwise just set the selection
- setMonthDisplayed(mSelectedDay, true);
- }
- return false;
- }
-
- /**
- * Updates the title and selected month if the view has moved to a new
- * month.
- */
- @Override
- public void onScroll(
- AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- SimpleWeekView child = (SimpleWeekView)view.getChildAt(0);
- if (child == null) {
- return;
- }
-
- // Figure out where we are
- long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
- mFirstVisibleDay.setJulianDay(child.getFirstJulianDay());
-
- // If we have moved since our last call update the direction
- if (currScroll < mPreviousScrollPosition) {
- mIsScrollingUp = true;
- } else if (currScroll > mPreviousScrollPosition) {
- mIsScrollingUp = false;
- } else {
- return;
- }
-
- mPreviousScrollPosition = currScroll;
- mPreviousScrollState = mCurrentScrollState;
-
- updateMonthHighlight(mListView);
- }
-
- /**
- * Figures out if the month being shown has changed and updates the
- * highlight if needed
- *
- * @param view The ListView containing the weeks
- */
- private void updateMonthHighlight(AbsListView view) {
- SimpleWeekView child = (SimpleWeekView) view.getChildAt(0);
- if (child == null) {
- return;
- }
-
- // Figure out where we are
- int offset = child.getBottom() < WEEK_MIN_VISIBLE_HEIGHT ? 1 : 0;
- // Use some hysteresis for checking which month to highlight. This
- // causes the month to transition when two full weeks of a month are
- // visible.
- child = (SimpleWeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
-
- if (child == null) {
- return;
- }
-
- // Find out which month we're moving into
- int month;
- if (mIsScrollingUp) {
- month = child.getFirstMonth();
- } else {
- month = child.getLastMonth();
- }
-
- // And how it relates to our current highlighted month
- int monthDiff;
- if (mCurrentMonthDisplayed == 11 && month == 0) {
- monthDiff = 1;
- } else if (mCurrentMonthDisplayed == 0 && month == 11) {
- monthDiff = -1;
- } else {
- monthDiff = month - mCurrentMonthDisplayed;
- }
-
- // Only switch months if we're scrolling away from the currently
- // selected month
- if (monthDiff != 0) {
- int julianDay = child.getFirstJulianDay();
- if (mIsScrollingUp) {
- // Takes the start of the week
- } else {
- // Takes the start of the following week
- julianDay += DAYS_PER_WEEK;
- }
- mTempTime.setJulianDay(julianDay);
- setMonthDisplayed(mTempTime, false);
- }
- }
-
- /**
- * Sets the month displayed at the top of this view based on time. Override
- * to add custom events when the title is changed.
- *
- * @param time A day in the new focus month.
- * @param updateHighlight TODO(epastern):
- */
- protected void setMonthDisplayed(Time time, boolean updateHighlight) {
- CharSequence oldMonth = mMonthName.getText();
- mMonthName.setText(Utils.formatMonthYear(mContext, time));
- mMonthName.invalidate();
- if (!TextUtils.equals(oldMonth, mMonthName.getText())) {
- mMonthName.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- mCurrentMonthDisplayed = time.month;
- if (updateHighlight) {
- mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- // use a post to prevent re-entering onScrollStateChanged before it
- // exits
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
-
- protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
-
- protected class ScrollStateRunnable implements Runnable {
- private int mNewState;
-
- /**
- * Sets up the runnable with a short delay in case the scroll state
- * immediately changes again.
- *
- * @param view The list view that changed state
- * @param scrollState The new state it changed to
- */
- public void doScrollStateChange(AbsListView view, int scrollState) {
- mHandler.removeCallbacks(this);
- mNewState = scrollState;
- mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
- }
-
- public void run() {
- mCurrentScrollState = mNewState;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG,
- "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
- }
- // Fix the position after a scroll or a fling ends
- if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
- && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- mPreviousScrollState = mNewState;
- mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
- } else {
- mPreviousScrollState = mNewState;
- }
- }
- }
-}
diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.kt b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
new file mode 100644
index 00000000..01fcbac6
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.app.Activity
+import android.app.ListFragment
+import android.content.Context
+import android.content.res.Resources
+import android.database.DataSetObserver
+import android.os.Bundle
+import android.os.Handler
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+import android.widget.ListView
+import android.widget.TextView
+import java.util.Calendar
+import java.util.HashMap
+import java.util.Locale
+
+/**
+ *
+ *
+ * This displays a titled list of weeks with selectable days. It can be
+ * configured to display the week number, start the week on a given day, show a
+ * reduced number of days, or display an arbitrary number of weeks at a time. By
+ * overriding methods and changing variables this fragment can be customized to
+ * easily display a month selection component in a given style.
+ *
+ */
+open class SimpleDayPickerFragment(initialTime: Long) : ListFragment(), OnScrollListener {
+ protected var WEEK_MIN_VISIBLE_HEIGHT = 12
+ protected var BOTTOM_BUFFER = 20
+ protected var mSaturdayColor = 0
+ protected var mSundayColor = 0
+ protected var mDayNameColor = 0
+
+ // You can override these numbers to get a different appearance
+ @JvmField protected var mNumWeeks = 6
+ @JvmField protected var mShowWeekNumber = false
+ @JvmField protected var mDaysPerWeek = 7
+
+ // These affect the scroll speed and feel
+ protected var mFriction = 1.0f
+ @JvmField protected var mContext: Context? = null
+ @JvmField protected var mHandler: Handler = Handler()
+ protected var mMinimumFlingVelocity = 0f
+
+ // highlighted time
+ @JvmField protected var mSelectedDay: Time = Time()
+ @JvmField protected var mAdapter: SimpleWeeksAdapter? = null
+ @JvmField protected var mListView: ListView? = null
+ @JvmField protected var mDayNamesHeader: ViewGroup? = null
+ @JvmField protected var mDayLabels: Array<String?> = arrayOfNulls(7)
+
+ // disposable variable used for time calculations
+ @JvmField protected var mTempTime: Time = Time()
+
+ // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+ @JvmField protected var mFirstDayOfWeek = 0
+
+ // The first day of the focus month
+ @JvmField protected var mFirstDayOfMonth: Time = Time()
+
+ // The first day that is visible in the view
+ @JvmField protected var mFirstVisibleDay: Time = Time()
+
+ // The name of the month to display
+ protected var mMonthName: TextView? = null
+
+ // The last name announced by accessibility
+ protected var mPrevMonthName: CharSequence? = null
+
+ // which month should be displayed/highlighted [0-11]
+ protected var mCurrentMonthDisplayed = 0
+
+ // used for tracking during a scroll
+ protected var mPreviousScrollPosition: Long = 0
+
+ // used for tracking which direction the view is scrolling
+ protected var mIsScrollingUp = false
+
+ // used for tracking what state listview is in
+ protected var mPreviousScrollState: Int = OnScrollListener.SCROLL_STATE_IDLE
+
+ // used for tracking what state listview is in
+ protected var mCurrentScrollState: Int = OnScrollListener.SCROLL_STATE_IDLE
+
+ // This causes an update of the view at midnight
+ @JvmField protected var mTodayUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ val midnight = Time(mFirstVisibleDay.timezone)
+ midnight.setToNow()
+ val currentMillis: Long = midnight.toMillis(true)
+ midnight.hour = 0
+ midnight.minute = 0
+ midnight.second = 0
+ midnight.monthDay++
+ val millisToMidnight: Long = midnight.normalize(true) - currentMillis
+ mHandler?.postDelayed(this, millisToMidnight)
+ if (mAdapter != null) {
+ mAdapter?.notifyDataSetChanged()
+ }
+ }
+ }
+
+ // This allows us to update our position when a day is tapped
+ @JvmField protected var mObserver: DataSetObserver = object : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ val day: Time? = mAdapter!!.getSelectedDay()
+ if (day!!.year !== mSelectedDay!!.year || day!!.yearDay !== mSelectedDay.yearDay) {
+ goTo(day!!.toMillis(true), true, true, false)
+ }
+ }
+ }
+
+ @Override
+ override fun onAttach(activity: Activity) {
+ super.onAttach(activity)
+ mContext = activity
+ val tz: String = Time.getCurrentTimezone()
+ val viewConfig: ViewConfiguration = ViewConfiguration.get(activity)
+ mMinimumFlingVelocity = (viewConfig.getScaledMinimumFlingVelocity()).toFloat()
+
+ // Ensure we're in the correct time zone
+ mSelectedDay.switchTimezone(tz)
+ mSelectedDay.normalize(true)
+ mFirstDayOfMonth.timezone = tz
+ mFirstDayOfMonth.normalize(true)
+ mFirstVisibleDay.timezone = tz
+ mFirstVisibleDay.normalize(true)
+ mTempTime.timezone = tz
+ val res: Resources = activity.getResources()
+ mSaturdayColor = res.getColor(R.color.month_saturday)
+ mSundayColor = res.getColor(R.color.month_sunday)
+ mDayNameColor = res.getColor(R.color.month_day_names_color)
+
+ // Adjust sizes for screen density
+ if (mScale == 0f) {
+ mScale = activity.getResources().getDisplayMetrics().density
+ if (mScale != 1f) {
+ WEEK_MIN_VISIBLE_HEIGHT *= mScale.toInt()
+ BOTTOM_BUFFER *= mScale.toInt()
+ LIST_TOP_OFFSET *= mScale.toInt()
+ }
+ }
+ setUpAdapter()
+ setListAdapter(mAdapter)
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected open fun setUpAdapter() {
+ val weekParams = HashMap<String?, Int?>()
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, if (mShowWeekNumber) 1 else 0)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
+ Time.getJulianDay(mSelectedDay.toMillis(false), mSelectedDay.gmtoff))
+ if (mAdapter == null) {
+ mAdapter = SimpleWeeksAdapter(getActivity(), weekParams)
+ mAdapter?.registerDataSetObserver(mObserver)
+ } else {
+ mAdapter?.updateParams(weekParams)
+ }
+ // refresh the view with the new parameters
+ mAdapter?.notifyDataSetChanged()
+ }
+
+ @Override
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ @Override
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ setUpListView()
+ setUpHeader()
+ mMonthName = getView()?.findViewById(R.id.month_name) as? TextView
+ val child = mListView?.getChildAt(0) as? SimpleWeekView
+ if (child == null) {
+ return
+ }
+ val julianDay: Int = child.getFirstJulianDay()
+ mFirstVisibleDay.setJulianDay(julianDay)
+ // set the title to the month of the second week
+ mTempTime.setJulianDay(julianDay + DAYS_PER_WEEK)
+ setMonthDisplayed(mTempTime, true)
+ }
+
+ /**
+ * Sets up the strings to be used by the header. Override this method to use
+ * different strings or modify the view params.
+ */
+ protected open fun setUpHeader() {
+ mDayLabels = arrayOfNulls(7)
+ for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+ mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
+ DateUtils.LENGTH_SHORTEST).toUpperCase()
+ }
+ }
+
+ /**
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected fun setUpListView() {
+ // Configure the listview
+ mListView = getListView()
+ // Transparent background on scroll
+ mListView?.setCacheColorHint(0)
+ // No dividers
+ mListView?.setDivider(null)
+ // Items are clickable
+ mListView?.setItemsCanFocus(true)
+ // The thumb gets in the way, so disable it
+ mListView?.setFastScrollEnabled(false)
+ mListView?.setVerticalScrollBarEnabled(false)
+ mListView?.setOnScrollListener(this)
+ mListView?.setFadingEdgeLength(0)
+ // Make the scrolling behavior nicer
+ mListView?.setFriction(ViewConfiguration.getScrollFriction() * mFriction)
+ }
+
+ @Override
+ override fun onResume() {
+ super.onResume()
+ setUpAdapter()
+ doResumeUpdates()
+ }
+
+ @Override
+ override fun onPause() {
+ super.onPause()
+ mHandler.removeCallbacks(mTodayUpdater)
+ }
+
+ @Override
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true))
+ }
+
+ /**
+ * Updates the user preference fields. Override this to use a different
+ * preference space.
+ */
+ protected open fun doResumeUpdates() {
+ // Get default week start based on locale, subtracting one for use with android Time.
+ val cal: Calendar = Calendar.getInstance(Locale.getDefault())
+ mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1
+ mShowWeekNumber = false
+ updateHeader()
+ goTo(mSelectedDay.toMillis(true), false, false, false)
+ mAdapter?.setSelectedDay(mSelectedDay)
+ mTodayUpdater.run()
+ }
+
+ /**
+ * Fixes the day names header to provide correct spacing and updates the
+ * label text. Override this to set up a custom header.
+ */
+ protected fun updateHeader() {
+ var label: TextView = mDayNamesHeader!!.findViewById(R.id.wk_label) as TextView
+ if (mShowWeekNumber) {
+ label.setVisibility(View.VISIBLE)
+ } else {
+ label.setVisibility(View.GONE)
+ }
+ val offset = mFirstDayOfWeek - 1
+ for (i in 1..7) {
+ label = mDayNamesHeader!!.getChildAt(i) as TextView
+ if (i < mDaysPerWeek + 1) {
+ val position = (offset + i) % 7
+ label.setText(mDayLabels[position])
+ label.setVisibility(View.VISIBLE)
+ if (position == Time.SATURDAY) {
+ label.setTextColor(mSaturdayColor)
+ } else if (position == Time.SUNDAY) {
+ label.setTextColor(mSundayColor)
+ } else {
+ label.setTextColor(mDayNameColor)
+ }
+ } else {
+ label.setVisibility(View.GONE)
+ }
+ }
+ mDayNamesHeader?.invalidate()
+ }
+
+ @Override
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val v: View = inflater.inflate(R.layout.month_by_week,
+ container, false)
+ mDayNamesHeader = v.findViewById(R.id.day_names) as ViewGroup
+ return v
+ }
+
+ /**
+ * Returns the UTC millis since epoch representation of the currently
+ * selected time.
+ *
+ * @return
+ */
+ val selectedTime: Long
+ get() = mSelectedDay.toMillis(true)
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param time The time to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ fun goTo(time: Long, animate: Boolean, setSelected: Boolean, forceScroll: Boolean): Boolean {
+ if (time == -1L) {
+ Log.e(TAG, "time is invalid")
+ return false
+ }
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.set(time)
+ mSelectedDay.normalize(true)
+ }
+
+ // If this view isn't returned yet we won't be able to load the lists
+ // current position, so return after setting the selected day.
+ if (!isResumed()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "We're not visible yet")
+ }
+ return false
+ }
+ mTempTime.set(time)
+ var millis: Long = mTempTime.normalize(true)
+ // Get the week we're going to
+ // TODO push Util function into Calendar public api.
+ var position: Int = Utils.getWeeksSinceEpochFromJulianDay(
+ Time.getJulianDay(millis, mTempTime.gmtoff), mFirstDayOfWeek)
+ var child: View?
+ var i = 0
+ var top = 0
+ // Find a child that's completely in the view
+ do {
+ child = mListView?.getChildAt(i++)
+ if (child == null) {
+ break
+ }
+ top = child.getTop()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "child at " + (i - 1) + " has top " + top)
+ }
+ } while (top < 0)
+
+ // Compute the first and last position visible
+ val firstPosition: Int
+ firstPosition = if (child != null) {
+ mListView!!.getPositionForView(child)
+ } else {
+ 0
+ }
+ var lastPosition = firstPosition + mNumWeeks - 1
+ if (top > BOTTOM_BUFFER) {
+ lastPosition--
+ }
+ if (setSelected) {
+ mAdapter?.setSelectedDay(mSelectedDay)
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GoTo position $position")
+ }
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position < firstPosition || position > lastPosition || forceScroll) {
+ mFirstDayOfMonth.set(mTempTime)
+ mFirstDayOfMonth.monthDay = 1
+ millis = mFirstDayOfMonth.normalize(true)
+ setMonthDisplayed(mFirstDayOfMonth, true)
+ position = Utils.getWeeksSinceEpochFromJulianDay(
+ Time.getJulianDay(millis, mFirstDayOfMonth.gmtoff), mFirstDayOfWeek)
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING
+ if (animate) {
+ mListView?.smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION)
+ return true
+ } else {
+ mListView?.setSelectionFromTop(position, LIST_TOP_OFFSET)
+ // Perform any after scroll operations that are needed
+ onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE)
+ }
+ } else if (setSelected) {
+ // Otherwise just set the selection
+ setMonthDisplayed(mSelectedDay, true)
+ }
+ return false
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ override fun onScroll(
+ view: AbsListView,
+ firstVisibleItem: Int,
+ visibleItemCount: Int,
+ totalItemCount: Int
+ ) {
+ val child = view.getChildAt(0) as? SimpleWeekView
+ if (child == null) {
+ return
+ }
+
+ // Figure out where we are
+ val currScroll: Long = (view.getFirstVisiblePosition() * child.getHeight() -
+ child.getBottom()).toLong()
+ mFirstVisibleDay.setJulianDay(child.getFirstJulianDay())
+
+ // If we have moved since our last call update the direction
+ mIsScrollingUp = if (currScroll < mPreviousScrollPosition) {
+ true
+ } else if (currScroll > mPreviousScrollPosition) {
+ false
+ } else {
+ return
+ }
+ mPreviousScrollPosition = currScroll
+ mPreviousScrollState = mCurrentScrollState
+ updateMonthHighlight(mListView as? AbsListView)
+ }
+
+ /**
+ * Figures out if the month being shown has changed and updates the
+ * highlight if needed
+ *
+ * @param view The ListView containing the weeks
+ */
+ private fun updateMonthHighlight(view: AbsListView?) {
+ var child = view?.getChildAt(0) as? SimpleWeekView
+ if (child == null) {
+ return
+ }
+
+ // Figure out where we are
+ val offset = if (child?.getBottom() < WEEK_MIN_VISIBLE_HEIGHT) 1 else 0
+ // Use some hysteresis for checking which month to highlight. This
+ // causes the month to transition when two full weeks of a month are
+ // visible.
+ child = view?.getChildAt(SCROLL_HYST_WEEKS + offset) as? SimpleWeekView
+ if (child == null) {
+ return
+ }
+
+ // Find out which month we're moving into
+ val month: Int
+ month = if (mIsScrollingUp) {
+ child?.getFirstMonth()
+ } else {
+ child?.getLastMonth()
+ }
+
+ // And how it relates to our current highlighted month
+ val monthDiff: Int
+ monthDiff = if (mCurrentMonthDisplayed == 11 && month == 0) {
+ 1
+ } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+ -1
+ } else {
+ month - mCurrentMonthDisplayed
+ }
+
+ // Only switch months if we're scrolling away from the currently
+ // selected month
+ if (monthDiff != 0) {
+ var julianDay: Int = child.getFirstJulianDay()
+ if (mIsScrollingUp) {
+ // Takes the start of the week
+ } else {
+ // Takes the start of the following week
+ julianDay += DAYS_PER_WEEK
+ }
+ mTempTime.setJulianDay(julianDay)
+ setMonthDisplayed(mTempTime, false)
+ }
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ *
+ * @param time A day in the new focus month.
+ * @param updateHighlight TODO(epastern):
+ */
+ protected open fun setMonthDisplayed(time: Time, updateHighlight: Boolean) {
+ val oldMonth: CharSequence = mMonthName!!.getText()
+ mMonthName?.setText(Utils.formatMonthYear(mContext, time))
+ mMonthName?.invalidate()
+ if (!TextUtils.equals(oldMonth, mMonthName?.getText())) {
+ mMonthName?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ mCurrentMonthDisplayed = time.month
+ if (updateHighlight) {
+ mAdapter?.updateFocusMonth(mCurrentMonthDisplayed)
+ }
+ }
+
+ @Override
+ override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState)
+ }
+
+ @JvmField protected var mScrollStateChangedRunnable: ScrollStateRunnable = ScrollStateRunnable()
+
+ protected inner class ScrollStateRunnable : Runnable {
+ private var mNewState = 0
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ fun doScrollStateChange(view: AbsListView?, scrollState: Int) {
+ mHandler.removeCallbacks(this)
+ mNewState = scrollState
+ mHandler.postDelayed(this, SCROLL_CHANGE_DELAY.toLong())
+ }
+
+ override fun run() {
+ mCurrentScrollState = mNewState
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: $mNewState old state: $mPreviousScrollState")
+ }
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE &&
+ mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mPreviousScrollState = mNewState
+ mAdapter?.updateFocusMonth(mCurrentMonthDisplayed)
+ } else {
+ mPreviousScrollState = mNewState
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "MonthFragment"
+ private const val KEY_CURRENT_TIME = "current_time"
+
+ // Affects when the month selection will change while scrolling up
+ protected const val SCROLL_HYST_WEEKS = 2
+
+ // How long the GoTo fling animation should last
+ @JvmStatic protected val GOTO_SCROLL_DURATION = 500
+
+ // How long to wait after receiving an onScrollStateChanged notification
+ // before acting on it
+ protected const val SCROLL_CHANGE_DELAY = 40
+
+ // The number of days to display in each week
+ const val DAYS_PER_WEEK = 7
+
+ // The size of the month name displayed above the week list
+ protected const val MINI_MONTH_NAME_TEXT_SIZE = 18
+ var LIST_TOP_OFFSET = -1 // so that the top line will be under the separator
+ private var mScale = 0f
+ }
+
+ init {
+ goTo(initialTime, false, true, true)
+ mHandler = Handler()
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeekView.java b/src/com/android/calendar/month/SimpleWeekView.java
deleted file mode 100644
index 4d0c09f4..00000000
--- a/src/com/android/calendar/month/SimpleWeekView.java
+++ /dev/null
@@ -1,551 +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.month;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import java.security.InvalidParameterException;
-import java.util.HashMap;
-
-/**
- * <p>
- * This is a dynamic view for drawing a single week. It can be configured to
- * display the week number, start the week on a given day, or show a reduced
- * number of days. It is intended for use as a single view within a ListView.
- * See {@link SimpleWeeksAdapter} for usage.
- * </p>
- */
-public class SimpleWeekView extends View {
- private static final String TAG = "MonthView";
-
- /**
- * These params can be passed into the view to control how it appears.
- * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
- * values are unlikely to fit most layouts correctly.
- */
- /**
- * This sets the height of this week in pixels
- */
- public static final String VIEW_PARAMS_HEIGHT = "height";
- /**
- * This specifies the position (or weeks since the epoch) of this week,
- * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
- */
- public static final String VIEW_PARAMS_WEEK = "week";
- /**
- * This sets one of the days in this view as selected {@link Time#SUNDAY}
- * through {@link Time#SATURDAY}.
- */
- public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
- /**
- * Which day the week should start on. {@link Time#SUNDAY} through
- * {@link Time#SATURDAY}.
- */
- public static final String VIEW_PARAMS_WEEK_START = "week_start";
- /**
- * How many days to display at a time. Days will be displayed starting with
- * {@link #mWeekStart}.
- */
- public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
- /**
- * Which month is currently in focus, as defined by {@link Time#month}
- * [0-11].
- */
- public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
- /**
- * If this month should display week numbers. false if 0, true otherwise.
- */
- public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
-
- protected static int DEFAULT_HEIGHT = 32;
- protected static int MIN_HEIGHT = 10;
- protected static final int DEFAULT_SELECTED_DAY = -1;
- protected static final int DEFAULT_WEEK_START = Time.SUNDAY;
- protected static final int DEFAULT_NUM_DAYS = 7;
- protected static final int DEFAULT_SHOW_WK_NUM = 0;
- protected static final int DEFAULT_FOCUS_MONTH = -1;
-
- protected static int DAY_SEPARATOR_WIDTH = 1;
-
- protected static int MINI_DAY_NUMBER_TEXT_SIZE = 14;
- protected static int MINI_WK_NUMBER_TEXT_SIZE = 12;
- protected static int MINI_TODAY_NUMBER_TEXT_SIZE = 18;
- protected static int MINI_TODAY_OUTLINE_WIDTH = 2;
- protected static int WEEK_NUM_MARGIN_BOTTOM = 4;
-
- // used for scaling to the device density
- protected static float mScale = 0;
-
- // affects the padding on the sides of this view
- protected int mPadding = 0;
-
- protected Rect r = new Rect();
- protected Paint p = new Paint();
- protected Paint mMonthNumPaint;
- protected Drawable mSelectedDayLine;
-
- // Cache the number strings so we don't have to recompute them each time
- protected String[] mDayNumbers;
- // Quick lookup for checking which days are in the focus month
- protected boolean[] mFocusDay;
- // Quick lookup for checking which days are in an odd month (to set a different background)
- protected boolean[] mOddMonth;
- // The Julian day of the first day displayed by this item
- protected int mFirstJulianDay = -1;
- // The month of the first day in this week
- protected int mFirstMonth = -1;
- // The month of the last day in this week
- protected int mLastMonth = -1;
- // The position of this week, equivalent to weeks since the week of Jan 1st,
- // 1970
- protected int mWeek = -1;
- // Quick reference to the width of this view, matches parent
- protected int mWidth;
- // The height this view should draw at in pixels, set by height param
- protected int mHeight = DEFAULT_HEIGHT;
- // Whether the week number should be shown
- protected boolean mShowWeekNum = false;
- // If this view contains the selected day
- protected boolean mHasSelectedDay = false;
- // If this view contains the today
- protected boolean mHasToday = false;
- // Which day is selected [0-6] or -1 if no day is selected
- protected int mSelectedDay = DEFAULT_SELECTED_DAY;
- // Which day is today [0-6] or -1 if no day is today
- protected int mToday = DEFAULT_SELECTED_DAY;
- // Which day of the week to start on [0-6]
- protected int mWeekStart = DEFAULT_WEEK_START;
- // How many days to display
- protected int mNumDays = DEFAULT_NUM_DAYS;
- // The number of days + a spot for week number if it is displayed
- protected int mNumCells = mNumDays;
- // The left edge of the selected day
- protected int mSelectedLeft = -1;
- // The right edge of the selected day
- protected int mSelectedRight = -1;
- // The timezone to display times/dates in (used for determining when Today
- // is)
- protected String mTimeZone = Time.getCurrentTimezone();
-
- protected int mBGColor;
- protected int mSelectedWeekBGColor;
- protected int mFocusMonthColor;
- protected int mOtherMonthColor;
- protected int mDaySeparatorColor;
- protected int mTodayOutlineColor;
- protected int mWeekNumColor;
-
- public SimpleWeekView(Context context) {
- super(context);
-
- Resources res = context.getResources();
-
- mBGColor = res.getColor(R.color.month_bgcolor);
- mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor);
- mFocusMonthColor = res.getColor(R.color.month_mini_day_number);
- mOtherMonthColor = res.getColor(R.color.month_other_month_day_number);
- mDaySeparatorColor = res.getColor(R.color.month_grid_lines);
- mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color);
- mWeekNumColor = res.getColor(R.color.month_week_num_color);
- mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light);
-
- if (mScale == 0) {
- mScale = context.getResources().getDisplayMetrics().density;
- if (mScale != 1) {
- DEFAULT_HEIGHT *= mScale;
- MIN_HEIGHT *= mScale;
- MINI_DAY_NUMBER_TEXT_SIZE *= mScale;
- MINI_TODAY_NUMBER_TEXT_SIZE *= mScale;
- MINI_TODAY_OUTLINE_WIDTH *= mScale;
- WEEK_NUM_MARGIN_BOTTOM *= mScale;
- DAY_SEPARATOR_WIDTH *= mScale;
- MINI_WK_NUMBER_TEXT_SIZE *= mScale;
- }
- }
-
- // Sets up any standard paints that will be used
- initView();
- }
-
- /**
- * Sets all the parameters for displaying this week. The only required
- * parameter is the week number. Other parameters have a default value and
- * will only update if a new value is included, except for focus month,
- * which will always default to no focus month if no value is passed in. See
- * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
- *
- * @param params A map of the new parameters, see
- * {@link #VIEW_PARAMS_HEIGHT}
- * @param tz The time zone this view should reference times in
- */
- public void setWeekParams(HashMap<String, Integer> params, String tz) {
- if (!params.containsKey(VIEW_PARAMS_WEEK)) {
- throw new InvalidParameterException("You must specify the week number for this view");
- }
- setTag(params);
- mTimeZone = tz;
- // We keep the current value for any params not present
- if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
- mHeight = params.get(VIEW_PARAMS_HEIGHT);
- if (mHeight < MIN_HEIGHT) {
- mHeight = MIN_HEIGHT;
- }
- }
- if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
- mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
- }
- mHasSelectedDay = mSelectedDay != -1;
- if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
- mNumDays = params.get(VIEW_PARAMS_NUM_DAYS);
- }
- if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
- if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
- mShowWeekNum = true;
- } else {
- mShowWeekNum = false;
- }
- }
- mNumCells = mShowWeekNum ? mNumDays + 1 : mNumDays;
-
- // Allocate space for caching the day numbers and focus values
- mDayNumbers = new String[mNumCells];
- mFocusDay = new boolean[mNumCells];
- mOddMonth = new boolean[mNumCells];
- mWeek = params.get(VIEW_PARAMS_WEEK);
- int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek);
- Time time = new Time(tz);
- time.setJulianDay(julianMonday);
-
- // If we're showing the week number calculate it based on Monday
- int i = 0;
- if (mShowWeekNum) {
- mDayNumbers[0] = Integer.toString(time.getWeekNumber());
- i++;
- }
-
- if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
- mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
- }
-
- // Now adjust our starting day based on the start day of the week
- // If the week is set to start on a Saturday the first week will be
- // Dec 27th 1969 -Jan 2nd, 1970
- if (time.weekDay != mWeekStart) {
- int diff = time.weekDay - mWeekStart;
- if (diff < 0) {
- diff += 7;
- }
- time.monthDay -= diff;
- time.normalize(true);
- }
-
- mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff);
- mFirstMonth = time.month;
-
- // Figure out what day today is
- Time today = new Time(tz);
- today.setToNow();
- mHasToday = false;
- mToday = -1;
-
- int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? params.get(
- VIEW_PARAMS_FOCUS_MONTH)
- : DEFAULT_FOCUS_MONTH;
-
- for (; i < mNumCells; i++) {
- if (time.monthDay == 1) {
- mFirstMonth = time.month;
- }
- mOddMonth [i] = (time.month %2) == 1;
- if (time.month == focusMonth) {
- mFocusDay[i] = true;
- } else {
- mFocusDay[i] = false;
- }
- if (time.year == today.year && time.yearDay == today.yearDay) {
- mHasToday = true;
- mToday = i;
- }
- mDayNumbers[i] = Integer.toString(time.monthDay++);
- time.normalize(true);
- }
- // We do one extra add at the end of the loop, if that pushed us to a
- // new month undo it
- if (time.monthDay == 1) {
- time.monthDay--;
- time.normalize(true);
- }
- mLastMonth = time.month;
-
- updateSelectionPositions();
- }
-
- /**
- * Sets up the text and style properties for painting. Override this if you
- * want to use a different paint.
- */
- protected void initView() {
- p.setFakeBoldText(false);
- p.setAntiAlias(true);
- p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
- p.setStyle(Style.FILL);
-
- mMonthNumPaint = new Paint();
- mMonthNumPaint.setFakeBoldText(true);
- mMonthNumPaint.setAntiAlias(true);
- mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
- mMonthNumPaint.setColor(mFocusMonthColor);
- mMonthNumPaint.setStyle(Style.FILL);
- mMonthNumPaint.setTextAlign(Align.CENTER);
- }
-
- /**
- * Returns the month of the first day in this week
- *
- * @return The month the first day of this view is in
- */
- public int getFirstMonth() {
- return mFirstMonth;
- }
-
- /**
- * Returns the month of the last day in this week
- *
- * @return The month the last day of this view is in
- */
- public int getLastMonth() {
- return mLastMonth;
- }
-
- /**
- * Returns the julian day of the first day in this view.
- *
- * @return The julian day of the first day in the view.
- */
- public int getFirstJulianDay() {
- return mFirstJulianDay;
- }
-
- /**
- * Calculates the day that the given x position is in, accounting for week
- * number. Returns a Time referencing that day or null if
- *
- * @param x The x position of the touch event
- * @return A time object for the tapped day or null if the position wasn't
- * in a day
- */
- public Time getDayFromLocation(float x) {
- int dayStart = mShowWeekNum ? (mWidth - mPadding * 2) / mNumCells + mPadding : mPadding;
- if (x < dayStart || x > mWidth - mPadding) {
- return null;
- }
- // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
- int day = mFirstJulianDay + dayPosition;
-
- Time time = new Time(mTimeZone);
- if (mWeek == 0) {
- // This week is weird...
- if (day < Time.EPOCH_JULIAN_DAY) {
- day++;
- } else if (day == Time.EPOCH_JULIAN_DAY) {
- time.set(1, 0, 1970);
- time.normalize(true);
- return time;
- }
- }
-
- time.setJulianDay(day);
- return time;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNums(canvas);
- drawDaySeparators(canvas);
- }
-
- /**
- * This draws the selection highlight if a day is selected in this week.
- * Override this method if you wish to have a different background drawn.
- *
- * @param canvas The canvas to draw on
- */
- protected void drawBackground(Canvas canvas) {
- if (mHasSelectedDay) {
- p.setColor(mSelectedWeekBGColor);
- p.setStyle(Style.FILL);
- } else {
- return;
- }
- r.top = 1;
- r.bottom = mHeight - 1;
- r.left = mPadding;
- r.right = mSelectedLeft;
- canvas.drawRect(r, p);
- r.left = mSelectedRight;
- r.right = mWidth - mPadding;
- canvas.drawRect(r, p);
- }
-
- /**
- * Draws the week and month day numbers for this week. Override this method
- * if you need different placement.
- *
- * @param canvas The canvas to draw on
- */
- protected void drawWeekNums(Canvas canvas) {
- int y = ((mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH;
- int nDays = mNumCells;
-
- int i = 0;
- int divisor = 2 * nDays;
- if (mShowWeekNum) {
- p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE);
- p.setStyle(Style.FILL);
- p.setTextAlign(Align.CENTER);
- p.setAntiAlias(true);
- p.setColor(mWeekNumColor);
- int x = (mWidth - mPadding * 2) / divisor + mPadding;
- canvas.drawText(mDayNumbers[0], x, y, p);
- i++;
- }
-
- boolean isFocusMonth = mFocusDay[i];
- mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
- mMonthNumPaint.setFakeBoldText(false);
- for (; i < nDays; i++) {
- if (mFocusDay[i] != isFocusMonth) {
- isFocusMonth = mFocusDay[i];
- mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
- }
- if (mHasToday && mToday == i) {
- mMonthNumPaint.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE);
- mMonthNumPaint.setFakeBoldText(true);
- }
- int x = (2 * i + 1) * (mWidth - mPadding * 2) / (divisor) + mPadding;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
- if (mHasToday && mToday == i) {
- mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
- mMonthNumPaint.setFakeBoldText(false);
- }
- }
- }
-
- /**
- * Draws a horizontal line for separating the weeks. Override this method if
- * you want custom separators.
- *
- * @param canvas The canvas to draw on
- */
- protected void drawDaySeparators(Canvas canvas) {
- if (mHasSelectedDay) {
- r.top = 1;
- r.bottom = mHeight - 1;
- r.left = mSelectedLeft + 1;
- r.right = mSelectedRight - 1;
- p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH);
- p.setStyle(Style.STROKE);
- p.setColor(mTodayOutlineColor);
- canvas.drawRect(r, p);
- }
- if (mShowWeekNum) {
- p.setColor(mDaySeparatorColor);
- p.setStrokeWidth(DAY_SEPARATOR_WIDTH);
-
- int x = (mWidth - mPadding * 2) / mNumCells + mPadding;
- canvas.drawLine(x, 0, x, mHeight, p);
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mWidth = w;
- updateSelectionPositions();
- }
-
- /**
- * This calculates the positions for the selected day lines.
- */
- protected void updateSelectionPositions() {
- if (mHasSelectedDay) {
- int selectedPosition = mSelectedDay - mWeekStart;
- if (selectedPosition < 0) {
- selectedPosition += 7;
- }
- if (mShowWeekNum) {
- selectedPosition++;
- }
- mSelectedLeft = selectedPosition * (mWidth - mPadding * 2) / mNumCells
- + mPadding;
- mSelectedRight = (selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells
- + mPadding;
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- Context context = getContext();
- // only send accessibility events if accessibility and exploration are
- // on.
- AccessibilityManager am = (AccessibilityManager) context
- .getSystemService(Service.ACCESSIBILITY_SERVICE);
- if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
- return super.onHoverEvent(event);
- }
- if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
- Time hover = getDayFromLocation(event.getX());
- if (hover != null
- && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) {
- Long millis = hover.toMillis(true);
- String date = Utils.formatDateRange(context, millis, millis,
- DateUtils.FORMAT_SHOW_DATE);
- AccessibilityEvent accessEvent =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
- accessEvent.getText().add(date);
- sendAccessibilityEventUnchecked(accessEvent);
- mLastHoverTime = hover;
- }
- }
- return true;
- }
-
- Time mLastHoverTime = null;
-} \ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeekView.kt b/src/com/android/calendar/month/SimpleWeekView.kt
new file mode 100644
index 00000000..4d1298d4
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeekView.kt
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.app.Service
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.view.MotionEvent
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import java.security.InvalidParameterException
+import java.util.HashMap
+
+/**
+ *
+ *
+ * This is a dynamic view for drawing a single week. It can be configured to
+ * display the week number, start the week on a given day, or show a reduced
+ * number of days. It is intended for use as a single view within a ListView.
+ * See [SimpleWeeksAdapter] for usage.
+ *
+ */
+open class SimpleWeekView(context: Context) : View(context) {
+ // affects the padding on the sides of this view
+ @JvmField protected var mPadding = 0
+ @JvmField protected var r: Rect = Rect()
+ @JvmField protected var p: Paint = Paint()
+ @JvmField protected var mMonthNumPaint: Paint = Paint()
+ @JvmField protected var mSelectedDayLine: Drawable
+
+ // Cache the number strings so we don't have to recompute them each time
+ @JvmField protected var mDayNumbers: Array<String?>? = null
+
+ // How many days to display
+ @JvmField protected var mNumDays = DEFAULT_NUM_DAYS
+
+ // The number of days + a spot for week number if it is displayed
+ @JvmField protected var mNumCells = mNumDays
+
+ // Quick lookup for checking which days are in the focus month
+ @JvmField protected var mFocusDay: BooleanArray = BooleanArray(mNumCells)
+
+ // Quick lookup for checking which days are in an odd month (to set a different background)
+ @JvmField protected var mOddMonth: BooleanArray = BooleanArray(mNumCells)
+
+ // The Julian day of the first day displayed by this item
+ @JvmField protected var mFirstJulianDay = -1
+
+ // The month of the first day in this week
+ @JvmField protected var firstMonth = -1
+
+ // The month of the last day in this week
+ @JvmField protected var lastMonth = -1
+
+ // The position of this week, equivalent to weeks since the week of Jan 1st,
+ // 1970
+ @JvmField var mWeek = -1
+
+ // Quick reference to the width of this view, matches parent
+ @JvmField protected var mWidth = 0
+
+ // The height this view should draw at in pixels, set by height param
+ @JvmField protected var mHeight = DEFAULT_HEIGHT
+
+ // Whether the week number should be shown
+ @JvmField protected var mShowWeekNum = false
+
+ // If this view contains the selected day
+ @JvmField protected var mHasSelectedDay = false
+
+ // If this view contains the today
+ open protected var mHasToday = false
+
+ // Which day is selected [0-6] or -1 if no day is selected
+ @JvmField protected var mSelectedDay = DEFAULT_SELECTED_DAY
+
+ // Which day is today [0-6] or -1 if no day is today
+ @JvmField protected var mToday: Int = DEFAULT_SELECTED_DAY
+
+ // Which day of the week to start on [0-6]
+ @JvmField protected var mWeekStart = DEFAULT_WEEK_START
+
+ // The left edge of the selected day
+ @JvmField protected var mSelectedLeft = -1
+
+ // The right edge of the selected day
+ @JvmField protected var mSelectedRight = -1
+
+ // The timezone to display times/dates in (used for determining when Today
+ // is)
+ @JvmField protected var mTimeZone: String = Time.getCurrentTimezone()
+ @JvmField protected var mBGColor: Int
+ @JvmField protected var mSelectedWeekBGColor: Int
+ @JvmField protected var mFocusMonthColor: Int
+ @JvmField protected var mOtherMonthColor: Int
+ @JvmField protected var mDaySeparatorColor: Int
+ @JvmField protected var mTodayOutlineColor: Int
+ @JvmField protected var mWeekNumColor: Int
+
+ /**
+ * Sets all the parameters for displaying this week. The only required
+ * parameter is the week number. Other parameters have a default value and
+ * will only update if a new value is included, except for focus month,
+ * which will always default to no focus month if no value is passed in. See
+ * [.VIEW_PARAMS_HEIGHT] for more info on parameters.
+ *
+ * @param params A map of the new parameters, see
+ * [.VIEW_PARAMS_HEIGHT]
+ * @param tz The time zone this view should reference times in
+ */
+ open fun setWeekParams(params: HashMap<String?, Int?>, tz: String) {
+ if (!params.containsKey(VIEW_PARAMS_WEEK)) {
+ throw InvalidParameterException("You must specify the week number for this view")
+ }
+ setTag(params)
+ mTimeZone = tz
+ // We keep the current value for any params not present
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mHeight = (params.get(VIEW_PARAMS_HEIGHT))!!.toInt()
+ if (mHeight < MIN_HEIGHT) {
+ mHeight = MIN_HEIGHT
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+ mSelectedDay = (params.get(VIEW_PARAMS_SELECTED_DAY))!!.toInt()
+ }
+ mHasSelectedDay = mSelectedDay != -1
+ if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
+ mNumDays = (params.get(VIEW_PARAMS_NUM_DAYS))!!.toInt()
+ }
+ if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
+ mShowWeekNum =
+ if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
+ true
+ } else {
+ false
+ }
+ }
+ mNumCells = if (mShowWeekNum) mNumDays + 1 else mNumDays
+
+ // Allocate space for caching the day numbers and focus values
+ mDayNumbers = arrayOfNulls(mNumCells)
+ mFocusDay = BooleanArray(mNumCells)
+ mOddMonth = BooleanArray(mNumCells)
+ mWeek = (params.get(VIEW_PARAMS_WEEK))!!.toInt()
+ val julianMonday: Int = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek)
+ val time = Time(tz)
+ time.setJulianDay(julianMonday)
+
+ // If we're showing the week number calculate it based on Monday
+ var i = 0
+ if (mShowWeekNum) {
+ mDayNumbers!![0] = Integer.toString(time.getWeekNumber())
+ i++
+ }
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = (params.get(VIEW_PARAMS_WEEK_START))!!.toInt()
+ }
+
+ // Now adjust our starting day based on the start day of the week
+ // If the week is set to start on a Saturday the first week will be
+ // Dec 27th 1969 -Jan 2nd, 1970
+ if (time.weekDay !== mWeekStart) {
+ var diff: Int = time.weekDay - mWeekStart
+ if (diff < 0) {
+ diff += 7
+ }
+ time.monthDay -= diff
+ time.normalize(true)
+ }
+ mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff)
+ firstMonth = time.month
+
+ // Figure out what day today is
+ val today = Time(tz)
+ today.setToNow()
+ mHasToday = false
+ mToday = -1
+ val focusMonth = if (params.containsKey(VIEW_PARAMS_FOCUS_MONTH)) params.get(
+ VIEW_PARAMS_FOCUS_MONTH
+ ) else DEFAULT_FOCUS_MONTH
+ while (i < mNumCells) {
+ if (time.monthDay === 1) {
+ firstMonth = time.month
+ }
+ mOddMonth!![i] = time.month % 2 === 1
+ if (time.month === focusMonth) {
+ mFocusDay!![i] = true
+ } else {
+ mFocusDay!![i] = false
+ }
+ if (time.year === today.year && time.yearDay === today.yearDay) {
+ mHasToday = true
+ mToday = i
+ }
+ mDayNumbers!![i] = Integer.toString(time.monthDay++)
+ time.normalize(true)
+ i++
+ }
+ // We do one extra add at the end of the loop, if that pushed us to a
+ // new month undo it
+ if (time.monthDay === 1) {
+ time.monthDay--
+ time.normalize(true)
+ }
+ lastMonth = time.month
+ updateSelectionPositions()
+ }
+
+ /**
+ * Sets up the text and style properties for painting. Override this if you
+ * want to use a different paint.
+ */
+ protected open fun initView() {
+ p.setFakeBoldText(false)
+ p.setAntiAlias(true)
+ p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+ p.setStyle(Style.FILL)
+ mMonthNumPaint = Paint()
+ mMonthNumPaint?.setFakeBoldText(true)
+ mMonthNumPaint?.setAntiAlias(true)
+ mMonthNumPaint?.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+ mMonthNumPaint?.setColor(mFocusMonthColor)
+ mMonthNumPaint?.setStyle(Style.FILL)
+ mMonthNumPaint?.setTextAlign(Align.CENTER)
+ }
+
+ /**
+ * Returns the month of the first day in this week
+ *
+ * @return The month the first day of this view is in
+ */
+ fun getFirstMonth(): Int {
+ return firstMonth
+ }
+
+ /**
+ * Returns the month of the last day in this week
+ *
+ * @return The month the last day of this view is in
+ */
+ fun getLastMonth(): Int {
+ return lastMonth
+ }
+
+ /**
+ * Returns the julian day of the first day in this view.
+ *
+ * @return The julian day of the first day in the view.
+ */
+ fun getFirstJulianDay(): Int {
+ return mFirstJulianDay
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number. Returns a Time referencing that day or null if
+ *
+ * @param x The x position of the touch event
+ * @return A time object for the tapped day or null if the position wasn't
+ * in a day
+ */
+ open fun getDayFromLocation(x: Float): Time? {
+ val dayStart =
+ if (mShowWeekNum) (mWidth - mPadding * 2) / mNumCells + mPadding else mPadding
+ if (x < dayStart || x > mWidth - mPadding) {
+ return null
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ val dayPosition = ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)).toInt()
+ var day = mFirstJulianDay + dayPosition
+ val time = Time(mTimeZone)
+ if (mWeek == 0) {
+ // This week is weird...
+ if (day < Time.EPOCH_JULIAN_DAY) {
+ day++
+ } else if (day == Time.EPOCH_JULIAN_DAY) {
+ time.set(1, 0, 1970)
+ time.normalize(true)
+ return time
+ }
+ }
+ time.setJulianDay(day)
+ return time
+ }
+
+ @Override
+ protected override fun onDraw(canvas: Canvas) {
+ drawBackground(canvas)
+ drawWeekNums(canvas)
+ drawDaySeparators(canvas)
+ }
+
+ /**
+ * This draws the selection highlight if a day is selected in this week.
+ * Override this method if you wish to have a different background drawn.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected open fun drawBackground(canvas: Canvas) {
+ if (mHasSelectedDay) {
+ p.setColor(mSelectedWeekBGColor)
+ p.setStyle(Style.FILL)
+ } else {
+ return
+ }
+ r.top = 1
+ r.bottom = mHeight - 1
+ r.left = mPadding
+ r.right = mSelectedLeft
+ canvas.drawRect(r, p)
+ r.left = mSelectedRight
+ r.right = mWidth - mPadding
+ canvas.drawRect(r, p)
+ }
+
+ /**
+ * Draws the week and month day numbers for this week. Override this method
+ * if you need different placement.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected open fun drawWeekNums(canvas: Canvas) {
+ val y = (mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH
+ val nDays = mNumCells
+ var i = 0
+ val divisor = 2 * nDays
+ if (mShowWeekNum) {
+ p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE.toFloat())
+ p.setStyle(Style.FILL)
+ p.setTextAlign(Align.CENTER)
+ p.setAntiAlias(true)
+ p.setColor(mWeekNumColor)
+ val x = (mWidth - mPadding * 2) / divisor + mPadding
+ canvas.drawText(mDayNumbers!![0] as String, x.toFloat(), y.toFloat(), p)
+ i++
+ }
+ var isFocusMonth = mFocusDay!![i]
+ mMonthNumPaint?.setColor(if (isFocusMonth) mFocusMonthColor else mOtherMonthColor)
+ mMonthNumPaint?.setFakeBoldText(false)
+ while (i < nDays) {
+ if (mFocusDay!![i] != isFocusMonth) {
+ isFocusMonth = mFocusDay!![i]
+ mMonthNumPaint?.setColor(if (isFocusMonth) mFocusMonthColor else mOtherMonthColor)
+ }
+ if (mHasToday && mToday == i) {
+ mMonthNumPaint?.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE.toFloat())
+ mMonthNumPaint?.setFakeBoldText(true)
+ }
+ val x = (2 * i + 1) * (mWidth - mPadding * 2) / divisor + mPadding
+ canvas.drawText(mDayNumbers!![i] as String, x.toFloat(), y.toFloat(),
+ mMonthNumPaint as Paint)
+ if (mHasToday && mToday == i) {
+ mMonthNumPaint?.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+ mMonthNumPaint?.setFakeBoldText(false)
+ }
+ i++
+ }
+ }
+
+ /**
+ * Draws a horizontal line for separating the weeks. Override this method if
+ * you want custom separators.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected open fun drawDaySeparators(canvas: Canvas) {
+ if (mHasSelectedDay) {
+ r.top = 1
+ r.bottom = mHeight - 1
+ r.left = mSelectedLeft + 1
+ r.right = mSelectedRight - 1
+ p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH.toFloat())
+ p.setStyle(Style.STROKE)
+ p.setColor(mTodayOutlineColor)
+ canvas.drawRect(r, p)
+ }
+ if (mShowWeekNum) {
+ p.setColor(mDaySeparatorColor)
+ p.setStrokeWidth(DAY_SEPARATOR_WIDTH.toFloat())
+ val x = (mWidth - mPadding * 2) / mNumCells + mPadding
+ canvas.drawLine(x.toFloat(), 0f, x.toFloat(), mHeight.toFloat(), p)
+ }
+ }
+
+ @Override
+ protected override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ mWidth = w
+ updateSelectionPositions()
+ }
+
+ /**
+ * This calculates the positions for the selected day lines.
+ */
+ protected open fun updateSelectionPositions() {
+ if (mHasSelectedDay) {
+ var selectedPosition = mSelectedDay - mWeekStart
+ if (selectedPosition < 0) {
+ selectedPosition += 7
+ }
+ if (mShowWeekNum) {
+ selectedPosition++
+ }
+ mSelectedLeft = (selectedPosition * (mWidth - mPadding * 2) / mNumCells +
+ mPadding)
+ mSelectedRight = ((selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells +
+ mPadding)
+ }
+ }
+
+ @Override
+ protected override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight)
+ }
+
+ @Override
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ val context: Context = getContext()
+ // only send accessibility events if accessibility and exploration are
+ // on.
+ val am: AccessibilityManager = context
+ .getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
+ return super.onHoverEvent(event)
+ }
+ if (event.getAction() !== MotionEvent.ACTION_HOVER_EXIT) {
+ val hover: Time? = getDayFromLocation(event.getX())
+ if (hover != null &&
+ (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) !== 0)
+ ) {
+ val millis: Long = hover.toMillis(true)
+ val date: String? = Utils.formatDateRange(
+ context, millis, millis,
+ DateUtils.FORMAT_SHOW_DATE
+ )
+ val accessEvent: AccessibilityEvent =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
+ accessEvent.getText().add(date)
+ sendAccessibilityEventUnchecked(accessEvent)
+ mLastHoverTime = hover
+ }
+ }
+ return true
+ }
+
+ @JvmField var mLastHoverTime: Time? = null
+
+ companion object {
+ private const val TAG = "MonthView"
+ /**
+ * These params can be passed into the view to control how it appears.
+ * [.VIEW_PARAMS_WEEK] is the only required field, though the default
+ * values are unlikely to fit most layouts correctly.
+ */
+ /**
+ * This sets the height of this week in pixels
+ */
+ const val VIEW_PARAMS_HEIGHT = "height"
+
+ /**
+ * This specifies the position (or weeks since the epoch) of this week,
+ * calculated using [Utils.getWeeksSinceEpochFromJulianDay]
+ */
+ const val VIEW_PARAMS_WEEK = "week"
+
+ /**
+ * This sets one of the days in this view as selected [Time.SUNDAY]
+ * through [Time.SATURDAY].
+ */
+ const val VIEW_PARAMS_SELECTED_DAY = "selected_day"
+
+ /**
+ * Which day the week should start on. [Time.SUNDAY] through
+ * [Time.SATURDAY].
+ */
+ const val VIEW_PARAMS_WEEK_START = "week_start"
+
+ /**
+ * How many days to display at a time. Days will be displayed starting with
+ * [.mWeekStart].
+ */
+ const val VIEW_PARAMS_NUM_DAYS = "num_days"
+
+ /**
+ * Which month is currently in focus, as defined by [Time.month]
+ * [0-11].
+ */
+ const val VIEW_PARAMS_FOCUS_MONTH = "focus_month"
+
+ /**
+ * If this month should display week numbers. false if 0, true otherwise.
+ */
+ const val VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"
+ protected var DEFAULT_HEIGHT = 32
+ protected var MIN_HEIGHT = 10
+ protected const val DEFAULT_SELECTED_DAY = -1
+ protected val DEFAULT_WEEK_START: Int = Time.SUNDAY
+ protected const val DEFAULT_NUM_DAYS = 7
+ protected const val DEFAULT_SHOW_WK_NUM = 0
+ protected const val DEFAULT_FOCUS_MONTH = -1
+ protected var DAY_SEPARATOR_WIDTH = 1
+ protected var MINI_DAY_NUMBER_TEXT_SIZE = 14
+ protected var MINI_WK_NUMBER_TEXT_SIZE = 12
+ protected var MINI_TODAY_NUMBER_TEXT_SIZE = 18
+ protected var MINI_TODAY_OUTLINE_WIDTH = 2
+ protected var WEEK_NUM_MARGIN_BOTTOM = 4
+
+ // used for scaling to the device density
+ @JvmStatic protected var mScale = 0f
+ }
+
+ init {
+ val res: Resources = context.getResources()
+ mBGColor = res.getColor(R.color.month_bgcolor)
+ mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor)
+ mFocusMonthColor = res.getColor(R.color.month_mini_day_number)
+ mOtherMonthColor = res.getColor(R.color.month_other_month_day_number)
+ mDaySeparatorColor = res.getColor(R.color.month_grid_lines)
+ mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color)
+ mWeekNumColor = res.getColor(R.color.month_week_num_color)
+ mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light)
+ if (mScale == 0f) {
+ mScale = context.getResources().getDisplayMetrics().density
+ if (mScale != 1f) {
+ DEFAULT_HEIGHT *= mScale.toInt()
+ MIN_HEIGHT *= mScale.toInt()
+ MINI_DAY_NUMBER_TEXT_SIZE *= mScale.toInt()
+ MINI_TODAY_NUMBER_TEXT_SIZE *= mScale.toInt()
+ MINI_TODAY_OUTLINE_WIDTH *= mScale.toInt()
+ WEEK_NUM_MARGIN_BOTTOM *= mScale.toInt()
+ DAY_SEPARATOR_WIDTH *= mScale.toInt()
+ MINI_WK_NUMBER_TEXT_SIZE *= mScale.toInt()
+ }
+ }
+
+ // Sets up any standard paints that will be used
+ initView()
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeeksAdapter.java b/src/com/android/calendar/month/SimpleWeeksAdapter.java
deleted file mode 100644
index d29b2622..00000000
--- a/src/com/android/calendar/month/SimpleWeeksAdapter.java
+++ /dev/null
@@ -1,302 +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.month;
-
-// TODO Remove calendar imports when the required methods have been
-// refactored into the public api
-import com.android.calendar.CalendarController;
-import com.android.calendar.Utils;
-
-import android.content.Context;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * <p>
- * This is a specialized adapter for creating a list of weeks with selectable
- * days. It can be configured to display the week number, start the week on a
- * given day, show a reduced number of days, or display an arbitrary number of
- * weeks at a time. See {@link SimpleDayPickerFragment} for usage.
- * </p>
- */
-public class SimpleWeeksAdapter extends BaseAdapter implements OnTouchListener {
-
- private static final String TAG = "MonthByWeek";
-
- /**
- * The number of weeks to display at a time.
- */
- public static final String WEEK_PARAMS_NUM_WEEKS = "num_weeks";
- /**
- * Which month should be in focus currently.
- */
- public static final String WEEK_PARAMS_FOCUS_MONTH = "focus_month";
- /**
- * Whether the week number should be shown. Non-zero to show them.
- */
- public static final String WEEK_PARAMS_SHOW_WEEK = "week_numbers";
- /**
- * Which day the week should start on. {@link Time#SUNDAY} through
- * {@link Time#SATURDAY}.
- */
- public static final String WEEK_PARAMS_WEEK_START = "week_start";
- /**
- * The Julian day to highlight as selected.
- */
- public static final String WEEK_PARAMS_JULIAN_DAY = "selected_day";
- /**
- * How many days of the week to display [1-7].
- */
- public static final String WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week";
-
- protected static final int WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK
- - CalendarController.MIN_CALENDAR_WEEK;
- protected static int DEFAULT_NUM_WEEKS = 6;
- protected static int DEFAULT_MONTH_FOCUS = 0;
- protected static int DEFAULT_DAYS_PER_WEEK = 7;
- protected static int DEFAULT_WEEK_HEIGHT = 32;
- protected static int WEEK_7_OVERHANG_HEIGHT = 7;
-
- protected static float mScale = 0;
- protected Context mContext;
- // The day to highlight as selected
- protected Time mSelectedDay;
- // The week since 1970 that the selected day is in
- protected int mSelectedWeek;
- // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
- protected int mFirstDayOfWeek;
- protected boolean mShowWeekNumber = false;
- protected GestureDetector mGestureDetector;
- protected int mNumWeeks = DEFAULT_NUM_WEEKS;
- protected int mDaysPerWeek = DEFAULT_DAYS_PER_WEEK;
- protected int mFocusMonth = DEFAULT_MONTH_FOCUS;
-
- public SimpleWeeksAdapter(Context context, HashMap<String, Integer> params) {
- mContext = context;
-
- // Get default week start based on locale, subtracting one for use with android Time.
- Calendar cal = Calendar.getInstance(Locale.getDefault());
- mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
-
- if (mScale == 0) {
- mScale = context.getResources().getDisplayMetrics().density;
- if (mScale != 1) {
- WEEK_7_OVERHANG_HEIGHT *= mScale;
- }
- }
- init();
- updateParams(params);
- }
-
- /**
- * Set up the gesture detector and selected time
- */
- protected void init() {
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- mSelectedDay = new Time();
- mSelectedDay.setToNow();
- }
-
- /**
- * Parse the parameters and set any necessary fields. See
- * {@link #WEEK_PARAMS_NUM_WEEKS} for parameter details.
- *
- * @param params A list of parameters for this adapter
- */
- public void updateParams(HashMap<String, Integer> params) {
- if (params == null) {
- Log.e(TAG, "WeekParameters are null! Cannot update adapter.");
- return;
- }
- if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
- mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH);
- }
- if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
- mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS);
- }
- if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
- mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) != 0;
- }
- if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
- mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START);
- }
- if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
- int julianDay = params.get(WEEK_PARAMS_JULIAN_DAY);
- mSelectedDay.setJulianDay(julianDay);
- mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek);
- }
- if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
- mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK);
- }
- refresh();
- }
-
- /**
- * Updates the selected day and related parameters.
- *
- * @param selectedTime The time to highlight
- */
- public void setSelectedDay(Time selectedTime) {
- mSelectedDay.set(selectedTime);
- long millis = mSelectedDay.normalize(true);
- mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
- Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
- notifyDataSetChanged();
- }
-
- /**
- * Returns the currently highlighted day
- *
- * @return
- */
- public Time getSelectedDay() {
- return mSelectedDay;
- }
-
- /**
- * updates any config options that may have changed and refreshes the view
- */
- protected void refresh() {
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return WEEK_COUNT;
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- SimpleWeekView v;
- HashMap<String, Integer> drawingParams = null;
- if (convertView != null) {
- v = (SimpleWeekView) convertView;
- // We store the drawing parameters in the view so it can be recycled
- drawingParams = (HashMap<String, Integer>) v.getTag();
- } else {
- v = new SimpleWeekView(mContext);
- // Set up the new view
- LayoutParams params = new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- v.setLayoutParams(params);
- v.setClickable(true);
- v.setOnTouchListener(this);
- }
- if (drawingParams == null) {
- drawingParams = new HashMap<String, Integer>();
- }
- drawingParams.clear();
-
- int selectedDay = -1;
- if (mSelectedWeek == position) {
- selectedDay = mSelectedDay.weekDay;
- }
-
- // pass in all the view parameters
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
- (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
- drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
- v.setWeekParams(drawingParams, mSelectedDay.timezone);
- v.invalidate();
-
- return v;
- }
-
- /**
- * Changes which month is in focus and updates the view.
- *
- * @param month The month to show as in focus [0-11]
- */
- public void updateFocusMonth(int month) {
- mFocusMonth = month;
- notifyDataSetChanged();
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mGestureDetector.onTouchEvent(event)) {
- SimpleWeekView view = (SimpleWeekView) v;
- Time day = ((SimpleWeekView)v).getDayFromLocation(event.getX());
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Touched day at Row=" + view.mWeek + " day=" + day.toString());
- }
- if (day != null) {
- onDayTapped(day);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
- *
- * @param day The day that was tapped
- */
- protected void onDayTapped(Time day) {
- day.hour = mSelectedDay.hour;
- day.minute = mSelectedDay.minute;
- day.second = mSelectedDay.second;
- setSelectedDay(day);
- }
-
-
- /**
- * This is here so we can identify single tap events and set the selected
- * day correctly
- */
- protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
- }
-
- ListView mListView;
-
- public void setListView(ListView lv) {
- mListView = lv;
- }
-}
diff --git a/src/com/android/calendar/month/SimpleWeeksAdapter.kt b/src/com/android/calendar/month/SimpleWeeksAdapter.kt
new file mode 100644
index 00000000..164f05c5
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeeksAdapter.kt
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+// TODO Remove calendar imports when the required methods have been
+// refactored into the public api
+import com.android.calendar.CalendarController
+import com.android.calendar.Utils
+import android.content.Context
+import android.text.format.Time
+import android.util.Log
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewGroup
+import android.widget.AbsListView.LayoutParams
+import android.widget.BaseAdapter
+import android.widget.ListView
+import java.util.Calendar
+import java.util.HashMap
+import java.util.Locale
+
+/**
+ *
+ *
+ * This is a specialized adapter for creating a list of weeks with selectable
+ * days. It can be configured to display the week number, start the week on a
+ * given day, show a reduced number of days, or display an arbitrary number of
+ * weeks at a time. See [SimpleDayPickerFragment] for usage.
+ *
+ */
+open class SimpleWeeksAdapter(context: Context, params: HashMap<String?, Int?>?) : BaseAdapter(),
+ OnTouchListener {
+ protected var mContext: Context
+
+ // The day to highlight as selected
+ protected var mSelectedDay: Time? = null
+
+ // The week since 1970 that the selected day is in
+ protected var mSelectedWeek = 0
+
+ // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+ protected var mFirstDayOfWeek: Int
+ protected var mShowWeekNumber = false
+ protected var mGestureDetector: GestureDetector? = null
+ protected var mNumWeeks = DEFAULT_NUM_WEEKS
+ protected var mDaysPerWeek = DEFAULT_DAYS_PER_WEEK
+ protected var mFocusMonth = DEFAULT_MONTH_FOCUS
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ protected open fun init() {
+ mGestureDetector = GestureDetector(mContext, CalendarGestureListener())
+ mSelectedDay = Time()
+ mSelectedDay?.setToNow()
+ }
+
+ /**
+ * Parse the parameters and set any necessary fields. See
+ * [.WEEK_PARAMS_NUM_WEEKS] for parameter details.
+ *
+ * @param params A list of parameters for this adapter
+ */
+ fun updateParams(params: HashMap<String?, Int?>?) {
+ if (params == null) {
+ Log.e(TAG, "WeekParameters are null! Cannot update adapter.")
+ return
+ }
+ if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
+ // Casting from Int? --> Int
+ mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH) as Int
+ }
+ if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
+ // Casting from Int? --> Int
+ mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS) as Int
+ }
+ if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
+ // Casting from Int? --> Int
+ mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) as Int != 0
+ }
+ if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
+ // Casting from Int? --> Int
+ mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START) as Int
+ }
+ if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
+ // Casting from Int? --> Int
+ val julianDay: Int = params.get(WEEK_PARAMS_JULIAN_DAY) as Int
+ mSelectedDay?.setJulianDay(julianDay)
+ mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek)
+ }
+ if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
+ // Casting from Int? --> Int
+ mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK) as Int
+ }
+ refresh()
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param selectedTime The time to highlight
+ */
+ open fun setSelectedDay(selectedTime: Time?) {
+ mSelectedDay?.set(selectedTime)
+ val millis: Long = mSelectedDay!!.normalize(true)
+ mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
+ Time.getJulianDay(millis, mSelectedDay!!.gmtoff), mFirstDayOfWeek
+ )
+ notifyDataSetChanged()
+ }
+
+ /**
+ * Returns the currently highlighted day
+ *
+ * @return
+ */
+ fun getSelectedDay(): Time? {
+ return mSelectedDay
+ }
+
+ /**
+ * updates any config options that may have changed and refreshes the view
+ */
+ internal open fun refresh() {
+ notifyDataSetChanged()
+ }
+
+ @Override
+ override fun getCount(): Int {
+ return WEEK_COUNT
+ }
+
+ @Override
+ override fun getItem(position: Int): Any? {
+ return null
+ }
+
+ @Override
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val v: SimpleWeekView
+ var drawingParams: HashMap<String?, Int?>? = null
+ if (convertView != null) {
+ v = convertView as SimpleWeekView
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = v.getTag() as HashMap<String?, Int?>
+ } else {
+ v = SimpleWeekView(mContext)
+ // Set up the new view
+ val params = LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
+ )
+ v.setLayoutParams(params)
+ v.setClickable(true)
+ v.setOnTouchListener(this)
+ }
+ if (drawingParams == null) {
+ drawingParams = HashMap<String?, Int?>()
+ }
+ drawingParams.clear()
+ var selectedDay = -1
+ if (mSelectedWeek == position) {
+ selectedDay = mSelectedDay!!.weekDay
+ }
+
+ // pass in all the view parameters
+ drawingParams.put(
+ SimpleWeekView.VIEW_PARAMS_HEIGHT,
+ (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks
+ )
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, if (mShowWeekNumber) 1 else 0)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position)
+ drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth)
+ v.setWeekParams(drawingParams, mSelectedDay!!.timezone)
+ v.invalidate()
+ return v
+ }
+
+ /**
+ * Changes which month is in focus and updates the view.
+ *
+ * @param month The month to show as in focus [0-11]
+ */
+ fun updateFocusMonth(month: Int) {
+ mFocusMonth = month
+ notifyDataSetChanged()
+ }
+
+ @Override
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ if (mGestureDetector!!.onTouchEvent(event)) {
+ val view: SimpleWeekView = v as SimpleWeekView
+ val day: Time? = (v as SimpleWeekView).getDayFromLocation(event.getX())
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Touched day at Row=" + view.mWeek.toString() + " day=" +
+ day?.toString())
+ }
+ if (day != null) {
+ onDayTapped(day)
+ }
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ protected open fun onDayTapped(day: Time) {
+ day.hour = mSelectedDay!!.hour
+ day.minute = mSelectedDay!!.minute
+ day.second = mSelectedDay!!.second
+ setSelectedDay(day)
+ }
+
+ /**
+ * This is here so we can identify single tap events and set the selected
+ * day correctly
+ */
+ protected inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+ @Override
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ return true
+ }
+ }
+
+ var mListView: ListView? = null
+ fun setListView(lv: ListView?) {
+ mListView = lv
+ }
+
+ companion object {
+ private const val TAG = "MonthByWeek"
+
+ /**
+ * The number of weeks to display at a time.
+ */
+ const val WEEK_PARAMS_NUM_WEEKS = "num_weeks"
+
+ /**
+ * Which month should be in focus currently.
+ */
+ const val WEEK_PARAMS_FOCUS_MONTH = "focus_month"
+
+ /**
+ * Whether the week number should be shown. Non-zero to show them.
+ */
+ const val WEEK_PARAMS_SHOW_WEEK = "week_numbers"
+
+ /**
+ * Which day the week should start on. [Time.SUNDAY] through
+ * [Time.SATURDAY].
+ */
+ const val WEEK_PARAMS_WEEK_START = "week_start"
+
+ /**
+ * The Julian day to highlight as selected.
+ */
+ const val WEEK_PARAMS_JULIAN_DAY = "selected_day"
+
+ /**
+ * How many days of the week to display [1-7].
+ */
+ const val WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week"
+ protected const val WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK -
+ CalendarController.MIN_CALENDAR_WEEK
+ protected var DEFAULT_NUM_WEEKS = 6
+ protected var DEFAULT_MONTH_FOCUS = 0
+ protected var DEFAULT_DAYS_PER_WEEK = 7
+ protected var DEFAULT_WEEK_HEIGHT = 32
+ protected var WEEK_7_OVERHANG_HEIGHT = 7
+ protected var mScale = 0f
+ }
+
+ init {
+ mContext = context
+
+ // Get default week start based on locale, subtracting one for use with android Time.
+ val cal: Calendar = Calendar.getInstance(Locale.getDefault())
+ mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1
+ if (mScale == 0f) {
+ mScale = context.getResources().getDisplayMetrics().density
+ if (mScale != 1f) {
+ WEEK_7_OVERHANG_HEIGHT *= mScale.toInt()
+ }
+ }
+ init()
+ updateParams(params)
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.java b/src/com/android/calendar/widget/CalendarAppWidgetModel.java
deleted file mode 100644
index a989e18b..00000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetModel.java
+++ /dev/null
@@ -1,430 +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.widget;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.TimeZone;
-
-class CalendarAppWidgetModel {
- private static final String TAG = CalendarAppWidgetModel.class.getSimpleName();
- private static final boolean LOGD = false;
-
- private String mHomeTZName;
- private boolean mShowTZ;
- /**
- * {@link RowInfo} is a class that represents a single row in the widget. It
- * is actually only a pointer to either a {@link DayInfo} or an
- * {@link EventInfo} instance, since a row in the widget might be either a
- * day header or an event.
- */
- static class RowInfo {
- static final int TYPE_DAY = 0;
- static final int TYPE_MEETING = 1;
-
- /**
- * mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
- */
- final int mType;
-
- /**
- * If mType is TYPE_DAY, then mData is the index into day infos.
- * Otherwise mType is TYPE_MEETING and mData is the index into event
- * infos.
- */
- final int mIndex;
-
- RowInfo(int type, int index) {
- mType = type;
- mIndex = index;
- }
- }
-
- /**
- * {@link EventInfo} is a class that represents an event in the widget. It
- * contains all of the data necessary to display that event, including the
- * properly localized strings and visibility settings.
- */
- static class EventInfo {
- int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
- String when;
- int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
- String where;
- int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
- String title;
- int selfAttendeeStatus;
-
- long id;
- long start;
- long end;
- boolean allDay;
- int color;
-
- public EventInfo() {
- visibWhen = View.GONE;
- visibWhere = View.GONE;
- visibTitle = View.GONE;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("EventInfo [visibTitle=");
- builder.append(visibTitle);
- builder.append(", title=");
- builder.append(title);
- builder.append(", visibWhen=");
- builder.append(visibWhen);
- builder.append(", id=");
- builder.append(id);
- builder.append(", when=");
- builder.append(when);
- builder.append(", visibWhere=");
- builder.append(visibWhere);
- builder.append(", where=");
- builder.append(where);
- builder.append(", color=");
- builder.append(String.format("0x%x", color));
- builder.append(", selfAttendeeStatus=");
- builder.append(selfAttendeeStatus);
- builder.append("]");
- return builder.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + (allDay ? 1231 : 1237);
- result = prime * result + (int) (id ^ (id >>> 32));
- result = prime * result + (int) (end ^ (end >>> 32));
- result = prime * result + (int) (start ^ (start >>> 32));
- result = prime * result + ((title == null) ? 0 : title.hashCode());
- result = prime * result + visibTitle;
- result = prime * result + visibWhen;
- result = prime * result + visibWhere;
- result = prime * result + ((when == null) ? 0 : when.hashCode());
- result = prime * result + ((where == null) ? 0 : where.hashCode());
- result = prime * result + color;
- result = prime * result + selfAttendeeStatus;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- EventInfo other = (EventInfo) obj;
- if (id != other.id)
- return false;
- if (allDay != other.allDay)
- return false;
- if (end != other.end)
- return false;
- if (start != other.start)
- return false;
- if (title == null) {
- if (other.title != null)
- return false;
- } else if (!title.equals(other.title))
- return false;
- if (visibTitle != other.visibTitle)
- return false;
- if (visibWhen != other.visibWhen)
- return false;
- if (visibWhere != other.visibWhere)
- return false;
- if (when == null) {
- if (other.when != null)
- return false;
- } else if (!when.equals(other.when)) {
- return false;
- }
- if (where == null) {
- if (other.where != null)
- return false;
- } else if (!where.equals(other.where)) {
- return false;
- }
- if (color != other.color) {
- return false;
- }
- if (selfAttendeeStatus != other.selfAttendeeStatus) {
- return false;
- }
- return true;
- }
- }
-
- /**
- * {@link DayInfo} is a class that represents a day header in the widget. It
- * contains all of the data necessary to display that day header, including
- * the properly localized string.
- */
- static class DayInfo {
-
- /** The Julian day */
- final int mJulianDay;
-
- /** The string representation of this day header, to be displayed */
- final String mDayLabel;
-
- DayInfo(int julianDay, String label) {
- mJulianDay = julianDay;
- mDayLabel = label;
- }
-
- @Override
- public String toString() {
- return mDayLabel;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode());
- result = prime * result + mJulianDay;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- DayInfo other = (DayInfo) obj;
- if (mDayLabel == null) {
- if (other.mDayLabel != null)
- return false;
- } else if (!mDayLabel.equals(other.mDayLabel))
- return false;
- if (mJulianDay != other.mJulianDay)
- return false;
- return true;
- }
-
- }
-
- final List<RowInfo> mRowInfos;
- final List<EventInfo> mEventInfos;
- final List<DayInfo> mDayInfos;
- final Context mContext;
- final long mNow;
- final int mTodayJulianDay;
- final int mMaxJulianDay;
-
- public CalendarAppWidgetModel(Context context, String timeZone) {
- mNow = System.currentTimeMillis();
- Time time = new Time(timeZone);
- time.setToNow(); // This is needed for gmtoff to be set
- mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff);
- mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1;
- mEventInfos = new ArrayList<EventInfo>(50);
- mRowInfos = new ArrayList<RowInfo>(50);
- mDayInfos = new ArrayList<DayInfo>(8);
- mContext = context;
- }
-
- public void buildFromCursor(Cursor cursor, String timeZone) {
- final Time recycle = new Time(timeZone);
- final ArrayList<LinkedList<RowInfo>> mBuckets =
- new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS);
- for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) {
- mBuckets.add(new LinkedList<RowInfo>());
- }
- recycle.setToNow();
- mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone());
- if (mShowTZ) {
- mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0,
- TimeZone.SHORT);
- }
-
- cursor.moveToPosition(-1);
- String tz = Utils.getTimeZone(mContext, null);
- while (cursor.moveToNext()) {
- final int rowId = cursor.getPosition();
- final long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID);
- final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0;
- long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN);
- long end = cursor.getLong(CalendarAppWidgetService.INDEX_END);
- final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE);
- final String location =
- cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION);
- // we don't compute these ourselves because it seems to produce the
- // wrong endDay for all day events
- final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY);
- final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY);
- final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR);
- final int selfStatus = cursor
- .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS);
-
- // Adjust all-day times into local timezone
- if (allDay) {
- start = Utils.convertAlldayUtcToLocal(recycle, start, tz);
- end = Utils.convertAlldayUtcToLocal(recycle, end, tz);
- }
-
- if (LOGD) {
- Log.d(TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start
- + " end:" + end + " eventId:" + eventId);
- }
-
- // we might get some extra events when querying, in order to
- // deal with all-day events
- if (end < mNow) {
- continue;
- }
-
- int i = mEventInfos.size();
- mEventInfos.add(populateEventInfo(eventId, allDay, start, end, startDay, endDay, title,
- location, color, selfStatus));
- // populate the day buckets that this event falls into
- int from = Math.max(startDay, mTodayJulianDay);
- int to = Math.min(endDay, mMaxJulianDay);
- for (int day = from; day <= to; day++) {
- LinkedList<RowInfo> bucket = mBuckets.get(day - mTodayJulianDay);
- RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i);
- if (allDay) {
- bucket.addFirst(rowInfo);
- } else {
- bucket.add(rowInfo);
- }
- }
- }
-
- int day = mTodayJulianDay;
- int count = 0;
- for (LinkedList<RowInfo> bucket : mBuckets) {
- if (!bucket.isEmpty()) {
- // We don't show day header in today
- if (day != mTodayJulianDay) {
- final DayInfo dayInfo = populateDayInfo(day, recycle);
- // Add the day header
- final int dayIndex = mDayInfos.size();
- mDayInfos.add(dayInfo);
- mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex));
- }
-
- // Add the event row infos
- mRowInfos.addAll(bucket);
- count += bucket.size();
- }
- day++;
- if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
- break;
- }
- }
- }
-
- private EventInfo populateEventInfo(long eventId, boolean allDay, long start, long end,
- int startDay, int endDay, String title, String location, int color, int selfStatus) {
- EventInfo eventInfo = new EventInfo();
-
- // Compute a human-readable string for the start time of the event
- StringBuilder whenString = new StringBuilder();
- int visibWhen;
- int flags = DateUtils.FORMAT_ABBREV_ALL;
- visibWhen = View.VISIBLE;
- if (allDay) {
- flags |= DateUtils.FORMAT_SHOW_DATE;
- whenString.append(Utils.formatDateRange(mContext, start, end, flags));
- } else {
- flags |= DateUtils.FORMAT_SHOW_TIME;
- if (DateFormat.is24HourFormat(mContext)) {
- flags |= DateUtils.FORMAT_24HOUR;
- }
- if (endDay > startDay) {
- flags |= DateUtils.FORMAT_SHOW_DATE;
- }
- whenString.append(Utils.formatDateRange(mContext, start, end, flags));
-
- if (mShowTZ) {
- whenString.append(" ").append(mHomeTZName);
- }
- }
- eventInfo.id = eventId;
- eventInfo.start = start;
- eventInfo.end = end;
- eventInfo.allDay = allDay;
- eventInfo.when = whenString.toString();
- eventInfo.visibWhen = visibWhen;
- eventInfo.color = color;
- eventInfo.selfAttendeeStatus = selfStatus;
-
- // What
- if (TextUtils.isEmpty(title)) {
- eventInfo.title = mContext.getString(R.string.no_title_label);
- } else {
- eventInfo.title = title;
- }
- eventInfo.visibTitle = View.VISIBLE;
-
- // Where
- if (!TextUtils.isEmpty(location)) {
- eventInfo.visibWhere = View.VISIBLE;
- eventInfo.where = location;
- } else {
- eventInfo.visibWhere = View.GONE;
- }
- return eventInfo;
- }
-
- private DayInfo populateDayInfo(int julianDay, Time recycle) {
- long millis = recycle.setJulianDay(julianDay);
- int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE;
-
- String label;
- if (julianDay == mTodayJulianDay + 1) {
- label = mContext.getString(R.string.agenda_tomorrow,
- Utils.formatDateRange(mContext, millis, millis, flags).toString());
- } else {
- flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
- label = Utils.formatDateRange(mContext, millis, millis, flags);
- }
- return new DayInfo(julianDay, label);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("\nCalendarAppWidgetModel [eventInfos=");
- builder.append(mEventInfos);
- builder.append("]");
- return builder.toString();
- }
-} \ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.kt b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
new file mode 100644
index 00000000..440d178b
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.content.Context
+import android.database.Cursor
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.View
+import java.util.ArrayList
+import java.util.LinkedList
+import java.util.TimeZone
+
+internal class CalendarAppWidgetModel(context: Context, timeZone: String?) {
+ private var mHomeTZName: String? = null
+ private var mShowTZ = false
+
+ /**
+ * [RowInfo] is a class that represents a single row in the widget. It
+ * is actually only a pointer to either a [DayInfo] or an
+ * [EventInfo] instance, since a row in the widget might be either a
+ * day header or an event.
+ */
+ internal class RowInfo(
+ /**
+ * mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
+ */
+ @JvmField val mType: Int,
+ /**
+ * If mType is TYPE_DAY, then mData is the index into day infos.
+ * Otherwise mType is TYPE_MEETING and mData is the index into event
+ * infos.
+ */
+ @JvmField val mIndex: Int
+ ) {
+ companion object {
+ const val TYPE_DAY = 0
+ const val TYPE_MEETING = 1
+ }
+ }
+
+ /**
+ * [EventInfo] is a class that represents an event in the widget. It
+ * contains all of the data necessary to display that event, including the
+ * properly localized strings and visibility settings.
+ */
+ internal class EventInfo {
+ // Visibility value for When textview (View.GONE or View.VISIBLE)
+ @JvmField var visibWhen: Int
+ @JvmField var `when`: String? = null
+ // Visibility value for Where textview (View.GONE or View.VISIBLE)
+ @JvmField var visibWhere: Int
+ @JvmField var where: String? = null
+ // Visibility value for Title textview (View.GONE or View.VISIBLE)
+ @JvmField var visibTitle: Int
+ @JvmField var title: String? = null
+ @JvmField var selfAttendeeStatus = 0
+ @JvmField var id: Long = 0
+ @JvmField var start: Long = 0
+ @JvmField var end: Long = 0
+ @JvmField var allDay = false
+ @JvmField var color = 0
+
+ @Override
+ override fun toString(): String {
+ val builder = StringBuilder()
+ builder.append("EventInfo [visibTitle=")
+ builder.append(visibTitle)
+ builder.append(", title=")
+ builder.append(title)
+ builder.append(", visibWhen=")
+ builder.append(visibWhen)
+ builder.append(", id=")
+ builder.append(id)
+ builder.append(", when=")
+ builder.append(`when`)
+ builder.append(", visibWhere=")
+ builder.append(visibWhere)
+ builder.append(", where=")
+ builder.append(where)
+ builder.append(", color=")
+ builder.append(String.format("0x%x", color))
+ builder.append(", selfAttendeeStatus=")
+ builder.append(selfAttendeeStatus)
+ builder.append("]")
+ return builder.toString()
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ val prime = 31
+ var result = 1
+ result = prime * result + if (allDay) 1231 else 1237
+ result = prime * result + (id xor (id ushr 32)).toInt()
+ result = prime * result + (end xor (end ushr 32)).toInt()
+ result = prime * result + (start xor (start ushr 32)).toInt()
+ result = prime * result + if (title == null) 0 else title!!.hashCode()
+ result = prime * result + visibTitle
+ result = prime * result + visibWhen
+ result = prime * result + visibWhere
+ result = prime * result + if (`when` == null) 0 else `when`!!.hashCode()
+ result = prime * result + if (where == null) 0 else where!!.hashCode()
+ result = prime * result + color
+ result = prime * result + selfAttendeeStatus
+ return result
+ }
+
+ @Override
+ override fun equals(obj: Any?): Boolean {
+ if (this == obj) return true
+ if (obj == null) return false
+ if (this::class != obj::class) return false
+ val other = obj as EventInfo
+ if (id != other.id) return false
+ if (allDay != other.allDay) return false
+ if (end != other.end) return false
+ if (start != other.start) return false
+ if (title == null) {
+ if (other.title != null) return false
+ } else if (!title!!.equals(other.title)) return false
+ if (visibTitle != other.visibTitle) return false
+ if (visibWhen != other.visibWhen) return false
+ if (visibWhere != other.visibWhere) return false
+ if (`when` == null) {
+ if (other.`when` != null) return false
+ } else if (!`when`!!.equals(other.`when`)) {
+ return false
+ }
+ if (where == null) {
+ if (other.where != null) return false
+ } else if (!where!!.equals(other.where)) {
+ return false
+ }
+ if (color != other.color) {
+ return false
+ }
+ return if (selfAttendeeStatus != other.selfAttendeeStatus) {
+ false
+ } else true
+ }
+
+ init {
+ visibWhen = View.GONE
+ visibWhere = View.GONE
+ visibTitle = View.GONE
+ }
+ }
+
+ /**
+ * [DayInfo] is a class that represents a day header in the widget. It
+ * contains all of the data necessary to display that day header, including
+ * the properly localized string.
+ */
+ internal class DayInfo(
+ /** The Julian day */
+ @JvmField var mJulianDay: Int,
+ /** The string representation of this day header, to be displayed */
+ @JvmField var mDayLabel: String? = null
+ ) {
+ @Override
+ override fun toString(): String {
+ return mDayLabel as String
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ val prime = 31
+ var result = 1
+ result = prime * result + (mDayLabel?.hashCode() ?: 0)
+ result = prime * result + mJulianDay
+ return result
+ }
+
+ @Override
+ override fun equals(obj: Any?): Boolean {
+ if (this == obj) return true
+ if (obj == null) return false
+ if (this::class !== obj::class) return false
+ val other = obj as DayInfo
+ if (mDayLabel == null) {
+ if (other.mDayLabel != null) return false
+ } else if (!mDayLabel.equals(other.mDayLabel)) return false
+ return if (mJulianDay != other.mJulianDay) false else true
+ }
+ }
+
+ @JvmField val mRowInfos: ArrayList<RowInfo>
+ @JvmField val mEventInfos: ArrayList<EventInfo>
+ @JvmField val mDayInfos: ArrayList<DayInfo>
+ @JvmField val mContext: Context?
+ @JvmField val mNow: Long
+ @JvmField val mTodayJulianDay: Int
+ @JvmField val mMaxJulianDay: Int
+ fun buildFromCursor(cursor: Cursor, timeZone: String?) {
+ val recycle = Time(timeZone)
+ val mBuckets: ArrayList<LinkedList<RowInfo>> =
+ ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS)
+ for (i in 0 until CalendarAppWidgetService.MAX_DAYS) {
+ mBuckets.add(LinkedList<RowInfo>())
+ }
+ recycle.setToNow()
+ mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone())
+ if (mShowTZ) {
+ mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(
+ recycle.isDst !== 0,
+ TimeZone.SHORT
+ )
+ }
+ cursor.moveToPosition(-1)
+ val tz = Utils.getTimeZone(mContext, null)
+ while (cursor.moveToNext()) {
+ val rowId: Int = cursor.getPosition()
+ val eventId: Long = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID)
+ val allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) !== 0
+ var start: Long = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN)
+ var end: Long = cursor.getLong(CalendarAppWidgetService.INDEX_END)
+ val title: String = cursor.getString(CalendarAppWidgetService.INDEX_TITLE)
+ val location: String = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION)
+ // we don't compute these ourselves because it seems to produce the
+ // wrong endDay for all day events
+ val startDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY)
+ val endDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY)
+ val color: Int = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR)
+ val selfStatus: Int = cursor
+ .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS)
+
+ // Adjust all-day times into local timezone
+ if (allDay) {
+ start = Utils.convertAlldayUtcToLocal(recycle, start, tz as String)
+ end = Utils.convertAlldayUtcToLocal(recycle, end, tz as String)
+ }
+ if (LOGD) {
+ Log.d(
+ TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start +
+ " end:" + end + " eventId:" + eventId
+ )
+ }
+
+ // we might get some extra events when querying, in order to
+ // deal with all-day events
+ if (end < mNow) {
+ continue
+ }
+ val i: Int = mEventInfos.size
+ mEventInfos.add(
+ populateEventInfo(
+ eventId, allDay, start, end, startDay, endDay, title,
+ location, color, selfStatus
+ )
+ )
+ // populate the day buckets that this event falls into
+ val from: Int = Math.max(startDay, mTodayJulianDay)
+ val to: Int = Math.min(endDay, mMaxJulianDay)
+ for (day in from..to) {
+ val bucket: LinkedList<RowInfo> = mBuckets.get(day - mTodayJulianDay)
+ val rowInfo = RowInfo(RowInfo.TYPE_MEETING, i)
+ if (allDay) {
+ bucket.addFirst(rowInfo)
+ } else {
+ bucket.add(rowInfo)
+ }
+ }
+ }
+ var day = mTodayJulianDay
+ var count = 0
+ for (bucket in mBuckets) {
+ if (!bucket.isEmpty()) {
+ // We don't show day header in today
+ if (day != mTodayJulianDay) {
+ val dayInfo = populateDayInfo(day, recycle)
+ // Add the day header
+ val dayIndex: Int = mDayInfos.size
+ mDayInfos.add(dayInfo as CalendarAppWidgetModel.DayInfo)
+ mRowInfos.add(RowInfo(RowInfo.TYPE_DAY, dayIndex))
+ }
+
+ // Add the event row infos
+ mRowInfos.addAll(bucket)
+ count += bucket.size
+ }
+ day++
+ if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
+ break
+ }
+ }
+ }
+
+ private fun populateEventInfo(
+ eventId: Long,
+ allDay: Boolean,
+ start: Long,
+ end: Long,
+ startDay: Int,
+ endDay: Int,
+ title: String,
+ location: String,
+ color: Int,
+ selfStatus: Int
+ ): EventInfo {
+ val eventInfo = EventInfo()
+
+ // Compute a human-readable string for the start time of the event
+ val whenString = StringBuilder()
+ val visibWhen: Int
+ var flags: Int = DateUtils.FORMAT_ABBREV_ALL
+ visibWhen = View.VISIBLE
+ if (allDay) {
+ flags = flags or DateUtils.FORMAT_SHOW_DATE
+ whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+ } else {
+ flags = flags or DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(mContext)) {
+ flags = flags or DateUtils.FORMAT_24HOUR
+ }
+ if (endDay > startDay) {
+ flags = flags or DateUtils.FORMAT_SHOW_DATE
+ }
+ whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+ if (mShowTZ) {
+ whenString.append(" ").append(mHomeTZName)
+ }
+ }
+ eventInfo.id = eventId
+ eventInfo.start = start
+ eventInfo.end = end
+ eventInfo.allDay = allDay
+ eventInfo.`when` = whenString.toString()
+ eventInfo.visibWhen = visibWhen
+ eventInfo.color = color
+ eventInfo.selfAttendeeStatus = selfStatus
+
+ // What
+ if (TextUtils.isEmpty(title)) {
+ eventInfo.title = mContext?.getString(R.string.no_title_label)
+ } else {
+ eventInfo.title = title
+ }
+ eventInfo.visibTitle = View.VISIBLE
+
+ // Where
+ if (!TextUtils.isEmpty(location)) {
+ eventInfo.visibWhere = View.VISIBLE
+ eventInfo.where = location
+ } else {
+ eventInfo.visibWhere = View.GONE
+ }
+ return eventInfo
+ }
+
+ private fun populateDayInfo(julianDay: Int, recycle: Time?): DayInfo? {
+ val millis: Long = recycle?.setJulianDay(julianDay) as Long
+ var flags: Int = DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
+ val label: String?
+ if (julianDay == mTodayJulianDay + 1) {
+ label = mContext?.getString(
+ R.string.agenda_tomorrow,
+ Utils.formatDateRange(mContext, millis, millis, flags).toString()
+ )
+ } else {
+ flags = flags or DateUtils.FORMAT_SHOW_WEEKDAY
+ label = Utils.formatDateRange(mContext, millis, millis, flags)
+ }
+ return DayInfo(julianDay, label as String)
+ }
+
+ @Override
+ override fun toString(): String {
+ val builder = StringBuilder()
+ builder.append("\nCalendarAppWidgetModel [eventInfos=")
+ builder.append(mEventInfos)
+ builder.append("]")
+ return builder.toString()
+ }
+
+ companion object {
+ private val TAG: String = CalendarAppWidgetModel::class.java.getSimpleName()
+ private const val LOGD = false
+ }
+
+ init {
+ mNow = System.currentTimeMillis()
+ val time = Time(timeZone)
+ time.setToNow() // This is needed for gmtoff to be set
+ mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff)
+ mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1
+ mEventInfos = ArrayList<EventInfo>(50)
+ mRowInfos = ArrayList<RowInfo>(50)
+ mDayInfos = ArrayList<DayInfo>(8)
+ mContext = context
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
deleted file mode 100644
index 3a69efd3..00000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
+++ /dev/null
@@ -1,230 +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.widget;
-
-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.AlarmManager;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.widget.RemoteViews;
-
-import com.android.calendar.AllInOneActivity;
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-/**
- * Simple widget to show next upcoming calendar event.
- */
-public class CalendarAppWidgetProvider extends AppWidgetProvider {
- static final String TAG = "CalendarAppWidgetProvider";
- static final boolean LOGD = false;
-
- // TODO Move these to Calendar.java
- static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS";
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onReceive(Context context, Intent intent) {
- // Handle calendar-specific updates ourselves because they might be
- // coming in without extras, which AppWidgetProvider then blocks.
- final String action = intent.getAction();
- if (LOGD)
- Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString());
- if (Utils.getWidgetUpdateAction(context).equals(action)) {
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- performUpdate(context, appWidgetManager,
- appWidgetManager.getAppWidgetIds(getComponentName(context)),
- null /* no eventIds */);
- } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED)
- || action.equals(Intent.ACTION_TIME_CHANGED)
- || action.equals(Intent.ACTION_TIMEZONE_CHANGED)
- || action.equals(Intent.ACTION_DATE_CHANGED)
- || action.equals(Utils.getWidgetScheduledUpdateAction(context)))) {
- Intent service = new Intent(context, CalendarAppWidgetService.class);
- context.startService(service);
- } else {
- super.onReceive(context, intent);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onDisabled(Context context) {
- // Unsubscribe from all AlarmManager updates
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- PendingIntent pendingUpdate = getUpdateIntent(context);
- am.cancel(pendingUpdate);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */);
- }
-
-
- /**
- * Build {@link ComponentName} describing this specific
- * {@link AppWidgetProvider}
- */
- static ComponentName getComponentName(Context context) {
- return new ComponentName(context, CalendarAppWidgetProvider.class);
- }
-
- /**
- * Process and push out an update for the given appWidgetIds. This call
- * actually fires an intent to start {@link CalendarAppWidgetService} as a
- * background service which handles the actual update, to prevent ANR'ing
- * during database queries.
- *
- * @param context Context to use when starting {@link CalendarAppWidgetService}.
- * @param appWidgetIds List of specific appWidgetIds to update, or null for
- * all.
- * @param changedEventIds Specific events known to be changed. If present,
- * we use it to decide if an update is necessary.
- */
- private void performUpdate(Context context,
- AppWidgetManager appWidgetManager, int[] appWidgetIds,
- long[] changedEventIds) {
- // Launch over to service so it can perform update
- for (int appWidgetId : appWidgetIds) {
- if (LOGD) Log.d(TAG, "Building widget update...");
- Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
- updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- if (changedEventIds != null) {
- updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
- }
- updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
-
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
- // Calendar header
- Time time = new Time(Utils.getTimeZone(context, null));
- time.setToNow();
- long millis = time.toMillis(true);
- final String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1,
- DateUtils.LENGTH_MEDIUM);
- final String date = Utils.formatDateRange(context, millis, millis,
- DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_NO_YEAR);
- views.setTextViewText(R.id.day_of_week, dayOfWeek);
- views.setTextViewText(R.id.date, date);
- // Attach to list of events
- views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent);
- appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list);
-
-
- // Launch calendar app when the user taps on the header
- final Intent launchCalendarIntent = new Intent(Intent.ACTION_VIEW);
- launchCalendarIntent.setClass(context, AllInOneActivity.class);
- launchCalendarIntent
- .setData(Uri.parse("content://com.android.calendar/time/" + millis));
- final PendingIntent launchCalendarPendingIntent = PendingIntent.getActivity(
- context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */);
- views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent);
-
- // Each list item will call setOnClickExtra() to let the list know
- // which item
- // is selected by a user.
- final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context);
- views.setPendingIntentTemplate(R.id.events_list, updateEventIntent);
-
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
- }
-
- /**
- * Build the {@link PendingIntent} used to trigger an update of all calendar
- * widgets. Uses {@link Utils#getWidgetScheduledUpdateAction(Context)} to
- * directly target all widgets instead of using
- * {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
- *
- * @param context Context to use when building broadcast.
- */
- static PendingIntent getUpdateIntent(Context context) {
- Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(context));
- intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE);
- return PendingIntent.getBroadcast(context, 0 /* no requestCode */, intent,
- 0 /* no flags */);
- }
-
- /**
- * Build a {@link PendingIntent} to launch the Calendar app. This should be used
- * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}.
- */
- static PendingIntent getLaunchPendingIntentTemplate(Context context) {
- Intent launchIntent = new Intent();
- launchIntent.setAction(Intent.ACTION_VIEW);
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- launchIntent.setClass(context, AllInOneActivity.class);
- return PendingIntent.getActivity(context, 0 /* no requestCode */, launchIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- /**
- * Build an {@link Intent} available as FillInIntent to launch the Calendar app.
- * This should be used in combination with
- * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
- * If the go to time is 0, then calendar will be launched without a starting time.
- *
- * @param goToTime time that calendar should take the user to, or 0 to
- * indicate no specific start time.
- */
- static Intent getLaunchFillInIntent(Context context, long id, long start, long end,
- boolean allDay) {
- final Intent fillInIntent = new Intent();
- String dataString = "content://com.android.calendar/events";
- if (id != 0) {
- fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true);
- fillInIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-
- dataString += "/" + id;
- // If we have an event id - start the event info activity
- fillInIntent.setClass(context, EventInfoActivity.class);
- } else {
- // If we do not have an event id - start AllInOne
- fillInIntent.setClass(context, AllInOneActivity.class);
- }
- Uri data = Uri.parse(dataString);
- fillInIntent.setData(data);
- fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start);
- fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end);
- fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay);
-
- return fillInIntent;
- }
-}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt b/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt
new file mode 100644
index 00000000..b3539f22
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.widget.RemoteViews
+import com.android.calendar.AllInOneActivity
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.R
+import com.android.calendar.Utils
+
+/**
+ * Simple widget to show next upcoming calendar event.
+ */
+class CalendarAppWidgetProvider : AppWidgetProvider() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ override fun onReceive(context: Context?, intent: Intent?) {
+ // Handle calendar-specific updates ourselves because they might be
+ // coming in without extras, which AppWidgetProvider then blocks.
+ val action: String? = intent?.getAction()
+ if (LOGD) Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString())
+ if (Utils.getWidgetUpdateAction(context as Context).equals(action)) {
+ val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
+ performUpdate(
+ context as Context, appWidgetManager,
+ appWidgetManager.getAppWidgetIds(getComponentName(context)),
+ null /* no eventIds */
+ )
+ } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED) ||
+ action.equals(Intent.ACTION_TIME_CHANGED) ||
+ action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
+ action.equals(Intent.ACTION_DATE_CHANGED) ||
+ action.equals(Utils.getWidgetScheduledUpdateAction(context as Context)))
+ ) {
+ val service = Intent(context, CalendarAppWidgetService::class.java)
+ context?.startService(service)
+ } else {
+ super.onReceive(context, intent)
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ override fun onDisabled(context: Context) {
+ // Unsubscribe from all AlarmManager updates
+ val am: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val pendingUpdate: PendingIntent = getUpdateIntent(context)
+ am.cancel(pendingUpdate)
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
+ ) {
+ performUpdate(context, appWidgetManager,
+ appWidgetIds, null /* no eventIds */)
+ }
+
+ /**
+ * Process and push out an update for the given appWidgetIds. This call
+ * actually fires an intent to start [CalendarAppWidgetService] as a
+ * background service which handles the actual update, to prevent ANR'ing
+ * during database queries.
+ *
+ * @param context Context to use when starting [CalendarAppWidgetService].
+ * @param appWidgetIds List of specific appWidgetIds to update, or null for
+ * all.
+ * @param changedEventIds Specific events known to be changed. If present,
+ * we use it to decide if an update is necessary.
+ */
+ private fun performUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray,
+ changedEventIds: LongArray?
+ ) {
+ // Launch over to service so it can perform update
+ for (appWidgetId in appWidgetIds) {
+ if (LOGD) Log.d(TAG, "Building widget update...")
+ val updateIntent = Intent(context, CalendarAppWidgetService::class.java)
+ updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+ if (changedEventIds != null) {
+ updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds)
+ }
+ updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)))
+ val views = RemoteViews(context.getPackageName(), R.layout.appwidget)
+ // Calendar header
+ val time = Time(Utils.getTimeZone(context, null))
+ time.setToNow()
+ val millis: Long = time.toMillis(true)
+ val dayOfWeek: String = DateUtils.getDayOfWeekString(
+ time.weekDay + 1,
+ DateUtils.LENGTH_MEDIUM
+ )
+ val date: String? = Utils.formatDateRange(
+ context, millis, millis,
+ DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
+ or DateUtils.FORMAT_NO_YEAR
+ )
+ views.setTextViewText(R.id.day_of_week, dayOfWeek)
+ views.setTextViewText(R.id.date, date)
+ // Attach to list of events
+ views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent)
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list)
+
+ // Launch calendar app when the user taps on the header
+ val launchCalendarIntent = Intent(Intent.ACTION_VIEW)
+ launchCalendarIntent.setClass(context, AllInOneActivity::class.java)
+ launchCalendarIntent
+ .setData(Uri.parse("content://com.android.calendar/time/$millis"))
+ val launchCalendarPendingIntent: PendingIntent = PendingIntent.getActivity(
+ context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */
+ )
+ views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent)
+
+ // Each list item will call setOnClickExtra() to let the list know
+ // which item
+ // is selected by a user.
+ val updateEventIntent: PendingIntent = getLaunchPendingIntentTemplate(context)
+ views.setPendingIntentTemplate(R.id.events_list, updateEventIntent)
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+ }
+ }
+
+ companion object {
+ const val TAG = "CalendarAppWidgetProvider"
+ const val LOGD = false
+
+ // TODO Move these to Calendar.java
+ const val EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS"
+
+ /**
+ * Build [ComponentName] describing this specific
+ * [AppWidgetProvider]
+ */
+ @JvmStatic fun getComponentName(context: Context?): ComponentName {
+ return ComponentName(context as Context, CalendarAppWidgetProvider::class.java)
+ }
+
+ /**
+ * Build the [PendingIntent] used to trigger an update of all calendar
+ * widgets. Uses [Utils.getWidgetScheduledUpdateAction] to
+ * directly target all widgets instead of using
+ * [AppWidgetManager.EXTRA_APPWIDGET_IDS].
+ *
+ * @param context Context to use when building broadcast.
+ */
+ @JvmStatic fun getUpdateIntent(context: Context?): PendingIntent {
+ val intent = Intent(Utils.getWidgetScheduledUpdateAction(context as Context))
+ intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
+ return PendingIntent.getBroadcast(
+ context, 0 /* no requestCode */, intent,
+ 0 /* no flags */
+ )
+ }
+
+ /**
+ * Build a [PendingIntent] to launch the Calendar app. This should be used
+ * in combination with [RemoteViews.setPendingIntentTemplate].
+ */
+ @JvmStatic fun getLaunchPendingIntentTemplate(context: Context?): PendingIntent {
+ val launchIntent = Intent()
+ launchIntent.setAction(Intent.ACTION_VIEW)
+ launchIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME
+ )
+ launchIntent.setClass(context as Context, AllInOneActivity::class.java)
+ return PendingIntent.getActivity(
+ context, 0 /* no requestCode */, launchIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ }
+
+ /**
+ * Build an [Intent] available as FillInIntent to launch the Calendar app.
+ * This should be used in combination with
+ * [RemoteViews.setOnClickFillInIntent].
+ * If the go to time is 0, then calendar will be launched without a starting time.
+ *
+ * @param goToTime time that calendar should take the user to, or 0 to
+ * indicate no specific start time.
+ */
+ @JvmStatic fun getLaunchFillInIntent(
+ context: Context?,
+ id: Long,
+ start: Long,
+ end: Long,
+ allDay: Boolean
+ ): Intent {
+ val fillInIntent = Intent()
+ var dataString = "content://com.android.calendar/events"
+ if (id != 0L) {
+ fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true)
+ fillInIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME
+ )
+ dataString += "/$id"
+ // If we have an event id - start the event info activity
+ fillInIntent.setClass(context as Context, EventInfoActivity::class.java)
+ } else {
+ // If we do not have an event id - start AllInOne
+ fillInIntent.setClass(context as Context, AllInOneActivity::class.java)
+ }
+ val data: Uri = Uri.parse(dataString)
+ fillInIntent.setData(data)
+ fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start)
+ fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end)
+ fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay)
+ return fillInIntent
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.java b/src/com/android/calendar/widget/CalendarAppWidgetService.java
deleted file mode 100644
index ec702c7c..00000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetService.java
+++ /dev/null
@@ -1,626 +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.widget;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.Handler;
-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.View;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
-import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
-import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicInteger;
-
-
-public class CalendarAppWidgetService extends RemoteViewsService {
- private static final String TAG = "CalendarWidget";
-
- static final int EVENT_MIN_COUNT = 20;
- static final int EVENT_MAX_COUNT = 100;
- // Minimum delay between queries on the database for widget updates in ms
- static final int WIDGET_UPDATE_THROTTLE = 500;
-
- private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
- + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
- + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
-
- private static final String EVENT_SELECTION = Calendars.VISIBLE + "=1";
- private static final String EVENT_SELECTION_HIDE_DECLINED = Calendars.VISIBLE + "=1 AND "
- + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
-
- static final String[] EVENT_PROJECTION = new String[] {
- Instances.ALL_DAY,
- Instances.BEGIN,
- Instances.END,
- Instances.TITLE,
- Instances.EVENT_LOCATION,
- Instances.EVENT_ID,
- Instances.START_DAY,
- Instances.END_DAY,
- Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR.
- Instances.SELF_ATTENDEE_STATUS,
- };
-
- static final int INDEX_ALL_DAY = 0;
- static final int INDEX_BEGIN = 1;
- static final int INDEX_END = 2;
- static final int INDEX_TITLE = 3;
- static final int INDEX_EVENT_LOCATION = 4;
- static final int INDEX_EVENT_ID = 5;
- static final int INDEX_START_DAY = 6;
- static final int INDEX_END_DAY = 7;
- static final int INDEX_COLOR = 8;
- static final int INDEX_SELF_ATTENDEE_STATUS = 9;
-
- static {
- if (!Utils.isJellybeanOrLater()) {
- EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR;
- }
- }
- static final int MAX_DAYS = 7;
-
- private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
-
- /**
- * Update interval used when no next-update calculated, or bad trigger time in past.
- * Unit: milliseconds.
- */
- private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
-
- @Override
- public RemoteViewsFactory onGetViewFactory(Intent intent) {
- return new CalendarFactory(getApplicationContext(), intent);
- }
-
- public static class CalendarFactory extends BroadcastReceiver implements
- RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> {
- private static final boolean LOGD = false;
-
- // Suppress unnecessary logging about update time. Need to be static as this object is
- // re-instanciated frequently.
- // TODO: It seems loadData() is called via onCreate() four times, which should mean
- // unnecessary CalendarFactory object is created and dropped. It is not efficient.
- private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS;
-
- private Context mContext;
- private Resources mResources;
- private static CalendarAppWidgetModel mModel;
- private static Object mLock = new Object();
- private static volatile int mSerialNum = 0;
- private int mLastSerialNum = -1;
- private CursorLoader mLoader;
- private final Handler mHandler = new Handler();
- private static final AtomicInteger currentVersion = new AtomicInteger(0);
- private final ExecutorService executor = Executors.newSingleThreadExecutor();
- private int mAppWidgetId;
- private int mDeclinedColor;
- private int mStandardColor;
- private int mAllDayColor;
-
- private final Runnable mTimezoneChanged = new Runnable() {
- @Override
- public void run() {
- if (mLoader != null) {
- mLoader.forceLoad();
- }
- }
- };
-
- private Runnable createUpdateLoaderRunnable(final String selection,
- final PendingResult result, final int version) {
- return new Runnable() {
- @Override
- public void run() {
- // If there is a newer load request in the queue, skip loading.
- if (mLoader != null && version >= currentVersion.get()) {
- Uri uri = createLoaderUri();
- mLoader.setUri(uri);
- mLoader.setSelection(selection);
- synchronized (mLock) {
- mLastSerialNum = ++mSerialNum;
- }
- mLoader.forceLoad();
- }
- result.finish();
- }
- };
- }
-
- protected CalendarFactory(Context context, Intent intent) {
- mContext = context;
- mResources = context.getResources();
- mAppWidgetId = intent.getIntExtra(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
-
- mDeclinedColor = mResources.getColor(R.color.appwidget_item_declined_color);
- mStandardColor = mResources.getColor(R.color.appwidget_item_standard_color);
- mAllDayColor = mResources.getColor(R.color.appwidget_item_allday_color);
- }
-
- public CalendarFactory() {
- // This is being created as part of onReceive
-
- }
-
- @Override
- public void onCreate() {
- String selection = queryForSelection();
- initLoader(selection);
- }
-
- @Override
- public void onDataSetChanged() {
- }
-
- @Override
- public void onDestroy() {
- if (mLoader != null) {
- mLoader.reset();
- }
- }
-
- @Override
- public RemoteViews getLoadingView() {
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- R.layout.appwidget_loading);
- return views;
- }
-
- @Override
- public RemoteViews getViewAt(int position) {
- // we use getCount here so that it doesn't return null when empty
- if (position < 0 || position >= getCount()) {
- return null;
- }
-
- if (mModel == null) {
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- R.layout.appwidget_loading);
- final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
- 0, 0, false);
- views.setOnClickFillInIntent(R.id.appwidget_loading, intent);
- return views;
-
- }
- if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- R.layout.appwidget_no_events);
- final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
- 0, 0, false);
- views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
- return views;
- }
-
- RowInfo rowInfo = mModel.mRowInfos.get(position);
- if (rowInfo.mType == RowInfo.TYPE_DAY) {
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- R.layout.appwidget_day);
- DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
- updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
- return views;
- } else {
- RemoteViews views;
- final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
- if (eventInfo.allDay) {
- views = new RemoteViews(mContext.getPackageName(),
- R.layout.widget_all_day_item);
- } else {
- views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
- }
- int displayColor = Utils.getDisplayColorFromColor(eventInfo.color);
-
- final long now = System.currentTimeMillis();
- if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) {
- views.setInt(R.id.widget_row, "setBackgroundResource",
- R.drawable.agenda_item_bg_secondary);
- } else {
- views.setInt(R.id.widget_row, "setBackgroundResource",
- R.drawable.agenda_item_bg_primary);
- }
-
- if (!eventInfo.allDay) {
- updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when);
- updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where);
- }
- updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title);
-
- views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE);
-
- int selfAttendeeStatus = eventInfo.selfAttendeeStatus;
- if (eventInfo.allDay) {
- if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
- views.setInt(R.id.agenda_item_color, "setImageResource",
- R.drawable.widget_chip_not_responded_bg);
- views.setInt(R.id.title, "setTextColor", displayColor);
- } else {
- views.setInt(R.id.agenda_item_color, "setImageResource",
- R.drawable.widget_chip_responded_bg);
- views.setInt(R.id.title, "setTextColor", mAllDayColor);
- }
- if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
- // 40% opacity
- views.setInt(R.id.agenda_item_color, "setColorFilter",
- Utils.getDeclinedColorFromColor(displayColor));
- } else {
- views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
- }
- } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
- 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, "setImageResource",
- R.drawable.widget_chip_responded_bg);
- // 40% opacity
- views.setInt(R.id.agenda_item_color, "setColorFilter",
- Utils.getDeclinedColorFromColor(displayColor));
- } else {
- views.setInt(R.id.title, "setTextColor", mStandardColor);
- views.setInt(R.id.when, "setTextColor", mStandardColor);
- views.setInt(R.id.where, "setTextColor", mStandardColor);
- if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
- views.setInt(R.id.agenda_item_color, "setImageResource",
- R.drawable.widget_chip_not_responded_bg);
- } else {
- views.setInt(R.id.agenda_item_color, "setImageResource",
- R.drawable.widget_chip_responded_bg);
- }
- views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
- }
-
- long start = eventInfo.start;
- long end = eventInfo.end;
- // An element in ListView.
- if (eventInfo.allDay) {
- String tz = Utils.getTimeZone(mContext, null);
- Time recycle = new Time();
- start = Utils.convertAlldayLocalToUTC(recycle, start, tz);
- end = Utils.convertAlldayLocalToUTC(recycle, end, tz);
- }
- final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent(
- mContext, eventInfo.id, start, end, eventInfo.allDay);
- views.setOnClickFillInIntent(R.id.widget_row, fillInIntent);
- return views;
- }
- }
-
- @Override
- public int getViewTypeCount() {
- return 5;
- }
-
- @Override
- public int getCount() {
- // if there are no events, we still return 1 to represent the "no
- // events" view
- if (mModel == null) {
- return 1;
- }
- return Math.max(1, mModel.mRowInfos.size());
- }
-
- @Override
- public long getItemId(int position) {
- if (mModel == null || mModel.mRowInfos.isEmpty() || position >= getCount()) {
- return 0;
- }
- RowInfo rowInfo = mModel.mRowInfos.get(position);
- if (rowInfo.mType == RowInfo.TYPE_DAY) {
- return rowInfo.mIndex;
- }
- EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
- long prime = 31;
- long result = 1;
- result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32));
- result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32));
- return result;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- /**
- * Query across all calendars for upcoming event instances from now
- * until some time in the future. Widen the time range that we query by
- * one day on each end so that we can catch all-day events. All-day
- * events are stored starting at midnight in UTC but should be included
- * in the list of events starting at midnight local time. This may fetch
- * more events than we actually want, so we filter them out later.
- *
- * @param selection The selection string for the loader to filter the query with.
- */
- public void initLoader(String selection) {
- if (LOGD)
- Log.d(TAG, "Querying for widget events...");
-
- // Search for events from now until some time in the future
- Uri uri = createLoaderUri();
- mLoader = new CursorLoader(mContext, uri, EVENT_PROJECTION, selection, null,
- EVENT_SORT_ORDER);
- mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE);
- synchronized (mLock) {
- mLastSerialNum = ++mSerialNum;
- }
- mLoader.registerListener(mAppWidgetId, this);
- mLoader.startLoading();
-
- }
-
- /**
- * This gets the selection string for the loader. This ends up doing a query in the
- * shared preferences.
- */
- private String queryForSelection() {
- return Utils.getHideDeclinedEvents(mContext) ? EVENT_SELECTION_HIDE_DECLINED
- : EVENT_SELECTION;
- }
-
- /**
- * @return The uri for the loader
- */
- private Uri createLoaderUri() {
- long now = System.currentTimeMillis();
- // Add a day on either side to catch all-day events
- long begin = now - DateUtils.DAY_IN_MILLIS;
- long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS;
-
- Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end);
- return uri;
- }
-
- /* @VisibleForTesting */
- protected static CalendarAppWidgetModel buildAppWidgetModel(
- Context context, Cursor cursor, String timeZone) {
- CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone);
- model.buildFromCursor(cursor, timeZone);
- return model;
- }
-
- /**
- * Calculates and returns the next time we should push widget updates.
- */
- private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) {
- // Make sure an update happens at midnight or earlier
- long minUpdateTime = getNextMidnightTimeMillis(timeZone);
- for (EventInfo event : model.mEventInfos) {
- final long start;
- final long end;
- start = event.start;
- end = event.end;
-
- // We want to update widget when we enter/exit time range of an event.
- if (now < start) {
- minUpdateTime = Math.min(minUpdateTime, start);
- } else if (now < end) {
- minUpdateTime = Math.min(minUpdateTime, end);
- }
- }
- return minUpdateTime;
- }
-
- private static long getNextMidnightTimeMillis(String timezone) {
- Time time = new Time();
- time.setToNow();
- time.monthDay++;
- time.hour = 0;
- time.minute = 0;
- time.second = 0;
- long midnightDeviceTz = time.normalize(true);
-
- time.timezone = timezone;
- time.setToNow();
- time.monthDay++;
- time.hour = 0;
- time.minute = 0;
- time.second = 0;
- long midnightHomeTz = time.normalize(true);
-
- return Math.min(midnightDeviceTz, midnightHomeTz);
- }
-
- static void updateTextView(RemoteViews views, int id, int visibility, String string) {
- views.setViewVisibility(id, visibility);
- if (visibility == View.VISIBLE) {
- views.setTextViewText(id, string);
- }
- }
-
- /*
- * (non-Javadoc)
- * @see
- * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
- * .content.Loader, java.lang.Object)
- */
- @Override
- public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null) {
- return;
- }
- // If a newer update has happened since we started clean up and
- // return
- synchronized (mLock) {
- if (cursor.isClosed()) {
- Log.wtf(TAG, "Got a closed cursor from onLoadComplete");
- return;
- }
-
- if (mLastSerialNum != mSerialNum) {
- return;
- }
-
- final long now = System.currentTimeMillis();
- String tz = Utils.getTimeZone(mContext, mTimezoneChanged);
-
- // Copy it to a local static cursor.
- MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
- try {
- mModel = buildAppWidgetModel(mContext, matrixCursor, tz);
- } finally {
- if (matrixCursor != null) {
- matrixCursor.close();
- }
-
- if (cursor != null) {
- cursor.close();
- }
- }
-
- // Schedule an alarm to wake ourselves up for the next update.
- // We also cancel
- // all existing wake-ups because PendingIntents don't match
- // against extras.
- long triggerTime = calculateUpdateTime(mModel, now, tz);
-
- // If no next-update calculated, or bad trigger time in past,
- // schedule
- // update about six hours from now.
- if (triggerTime < now) {
- Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now));
- triggerTime = now + UPDATE_TIME_NO_EVENTS;
- }
-
- final AlarmManager alertManager = (AlarmManager) mContext
- .getSystemService(Context.ALARM_SERVICE);
- final PendingIntent pendingUpdate = CalendarAppWidgetProvider
- .getUpdateIntent(mContext);
-
- alertManager.cancel(pendingUpdate);
- alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate);
- Time time = new Time(Utils.getTimeZone(mContext, null));
- time.setToNow();
-
- if (time.normalize(true) != sLastUpdateTime) {
- Time time2 = new Time(Utils.getTimeZone(mContext, null));
- time2.set(sLastUpdateTime);
- time2.normalize(true);
- if (time.year != time2.year || time.yearDay != time2.yearDay) {
- final Intent updateIntent = new Intent(
- Utils.getWidgetUpdateAction(mContext));
- mContext.sendBroadcast(updateIntent);
- }
-
- sLastUpdateTime = time.toMillis(true);
- }
-
- AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
- if (widgetManager == null) {
- return;
- }
- if (mAppWidgetId == -1) {
- int[] ids = widgetManager.getAppWidgetIds(CalendarAppWidgetProvider
- .getComponentName(mContext));
-
- widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list);
- } else {
- widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list);
- }
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (LOGD)
- Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString());
- mContext = context;
-
- // We cannot do any queries from the UI thread, so push the 'selection' query
- // to a background thread. However the implementation of the latter query
- // (cursor loading) uses CursorLoader which must be initiated from the UI thread,
- // so there is some convoluted handshaking here.
- //
- // Note that as currently implemented, this must run in a single threaded executor
- // or else the loads may be run out of order.
- //
- // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously
- // in the background thread. All the handshaking going on here between the UI and
- // background thread with using goAsync, mHandler, and CursorLoader is confusing.
- final PendingResult result = goAsync();
- executor.submit(new Runnable() {
- @Override
- public void run() {
- // We always complete queryForSelection() even if the load task ends up being
- // canceled because of a more recent one. Optimizing this to allow
- // canceling would require keeping track of all the PendingResults
- // (from goAsync) to abort them. Defer this until it becomes a problem.
- final String selection = queryForSelection();
-
- if (mLoader == null) {
- mAppWidgetId = -1;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- initLoader(selection);
- result.finish();
- }
- });
- } else {
- mHandler.post(createUpdateLoaderRunnable(selection, result,
- currentVersion.incrementAndGet()));
- }
- }
- });
- }
- }
-
- /**
- * Format given time for debugging output.
- *
- * @param unixTime Target time to report.
- * @param now Current system time from {@link System#currentTimeMillis()}
- * for calculating time difference.
- */
- static String formatDebugTime(long unixTime, long now) {
- Time time = new Time();
- time.set(unixTime);
-
- long delta = unixTime - now;
- if (delta > DateUtils.MINUTE_IN_MILLIS) {
- delta /= DateUtils.MINUTE_IN_MILLIS;
- return String.format("[%d] %s (%+d mins)", unixTime,
- time.format("%H:%M:%S"), delta);
- } else {
- delta /= DateUtils.SECOND_IN_MILLIS;
- return String.format("[%d] %s (%+d secs)", unixTime,
- time.format("%H:%M:%S"), delta);
- }
- }
-}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.kt b/src/com/android/calendar/widget/CalendarAppWidgetService.kt
new file mode 100644
index 00000000..114fdf12
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetService.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.CursorLoader
+import android.content.Intent
+import android.content.Loader
+import android.content.res.Resources
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.os.Handler
+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.View
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import com.android.calendar.R
+import com.android.calendar.Utils
+import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo
+import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo
+import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.atomic.AtomicInteger
+
+class CalendarAppWidgetService : RemoteViewsService() {
+ companion object {
+ private const val TAG = "CalendarWidget"
+ const val EVENT_MIN_COUNT = 20
+ const val EVENT_MAX_COUNT = 100
+
+ // Minimum delay between queries on the database for widget updates in ms
+ const val WIDGET_UPDATE_THROTTLE = 500
+ private val EVENT_SORT_ORDER: String = (Instances.START_DAY.toString() + " ASC, " +
+ Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " +
+ Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT)
+ private val EVENT_SELECTION: String = Calendars.VISIBLE.toString() + "=1"
+ private val EVENT_SELECTION_HIDE_DECLINED: String =
+ (Calendars.VISIBLE.toString() + "=1 AND " +
+ Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED)
+ @JvmField
+ val EVENT_PROJECTION = arrayOf<String>(
+ Instances.ALL_DAY,
+ Instances.BEGIN,
+ Instances.END,
+ Instances.TITLE,
+ Instances.EVENT_LOCATION,
+ Instances.EVENT_ID,
+ Instances.START_DAY,
+ Instances.END_DAY,
+ Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR.
+ Instances.SELF_ATTENDEE_STATUS
+ )
+ const val INDEX_ALL_DAY = 0
+ const val INDEX_BEGIN = 1
+ const val INDEX_END = 2
+ const val INDEX_TITLE = 3
+ const val INDEX_EVENT_LOCATION = 4
+ const val INDEX_EVENT_ID = 5
+ const val INDEX_START_DAY = 6
+ const val INDEX_END_DAY = 7
+ const val INDEX_COLOR = 8
+ const val INDEX_SELF_ATTENDEE_STATUS = 9
+ const val MAX_DAYS = 7
+ private val SEARCH_DURATION: Long = MAX_DAYS * DateUtils.DAY_IN_MILLIS
+
+ /**
+ * Update interval used when no next-update calculated, or bad trigger time in past.
+ * Unit: milliseconds.
+ */
+ private val UPDATE_TIME_NO_EVENTS: Long = DateUtils.HOUR_IN_MILLIS * 6
+
+ /**
+ * Format given time for debugging output.
+ *
+ * @param unixTime Target time to report.
+ * @param now Current system time from [System.currentTimeMillis]
+ * for calculating time difference.
+ */
+ fun formatDebugTime(unixTime: Long, now: Long): String {
+ val time = Time()
+ time.set(unixTime)
+ var delta = unixTime - now
+ return if (delta > DateUtils.MINUTE_IN_MILLIS) {
+ delta /= DateUtils.MINUTE_IN_MILLIS
+ String.format(
+ "[%d] %s (%+d mins)", unixTime,
+ time.format("%H:%M:%S"), delta
+ )
+ } else {
+ delta /= DateUtils.SECOND_IN_MILLIS
+ String.format(
+ "[%d] %s (%+d secs)", unixTime,
+ time.format("%H:%M:%S"), delta
+ )
+ }
+ }
+
+ init {
+ if (!Utils.isJellybeanOrLater()) {
+ EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR
+ }
+ }
+ }
+
+ @Override
+ override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
+ return CalendarFactory(getApplicationContext(), intent)
+ }
+
+ class CalendarFactory : BroadcastReceiver, RemoteViewsService.RemoteViewsFactory,
+ Loader.OnLoadCompleteListener<Cursor?> {
+ private var mContext: Context? = null
+ private var mResources: Resources? = null
+ private var mLastSerialNum = -1
+ private var mLoader: CursorLoader? = null
+ private val mHandler: Handler = Handler()
+ private val executor: ExecutorService = Executors.newSingleThreadExecutor()
+ private var mAppWidgetId = 0
+ private var mDeclinedColor = 0
+ private var mStandardColor = 0
+ private var mAllDayColor = 0
+ private val mTimezoneChanged: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (mLoader != null) {
+ mLoader?.forceLoad()
+ }
+ }
+ }
+
+ private fun createUpdateLoaderRunnable(
+ selection: String,
+ result: PendingResult,
+ version: Int
+ ): Runnable {
+ return object : Runnable {
+ @Override
+ override fun run() {
+ // If there is a newer load request in the queue, skip loading.
+ if (mLoader != null && version >= currentVersion.get()) {
+ val uri: Uri = createLoaderUri()
+ mLoader?.setUri(uri)
+ mLoader?.setSelection(selection)
+ synchronized(mLock) { mLastSerialNum = ++mSerialNum }
+ mLoader?.forceLoad()
+ }
+ result.finish()
+ }
+ }
+ }
+
+ constructor(context: Context, intent: Intent) {
+ mContext = context
+ mResources = context.getResources()
+ mAppWidgetId = intent.getIntExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID
+ )
+ mDeclinedColor = mResources?.getColor(R.color.appwidget_item_declined_color) as Int
+ mStandardColor = mResources?.getColor(R.color.appwidget_item_standard_color) as Int
+ mAllDayColor = mResources?.getColor(R.color.appwidget_item_allday_color) as Int
+ }
+
+ constructor() {
+ // This is being created as part of onReceive
+ }
+
+ @Override
+ override fun onCreate() {
+ val selection = queryForSelection()
+ initLoader(selection)
+ }
+
+ @Override
+ override fun onDataSetChanged() {
+ }
+
+ @Override
+ override fun onDestroy() {
+ if (mLoader != null) {
+ mLoader?.reset()
+ }
+ }
+
+ @Override
+ override fun getLoadingView(): RemoteViews {
+ val views = RemoteViews(mContext?.getPackageName(), R.layout.appwidget_loading)
+ return views
+ }
+
+ @Override
+ override fun getViewAt(position: Int): RemoteViews? {
+ // we use getCount here so that it doesn't return null when empty
+ if (position < 0 || position >= getCount()) {
+ return null
+ }
+ if (mModel == null) {
+ val views = RemoteViews(
+ mContext?.getPackageName(),
+ R.layout.appwidget_loading
+ )
+ val intent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+ mContext,
+ 0,
+ 0,
+ 0,
+ false
+ )
+ views.setOnClickFillInIntent(R.id.appwidget_loading, intent)
+ return views
+ }
+ if (mModel!!.mEventInfos!!.isEmpty() || mModel!!.mRowInfos!!.isEmpty()) {
+ val views = RemoteViews(
+ mContext?.getPackageName(),
+ R.layout.appwidget_no_events
+ )
+ val intent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+ mContext,
+ 0,
+ 0,
+ 0,
+ false
+ )
+ views.setOnClickFillInIntent(R.id.appwidget_no_events, intent)
+ return views
+ }
+ val rowInfo: RowInfo? = mModel?.mRowInfos?.get(position)
+ return if (rowInfo!!.mType == RowInfo!!.TYPE_DAY) {
+ val views = RemoteViews(
+ mContext?.getPackageName(),
+ R.layout.appwidget_day
+ )
+ val dayInfo: DayInfo? = mModel?.mDayInfos?.get(rowInfo!!.mIndex)
+ updateTextView(views, R.id.date, View.VISIBLE, dayInfo!!.mDayLabel)
+ views
+ } else {
+ val views: RemoteViews?
+ val eventInfo: EventInfo? = mModel?.mEventInfos?.get(rowInfo.mIndex)
+ if (eventInfo!!.allDay) {
+ views = RemoteViews(
+ mContext?.getPackageName(),
+ R.layout.widget_all_day_item
+ )
+ } else {
+ views = RemoteViews(mContext?.getPackageName(), R.layout.widget_item)
+ }
+ val displayColor: Int = Utils.getDisplayColorFromColor(eventInfo!!.color)
+ val now: Long = System.currentTimeMillis()
+ if (!eventInfo!!.allDay && eventInfo!!.start <= now && now <= eventInfo!!.end) {
+ views?.setInt(
+ R.id.widget_row, "setBackgroundResource",
+ R.drawable.agenda_item_bg_secondary
+ )
+ } else {
+ views?.setInt(
+ R.id.widget_row, "setBackgroundResource",
+ R.drawable.agenda_item_bg_primary
+ )
+ }
+ if (!eventInfo?.allDay) {
+ updateTextView(views, R.id.`when`, eventInfo?.visibWhen
+ as Int, eventInfo?.`when`)
+ updateTextView(views, R.id.where, eventInfo?.visibWhere
+ as Int, eventInfo?.where)
+ }
+ updateTextView(views, R.id.title, eventInfo?.visibTitle as Int, eventInfo?.title)
+ views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE)
+ val selfAttendeeStatus: Int = eventInfo?.selfAttendeeStatus as Int
+ if (eventInfo!!.allDay) {
+ if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
+ views?.setInt(
+ R.id.agenda_item_color, "setImageResource",
+ R.drawable.widget_chip_not_responded_bg
+ )
+ views?.setInt(R.id.title, "setTextColor", displayColor)
+ } else {
+ views?.setInt(
+ R.id.agenda_item_color, "setImageResource",
+ R.drawable.widget_chip_responded_bg
+ )
+ views?.setInt(R.id.title, "setTextColor", mAllDayColor)
+ }
+ if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
+ // 40% opacity
+ views?.setInt(
+ R.id.agenda_item_color, "setColorFilter",
+ Utils.getDeclinedColorFromColor(displayColor)
+ )
+ } else {
+ views?.setInt(R.id.agenda_item_color, "setColorFilter", displayColor)
+ }
+ } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
+ 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, "setImageResource",
+ R.drawable.widget_chip_responded_bg
+ )
+ // 40% opacity
+ views?.setInt(
+ R.id.agenda_item_color, "setColorFilter",
+ Utils.getDeclinedColorFromColor(displayColor)
+ )
+ } else {
+ views?.setInt(R.id.title, "setTextColor", mStandardColor)
+ views?.setInt(R.id.`when`, "setTextColor", mStandardColor)
+ views?.setInt(R.id.where, "setTextColor", mStandardColor)
+ if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
+ views?.setInt(
+ R.id.agenda_item_color, "setImageResource",
+ R.drawable.widget_chip_not_responded_bg
+ )
+ } else {
+ views?.setInt(
+ R.id.agenda_item_color, "setImageResource",
+ R.drawable.widget_chip_responded_bg
+ )
+ }
+ views?.setInt(R.id.agenda_item_color, "setColorFilter", displayColor)
+ }
+ var start: Long = eventInfo?.start as Long
+ var end: Long = eventInfo?.end as Long
+ // An element in ListView.
+ if (eventInfo!!.allDay) {
+ val tz: String? = Utils.getTimeZone(mContext, null)
+ val recycle = Time()
+ start = Utils.convertAlldayLocalToUTC(recycle, start, tz as String)
+ end = Utils.convertAlldayLocalToUTC(recycle, end, tz as String)
+ }
+ val fillInIntent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+ mContext, eventInfo?.id, start, end, eventInfo?.allDay
+ )
+ views.setOnClickFillInIntent(R.id.widget_row, fillInIntent)
+ views
+ }
+ }
+
+ @Override
+ override fun getViewTypeCount(): Int {
+ return 5
+ }
+
+ @Override
+ override fun getCount(): Int {
+ // if there are no events, we still return 1 to represent the "no
+ // events" view
+ if (mModel == null) {
+ return 1
+ }
+ return Math.max(1, mModel?.mRowInfos?.size as Int)
+ }
+
+ @Override
+ override fun getItemId(position: Int): Long {
+ if (mModel == null || mModel?.mRowInfos?.isEmpty() as Boolean ||
+ position >= getCount()) {
+ return 0
+ }
+ val rowInfo: RowInfo = mModel?.mRowInfos?.get(position) as RowInfo
+ if (rowInfo.mType == RowInfo.TYPE_DAY) {
+ return rowInfo.mIndex.toLong()
+ }
+ val eventInfo: EventInfo = mModel?.mEventInfos?.get(rowInfo.mIndex) as EventInfo
+ val prime: Long = 31
+ var result: Long = 1
+ result = prime * result + (eventInfo.id xor (eventInfo.id ushr 32)) as Int
+ result = prime * result + (eventInfo.start xor (eventInfo.start ushr 32)) as Int
+ return result
+ }
+
+ @Override
+ override fun hasStableIds(): Boolean {
+ return true
+ }
+
+ /**
+ * Query across all calendars for upcoming event instances from now
+ * until some time in the future. Widen the time range that we query by
+ * one day on each end so that we can catch all-day events. All-day
+ * events are stored starting at midnight in UTC but should be included
+ * in the list of events starting at midnight local time. This may fetch
+ * more events than we actually want, so we filter them out later.
+ *
+ * @param selection The selection string for the loader to filter the query with.
+ */
+ fun initLoader(selection: String?) {
+ if (LOGD) Log.d(TAG, "Querying for widget events...")
+
+ // Search for events from now until some time in the future
+ val uri: Uri = createLoaderUri()
+ mLoader = CursorLoader(
+ mContext, uri, EVENT_PROJECTION, selection, null,
+ EVENT_SORT_ORDER
+ )
+ mLoader?.setUpdateThrottle(WIDGET_UPDATE_THROTTLE.toLong())
+ synchronized(mLock) { mLastSerialNum = ++mSerialNum }
+ mLoader?.registerListener(mAppWidgetId, this)
+ mLoader?.startLoading()
+ }
+
+ /**
+ * This gets the selection string for the loader. This ends up doing a query in the
+ * shared preferences.
+ */
+ private fun queryForSelection(): String {
+ return if (Utils.getHideDeclinedEvents(mContext)) EVENT_SELECTION_HIDE_DECLINED
+ else EVENT_SELECTION
+ }
+
+ /**
+ * @return The uri for the loader
+ */
+ private fun createLoaderUri(): Uri {
+ val now: Long = System.currentTimeMillis()
+ // Add a day on either side to catch all-day events
+ val begin: Long = now - DateUtils.DAY_IN_MILLIS
+ val end: Long =
+ now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS
+ return Uri.withAppendedPath(
+ Instances.CONTENT_URI,
+ begin.toString() + "/" + end
+ )
+ }
+
+ /**
+ * Calculates and returns the next time we should push widget updates.
+ */
+ private fun calculateUpdateTime(
+ model: CalendarAppWidgetModel,
+ now: Long,
+ timeZone: String
+ ): Long {
+ // Make sure an update happens at midnight or earlier
+ var minUpdateTime = getNextMidnightTimeMillis(timeZone)
+ for (event in model.mEventInfos) {
+ val start: Long
+ val end: Long
+ start = event.start
+ end = event.end
+
+ // We want to update widget when we enter/exit time range of an event.
+ if (now < start) {
+ minUpdateTime = Math.min(minUpdateTime, start)
+ } else if (now < end) {
+ minUpdateTime = Math.min(minUpdateTime, end)
+ }
+ }
+ return minUpdateTime
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
+ * .content.Loader, java.lang.Object)
+ */
+ @Override
+ override fun onLoadComplete(loader: Loader<Cursor?>?, cursor: Cursor?) {
+ if (cursor == null) {
+ return
+ }
+ // If a newer update has happened since we started clean up and
+ // return
+ synchronized(mLock) {
+ if (cursor.isClosed()) {
+ Log.wtf(TAG, "Got a closed cursor from onLoadComplete")
+ return
+ }
+ if (mLastSerialNum != mSerialNum) {
+ return
+ }
+ val now: Long = System.currentTimeMillis()
+ val tz: String? = Utils.getTimeZone(mContext, mTimezoneChanged)
+
+ // Copy it to a local static cursor.
+ val matrixCursor: MatrixCursor? = Utils.matrixCursorFromCursor(cursor)
+ try {
+ mModel = buildAppWidgetModel(mContext, matrixCursor, tz)
+ } finally {
+ if (matrixCursor != null) {
+ matrixCursor?.close()
+ }
+ if (cursor != null) {
+ cursor?.close()
+ }
+ }
+
+ // Schedule an alarm to wake ourselves up for the next update.
+ // We also cancel
+ // all existing wake-ups because PendingIntents don't match
+ // against extras.
+ var triggerTime = calculateUpdateTime(mModel as CalendarAppWidgetModel,
+ now, tz as String)
+
+ // If no next-update calculated, or bad trigger time in past,
+ // schedule
+ // update about six hours from now.
+ if (triggerTime < now) {
+ Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now))
+ triggerTime = now + UPDATE_TIME_NO_EVENTS
+ }
+ val alertManager: AlarmManager = mContext
+ ?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val pendingUpdate: PendingIntent = CalendarAppWidgetProvider
+ .getUpdateIntent(mContext)
+ alertManager.cancel(pendingUpdate)
+ alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate)
+ val time = Time(Utils.getTimeZone(mContext, null))
+ time.setToNow()
+ if (time.normalize(true) !== sLastUpdateTime) {
+ val time2 = Time(Utils.getTimeZone(mContext, null))
+ time2.set(sLastUpdateTime)
+ time2.normalize(true)
+ if (time.year !== time2.year || time.yearDay !== time2.yearDay) {
+ val updateIntent = Intent(
+ Utils.getWidgetUpdateAction(mContext as Context)
+ )
+ mContext?.sendBroadcast(updateIntent)
+ }
+ sLastUpdateTime = time.toMillis(true)
+ }
+ val widgetManager: AppWidgetManager = AppWidgetManager.getInstance(mContext)
+ if (widgetManager == null) {
+ return
+ }
+ if (mAppWidgetId == -1) {
+ val ids: IntArray = widgetManager.getAppWidgetIds(
+ CalendarAppWidgetProvider
+ .getComponentName(mContext)
+ )
+ widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list)
+ } else {
+ widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list)
+ }
+ }
+ }
+
+ @Override
+ override fun onReceive(context: Context?, intent: Intent) {
+ if (LOGD) Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString())
+ mContext = context
+
+ // We cannot do any queries from the UI thread, so push the 'selection' query
+ // to a background thread. However the implementation of the latter query
+ // (cursor loading) uses CursorLoader which must be initiated from the UI thread,
+ // so there is some convoluted handshaking here.
+ //
+ // Note that as currently implemented, this must run in a single threaded executor
+ // or else the loads may be run out of order.
+ //
+ // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously
+ // in the background thread. All the handshaking going on here between the UI and
+ // background thread with using goAsync, mHandler, and CursorLoader is confusing.
+ val result: PendingResult = goAsync()
+ executor.submit(object : Runnable {
+ @Override
+ override fun run() {
+ // We always complete queryForSelection() even if the load task ends up being
+ // canceled because of a more recent one. Optimizing this to allow
+ // canceling would require keeping track of all the PendingResults
+ // (from goAsync) to abort them. Defer this until it becomes a problem.
+ val selection = queryForSelection()
+ if (mLoader == null) {
+ mAppWidgetId = -1
+ mHandler.post(object : Runnable {
+ @Override
+ override fun run() {
+ initLoader(selection)
+ result.finish()
+ }
+ })
+ } else {
+ mHandler.post(
+ createUpdateLoaderRunnable(
+ selection, result,
+ currentVersion.incrementAndGet()
+ )
+ )
+ }
+ }
+ })
+ }
+
+ internal companion object {
+ private const val LOGD = false
+
+ // Suppress unnecessary logging about update time. Need to be static as this object is
+ // re-instantiated frequently.
+ // TODO: It seems loadData() is called via onCreate() four times, which should mean
+ // unnecessary CalendarFactory object is created and dropped. It is not efficient.
+ private var sLastUpdateTime = UPDATE_TIME_NO_EVENTS
+ private var mModel: CalendarAppWidgetModel? = null
+ private val mLock: Object = Object()
+
+ @Volatile
+ private var mSerialNum = 0
+ private val currentVersion: AtomicInteger = AtomicInteger(0)
+
+ /* @VisibleForTesting */
+ @JvmStatic protected fun buildAppWidgetModel(
+ context: Context?,
+ cursor: Cursor?,
+ timeZone: String?
+ ): CalendarAppWidgetModel {
+ val model = CalendarAppWidgetModel(context as Context, timeZone)
+ model.buildFromCursor(cursor as Cursor, timeZone)
+ return model
+ }
+
+ @JvmStatic private fun getNextMidnightTimeMillis(timezone: String): Long {
+ val time = Time()
+ time.setToNow()
+ time.monthDay++
+ time.hour = 0
+ time.minute = 0
+ time.second = 0
+ val midnightDeviceTz: Long = time.normalize(true)
+ time.timezone = timezone
+ time.setToNow()
+ time.monthDay++
+ time.hour = 0
+ time.minute = 0
+ time.second = 0
+ val midnightHomeTz: Long = time.normalize(true)
+ return Math.min(midnightDeviceTz, midnightHomeTz)
+ }
+
+ @JvmStatic fun updateTextView(
+ views: RemoteViews,
+ id: Int,
+ visibility: Int,
+ string: String?
+ ) {
+ views.setViewVisibility(id, visibility)
+ if (visibility == View.VISIBLE) {
+ views.setTextViewText(id, string)
+ }
+ }
+ }
+ }
+} \ No newline at end of file