Snap for 8730993 from 5729362442e88937f0d28853772c4b9cee5feddf to mainline-tzdata3-release

Change-Id: I2a26c5ce71d74fc7586f63170449e41942bfe402
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index 3c6477b..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,56 +0,0 @@
-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
new file mode 100644
index 0000000..dce26a4
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,53 @@
+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 fed61b0..c9c5a04 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,8 +38,7 @@
     <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-permission android:name="android.permission.POST_NOTIFICATIONS" />
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"></uses-sdk>
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29"></uses-sdk>
 
 
     <application android:name="CalendarApplication"
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
new file mode 100644
index 0000000..cec6a40
--- /dev/null
+++ b/src/com/android/calendar/AllInOneActivity.java
@@ -0,0 +1,1062 @@
+/*
+ * 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
deleted file mode 100644
index 6c2e825..0000000
--- a/src/com/android/calendar/AllInOneActivity.kt
+++ /dev/null
@@ -1,1065 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..c6e0a2b
--- /dev/null
+++ b/src/com/android/calendar/AsyncQueryServiceHelper.java
@@ -0,0 +1,70 @@
+/*
+ * 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
deleted file mode 100644
index 4797330..0000000
--- a/src/com/android/calendar/AsyncQueryServiceHelper.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.kt b/src/com/android/calendar/CalendarApplication.java
similarity index 70%
rename from src/com/android/calendar/CalendarApplication.kt
rename to src/com/android/calendar/CalendarApplication.java
index 445d725..d0ca469 100644
--- a/src/com/android/calendar/CalendarApplication.kt
+++ b/src/com/android/calendar/CalendarApplication.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -13,18 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.calendar
 
-import android.app.Application
+package com.android.calendar;
 
-class CalendarApplication : Application() {
-    override fun onCreate() {
-        super.onCreate()
+import android.app.Application;
+
+public class CalendarApplication extends Application {
+    @Override
+    public void 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
new file mode 100644
index 0000000..02456fd
--- /dev/null
+++ b/src/com/android/calendar/CalendarBackupAgent.java
@@ -0,0 +1,41 @@
+/*
+ * 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
deleted file mode 100644
index f3e230a..0000000
--- a/src/com/android/calendar/CalendarBackupAgent.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..37286f2
--- /dev/null
+++ b/src/com/android/calendar/CalendarController.java
@@ -0,0 +1,713 @@
+/*
+ * 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
deleted file mode 100644
index 16ee8fd..0000000
--- a/src/com/android/calendar/CalendarController.kt
+++ /dev/null
@@ -1,743 +0,0 @@
-/*
- * 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.java
new file mode 100644
index 0000000..5c8456f
--- /dev/null
+++ b/src/com/android/calendar/CalendarData.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+public final class CalendarData {
+    static final String[] 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" };
+}
diff --git a/src/com/android/calendar/CalendarData.kt b/src/com/android/calendar/CalendarData.kt
deleted file mode 100644
index 7370f2e..0000000
--- a/src/com/android/calendar/CalendarData.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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
-
-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")
-
-    @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
new file mode 100644
index 0000000..0238c32
--- /dev/null
+++ b/src/com/android/calendar/CalendarUtils.java
@@ -0,0 +1,356 @@
+/*
+ * 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
deleted file mode 100644
index 94ca723..0000000
--- a/src/com/android/calendar/CalendarUtils.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..524268f
--- /dev/null
+++ b/src/com/android/calendar/CalendarViewAdapter.java
@@ -0,0 +1,409 @@
+/*
+ * 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
deleted file mode 100644
index 2fe1027..0000000
--- a/src/com/android/calendar/CalendarViewAdapter.kt
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..a9fb39e
--- /dev/null
+++ b/src/com/android/calendar/DayFragment.java
@@ -0,0 +1,256 @@
+/*
+ * 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
deleted file mode 100644
index 39e92f5..0000000
--- a/src/com/android/calendar/DayFragment.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..461ab31
--- /dev/null
+++ b/src/com/android/calendar/DayOfMonthDrawable.java
@@ -0,0 +1,77 @@
+/*
+ * 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
deleted file mode 100644
index e348b5a..0000000
--- a/src/com/android/calendar/DayOfMonthDrawable.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..2fc00b3
--- /dev/null
+++ b/src/com/android/calendar/DayView.java
@@ -0,0 +1,4008 @@
+/*
+ * 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
deleted file mode 100644
index 4262163..0000000
--- a/src/com/android/calendar/DayView.kt
+++ /dev/null
@@ -1,3990 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..095e43e
--- /dev/null
+++ b/src/com/android/calendar/Event.java
@@ -0,0 +1,642 @@
+/*
+ * 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
deleted file mode 100644
index c21a0a0..0000000
--- a/src/com/android/calendar/Event.kt
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..cdecb49
--- /dev/null
+++ b/src/com/android/calendar/EventGeometry.java
@@ -0,0 +1,170 @@
+/*
+ * 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
deleted file mode 100644
index 43fc3e7..0000000
--- a/src/com/android/calendar/EventGeometry.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..626e099
--- /dev/null
+++ b/src/com/android/calendar/EventInfoActivity.java
@@ -0,0 +1,190 @@
+/*
+ * 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
deleted file mode 100644
index c0a1b9c..0000000
--- a/src/com/android/calendar/EventInfoActivity.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..0aa83d0
--- /dev/null
+++ b/src/com/android/calendar/EventInfoFragment.java
@@ -0,0 +1,877 @@
+/*
+ * 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
deleted file mode 100644
index fcc27fc..0000000
--- a/src/com/android/calendar/EventInfoFragment.kt
+++ /dev/null
@@ -1,787 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..d34b1c7
--- /dev/null
+++ b/src/com/android/calendar/EventLoader.java
@@ -0,0 +1,286 @@
+/*
+ * 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
deleted file mode 100644
index a05e8a2..0000000
--- a/src/com/android/calendar/EventLoader.kt
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..a42f07e
--- /dev/null
+++ b/src/com/android/calendar/GeneralPreferences.java
@@ -0,0 +1,400 @@
+/*
+ * 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
deleted file mode 100644
index dd4c955..0000000
--- a/src/com/android/calendar/GeneralPreferences.kt
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * 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.java
new file mode 100644
index 0000000..3970115
--- /dev/null
+++ b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
@@ -0,0 +1,41 @@
+/*
+**
+** Copyright 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,
+** See the License for the specific language governing permissions and
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** limitations under the License.
+*/
+
+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);
+
+        Intent intent = getIntent();
+        if (intent != null) {
+            // Pass it on to the next Activity.
+            try {
+                startNextMatchingActivity(intent);
+            } catch (ActivityNotFoundException ex) {
+                // no browser installed? Just drop it.
+            }
+        }
+        finish();
+    }
+}
diff --git a/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt b/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt
deleted file mode 100644
index d2fe77f..0000000
--- a/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-** 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.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** See the License for the specific language governing permissions and
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** limitations under the License.
-*/
-package com.android.calendar
-
-import android.app.Activity
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.os.Bundle
-
-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 (ex: ActivityNotFoundException) {
-                // no browser installed? Just drop it.
-            }
-        }
-        finish()
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/calendar/MultiStateButton.java b/src/com/android/calendar/MultiStateButton.java
new file mode 100644
index 0000000..8034b28
--- /dev/null
+++ b/src/com/android/calendar/MultiStateButton.java
@@ -0,0 +1,192 @@
+/*
+ * 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
deleted file mode 100644
index f86ee6b..0000000
--- a/src/com/android/calendar/MultiStateButton.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..a59d3f4
--- /dev/null
+++ b/src/com/android/calendar/OtherPreferences.java
@@ -0,0 +1,210 @@
+/*
+ * 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
deleted file mode 100644
index f1507cc..0000000
--- a/src/com/android/calendar/OtherPreferences.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..981e7af
--- /dev/null
+++ b/src/com/android/calendar/StickyHeaderListView.java
@@ -0,0 +1,395 @@
+/*
+ * 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
deleted file mode 100644
index 37733b7..0000000
--- a/src/com/android/calendar/StickyHeaderListView.kt
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * 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.java
new file mode 100644
index 0000000..0e89286
--- /dev/null
+++ b/src/com/android/calendar/UpgradeReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import android.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);
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/UpgradeReceiver.kt b/src/com/android/calendar/UpgradeReceiver.kt
deleted file mode 100644
index ab2de1d..0000000
--- a/src/com/android/calendar/UpgradeReceiver.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-
-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
new file mode 100644
index 0000000..cc55c99
--- /dev/null
+++ b/src/com/android/calendar/Utils.java
@@ -0,0 +1,1499 @@
+/*
+ * 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
deleted file mode 100644
index ef78048..0000000
--- a/src/com/android/calendar/Utils.kt
+++ /dev/null
@@ -1,1577 +0,0 @@
-/*
- * 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.kt b/src/com/android/calendar/alerts/AlarmManagerInterface.java
similarity index 71%
rename from src/com/android/calendar/alerts/AlarmManagerInterface.kt
rename to src/com/android/calendar/alerts/AlarmManagerInterface.java
index be9d86f..3c66434 100644
--- a/src/com/android/calendar/alerts/AlarmManagerInterface.kt
+++ b/src/com/android/calendar/alerts/AlarmManagerInterface.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.calendar.alerts
 
-import android.app.PendingIntent
+package com.android.calendar.alerts;
+
+import android.app.PendingIntent;
 
 /**
  * AlarmManager abstracted to an interface for testability.
  */
-interface AlarmManagerInterface {
-    operator fun set(type: Int, triggerAtMillis: Long, operation: PendingIntent?)
-}
\ No newline at end of file
+public interface AlarmManagerInterface {
+    public void set(int type, long triggerAtMillis, PendingIntent operation);
+}
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.java b/src/com/android/calendar/alerts/AlarmScheduler.java
new file mode 100644
index 0000000..9782822
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlarmScheduler.java
@@ -0,0 +1,322 @@
+/*
+ * 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
deleted file mode 100644
index c93bbb0..0000000
--- a/src/com/android/calendar/alerts/AlarmScheduler.kt
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..ce80cae
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertReceiver.java
@@ -0,0 +1,123 @@
+/*
+ * 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
deleted file mode 100644
index 21afa90..0000000
--- a/src/com/android/calendar/alerts/AlertReceiver.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..d2c994d
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertService.java
@@ -0,0 +1,202 @@
+/*
+ * 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
deleted file mode 100644
index bc1b4e0..0000000
--- a/src/com/android/calendar/alerts/AlertService.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..b9aaec2
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertUtils.java
@@ -0,0 +1,108 @@
+/*
+ * 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
deleted file mode 100644
index 18b7e7d..0000000
--- a/src/com/android/calendar/alerts/AlertUtils.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..1ec3c22
--- /dev/null
+++ b/src/com/android/calendar/alerts/DismissAlarmsService.java
@@ -0,0 +1,136 @@
+/*
+ * 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
deleted file mode 100644
index 88683d3..0000000
--- a/src/com/android/calendar/alerts/DismissAlarmsService.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..27b3e16
--- /dev/null
+++ b/src/com/android/calendar/alerts/GlobalDismissManager.java
@@ -0,0 +1,84 @@
+/*
+ * 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
deleted file mode 100644
index 4cf0bc0..0000000
--- a/src/com/android/calendar/alerts/GlobalDismissManager.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..3a9b0b2
--- /dev/null
+++ b/src/com/android/calendar/alerts/InitAlarmsService.java
@@ -0,0 +1,62 @@
+/*
+ * 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
deleted file mode 100644
index 0ac8a47..0000000
--- a/src/com/android/calendar/alerts/InitAlarmsService.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.kt b/src/com/android/calendar/alerts/NotificationMgr.java
similarity index 65%
rename from src/com/android/calendar/alerts/NotificationMgr.kt
rename to src/com/android/calendar/alerts/NotificationMgr.java
index 609b814..0ab475c 100644
--- a/src/com/android/calendar/alerts/NotificationMgr.kt
+++ b/src/com/android/calendar/alerts/NotificationMgr.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -13,28 +13,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.calendar.alerts
 
-import com.android.calendar.alerts.AlertService.NotificationWrapper
+package com.android.calendar.alerts;
 
-abstract class NotificationMgr {
-    abstract fun notify(id: Int, notification: NotificationWrapper?)
-    abstract fun cancel(id: Int)
+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);
 
     /**
      * Don't actually use the notification framework's cancelAll since the SyncAdapter
      * might post notifications and we don't want to affect those.
      */
-    fun cancelAll() {
-        cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS)
+    public void cancelAll() {
+        cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS);
     }
 
     /**
      * Cancels IDs between the specified bounds, inclusively.
      */
-    fun cancelAllBetween(from: Int, to: Int) {
-        for (i in from..to) {
-            cancel(i)
+    public void cancelAllBetween(int from, int to) {
+        for (int i = from; i <= to; i++) {
+            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
new file mode 100644
index 0000000..3d291d0
--- /dev/null
+++ b/src/com/android/calendar/alerts/QuickResponseActivity.java
@@ -0,0 +1,108 @@
+/*
+ * 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
deleted file mode 100644
index afccaff..0000000
--- a/src/com/android/calendar/alerts/QuickResponseActivity.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..45a1bea
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekAdapter.java
@@ -0,0 +1,406 @@
+/*
+ * 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
deleted file mode 100644
index c67b356..0000000
--- a/src/com/android/calendar/month/MonthByWeekAdapter.kt
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..f8a518d
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekFragment.java
@@ -0,0 +1,494 @@
+/*
+ * 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
deleted file mode 100644
index 9fe9fe4..0000000
--- a/src/com/android/calendar/month/MonthByWeekFragment.kt
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..f2621cc
--- /dev/null
+++ b/src/com/android/calendar/month/MonthListView.java
@@ -0,0 +1,51 @@
+/*
+ * 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
deleted file mode 100644
index 1facb4c..0000000
--- a/src/com/android/calendar/month/MonthListView.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..e1c78c6
--- /dev/null
+++ b/src/com/android/calendar/month/MonthWeekEventsView.java
@@ -0,0 +1,1110 @@
+/*
+ * 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
deleted file mode 100644
index e4b1549..0000000
--- a/src/com/android/calendar/month/MonthWeekEventsView.kt
+++ /dev/null
@@ -1,1061 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..2efae6a
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleDayPickerFragment.java
@@ -0,0 +1,612 @@
+/*
+ * 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
deleted file mode 100644
index 01fcbac..0000000
--- a/src/com/android/calendar/month/SimpleDayPickerFragment.kt
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..4d0c09f
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeekView.java
@@ -0,0 +1,551 @@
+/*
+ * 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
deleted file mode 100644
index 4d1298d..0000000
--- a/src/com/android/calendar/month/SimpleWeekView.kt
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..d29b262
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeeksAdapter.java
@@ -0,0 +1,302 @@
+/*
+ * 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
deleted file mode 100644
index 164f05c..0000000
--- a/src/com/android/calendar/month/SimpleWeeksAdapter.kt
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..a989e18
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetModel.java
@@ -0,0 +1,430 @@
+/*
+ * 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
deleted file mode 100644
index 440d178..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..3a69efd
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
@@ -0,0 +1,230 @@
+/*
+ * 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
deleted file mode 100644
index b3539f2..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..ec702c7
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetService.java
@@ -0,0 +1,626 @@
+/*
+ * 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
deleted file mode 100644
index 114fdf1..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetService.kt
+++ /dev/null
@@ -1,665 +0,0 @@
-/*
- * 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