[automerger skipped] [DO NOT MERGE] am: ae487ada19 -s ours am: d66a732b4a -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Calendar/+/15831943

Change-Id: I272aa06a3ec65b1b2bd9f0ab75780fbbe14986f0
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..d732f94
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,55 @@
+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",
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+
+    product_specific: true,
+
+    aaptflags: ["--auto-add-overlay"],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index dce26a4..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include res dir from chips
-chips_dir := ../../../frameworks/opt/chips/res
-color_picker_dir := ../../../frameworks/opt/colorpicker/res
-timezonepicker_dir := ../../../frameworks/opt/timezonepicker/res
-res_dirs := $(chips_dir) $(color_picker_dir) $(timezonepicker_dir) res
-src_dirs := src
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.calendar.*
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under,$(src_dirs))
-
-# bundled
-#LOCAL_STATIC_JAVA_LIBRARIES += \
-#        android-common \
-#        libchips \
-#        calendar-common
-
-# unbundled
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        android-common \
-        libchips \
-        colorpicker \
-        android-opt-timezonepicker \
-        androidx.legacy_legacy-support-v4 \
-        calendar-common
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := Calendar
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/NOTICE
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-LOCAL_PRODUCT_MODULE := true
-
-LOCAL_AAPT_FLAGS := --auto-add-overlay
-LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
-LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker
-LOCAL_AAPT_FLAGS += --extra-packages com.android.timezonepicker
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c9c5a04..686be7a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,7 +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-sdk android:minSdkVersion="19" android:targetSdkVersion="29"></uses-sdk>
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"></uses-sdk>
 
 
     <application android:name="CalendarApplication"
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
deleted file mode 100644
index cec6a40..0000000
--- a/src/com/android/calendar/AllInOneActivity.java
+++ /dev/null
@@ -1,1062 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ObjectAnimator;
-import android.app.ActionBar;
-import android.app.ActionBar.Tab;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.AsyncQueryHandler;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
-import android.widget.TextView;
-
-import com.android.calendar.CalendarController.EventHandler;
-import com.android.calendar.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.month.MonthByWeekFragment;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-
-public class AllInOneActivity extends Activity implements EventHandler,
-        OnSharedPreferenceChangeListener, ActionBar.TabListener,
-        ActionBar.OnNavigationListener {
-    private static final String TAG = "AllInOneActivity";
-    private static final boolean DEBUG = false;
-    private static final String EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment";
-    private static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
-    private static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
-    private static final String BUNDLE_KEY_RESTORE_VIEW = "key_restore_view";
-    private static final String BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts";
-    private static final int HANDLER_KEY = 0;
-
-    // Indices of buttons for the drop down menu (tabs replacement)
-    // Must match the strings in the array buttons_list in arrays.xml and the
-    // OnNavigationListener
-    private static final int BUTTON_DAY_INDEX = 0;
-    private static final int BUTTON_WEEK_INDEX = 1;
-    private static final int BUTTON_MONTH_INDEX = 2;
-    private static final int BUTTON_AGENDA_INDEX = 3;
-
-    private CalendarController mController;
-    private static boolean mIsMultipane;
-    private static boolean mIsTabletConfig;
-    private boolean mOnSaveInstanceStateCalled = false;
-    private boolean mBackToPreviousView = false;
-    private ContentResolver mContentResolver;
-    private int mPreviousView;
-    private int mCurrentView;
-    private boolean mPaused = true;
-    private boolean mUpdateOnResume = false;
-    private boolean mHideControls = false;
-    private boolean mShowSideViews = true;
-    private boolean mShowWeekNum = false;
-    private TextView mHomeTime;
-    private TextView mDateRange;
-    private TextView mWeekTextView;
-    private View mMiniMonth;
-    private View mCalendarsList;
-    private View mMiniMonthContainer;
-    private View mSecondaryPane;
-    private String mTimeZone;
-    private boolean mShowCalendarControls;
-    private boolean mShowEventInfoFullScreen;
-    private int mWeekNum;
-    private int mCalendarControlsAnimationTime;
-    private int mControlsAnimateWidth;
-    private int mControlsAnimateHeight;
-
-    private long mViewEventId = -1;
-    private long mIntentEventStartMillis = -1;
-    private long mIntentEventEndMillis = -1;
-    private int mIntentAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE;
-    private boolean mIntentAllDay = false;
-
-    // Action bar and Navigation bar (left side of Action bar)
-    private ActionBar mActionBar;
-    private ActionBar.Tab mDayTab;
-    private ActionBar.Tab mWeekTab;
-    private ActionBar.Tab mMonthTab;
-    private MenuItem mControlsMenu;
-    private Menu mOptionsMenu;
-    private CalendarViewAdapter mActionBarMenuSpinnerAdapter;
-    private QueryHandler mHandler;
-    private boolean mCheckForAccounts = true;
-
-    private String mHideString;
-    private String mShowString;
-
-    DayOfMonthDrawable mDayOfMonthIcon;
-
-    int mOrientation;
-
-    // Params for animating the controls on the right
-    private LayoutParams mControlsParams;
-    private LinearLayout.LayoutParams mVerticalControlsParams;
-
-    private final AnimatorListener mSlideAnimationDoneListener = new AnimatorListener() {
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-
-        @Override
-        public void onAnimationEnd(android.animation.Animator animation) {
-            int visibility = mShowSideViews ? View.VISIBLE : View.GONE;
-            mMiniMonth.setVisibility(visibility);
-            mCalendarsList.setVisibility(visibility);
-            mMiniMonthContainer.setVisibility(visibility);
-        }
-
-        @Override
-        public void onAnimationRepeat(android.animation.Animator animation) {
-        }
-
-        @Override
-        public void onAnimationStart(android.animation.Animator animation) {
-        }
-    };
-
-    private class QueryHandler extends AsyncQueryHandler {
-        public QueryHandler(ContentResolver cr) {
-            super(cr);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            mCheckForAccounts = false;
-            try {
-                // If the query didn't return a cursor for some reason return
-                if (cursor == null || cursor.getCount() > 0 || isFinishing()) {
-                    return;
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-
-            Bundle options = new Bundle();
-            options.putCharSequence("introMessage",
-                    getResources().getString(R.string.create_an_account_desc));
-            options.putBoolean("allowSkip", true);
-
-            AccountManager am = AccountManager.get(AllInOneActivity.this);
-            am.addAccount("com.google", CalendarContract.AUTHORITY, null, options,
-                    AllInOneActivity.this,
-                    new AccountManagerCallback<Bundle>() {
-                        @Override
-                        public void run(AccountManagerFuture<Bundle> future) {
-                        }
-                    }, null);
-        }
-    }
-
-    private final Runnable mHomeTimeUpdater = new Runnable() {
-        @Override
-        public void run() {
-            mTimeZone = Utils.getTimeZone(AllInOneActivity.this, mHomeTimeUpdater);
-            updateSecondaryTitleFields(-1);
-            AllInOneActivity.this.invalidateOptionsMenu();
-            Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
-        }
-    };
-
-    // runs every midnight/time changes and refreshes the today icon
-    private final Runnable mTimeChangesUpdater = new Runnable() {
-        @Override
-        public void run() {
-            mTimeZone = Utils.getTimeZone(AllInOneActivity.this, mHomeTimeUpdater);
-            AllInOneActivity.this.invalidateOptionsMenu();
-            Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
-        }
-    };
-
-
-    // Create an observer so that we can update the views whenever a
-    // Calendar event changes.
-    private final ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return true;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            eventsChanged();
-        }
-    };
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        String action = intent.getAction();
-        if (DEBUG)
-            Log.d(TAG, "New intent received " + intent.toString());
-        // Don't change the date if we're just returning to the app's home
-        if (Intent.ACTION_VIEW.equals(action)
-                && !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false)) {
-            long millis = parseViewAction(intent);
-            if (millis == -1) {
-                millis = Utils.timeFromIntentInMillis(intent);
-            }
-            if (millis != -1 && mViewEventId == -1 && mController != null) {
-                Time time = new Time(mTimeZone);
-                time.set(millis);
-                time.normalize(true);
-                mController.sendEvent(this, EventType.GO_TO, time, time, -1, ViewType.CURRENT);
-            }
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) {
-            mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS);
-        }
-        // Launch add google account if this is first time and there are no
-        // accounts yet
-        if (mCheckForAccounts) {
-
-            mHandler = new QueryHandler(this.getContentResolver());
-            mHandler.startQuery(0, null, Calendars.CONTENT_URI, new String[] {
-                Calendars._ID
-            }, null, null /* selection args */, null /* sort order */);
-        }
-
-        // This needs to be created before setContentView
-        mController = CalendarController.getInstance(this);
-
-
-        // Get time from intent or icicle
-        long timeMillis = -1;
-        int viewType = -1;
-        final Intent intent = getIntent();
-        if (icicle != null) {
-            timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME);
-            viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1);
-        } else {
-            String action = intent.getAction();
-            if (Intent.ACTION_VIEW.equals(action)) {
-                // Open EventInfo later
-                timeMillis = parseViewAction(intent);
-            }
-
-            if (timeMillis == -1) {
-                timeMillis = Utils.timeFromIntentInMillis(intent);
-            }
-        }
-
-        if (viewType == -1 || viewType > ViewType.MAX_VALUE) {
-            viewType = Utils.getViewTypeFromIntentAndSharedPref(this);
-        }
-        mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater);
-        Time t = new Time(mTimeZone);
-        t.set(timeMillis);
-
-        if (DEBUG) {
-            if (icicle != null && intent != null) {
-                Log.d(TAG, "both, icicle:" + icicle.toString() + "  intent:" + intent.toString());
-            } else {
-                Log.d(TAG, "not both, icicle:" + icicle + " intent:" + intent);
-            }
-        }
-
-        Resources res = getResources();
-        mHideString = res.getString(R.string.hide_controls);
-        mShowString = res.getString(R.string.show_controls);
-        mOrientation = res.getConfiguration().orientation;
-        if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-            mControlsAnimateWidth = (int)res.getDimension(R.dimen.calendar_controls_width);
-            if (mControlsParams == null) {
-                mControlsParams = new LayoutParams(mControlsAnimateWidth, 0);
-            }
-            mControlsParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-        } else {
-            // Make sure width is in between allowed min and max width values
-            mControlsAnimateWidth = Math.max(res.getDisplayMetrics().widthPixels * 45 / 100,
-                    (int)res.getDimension(R.dimen.min_portrait_calendar_controls_width));
-            mControlsAnimateWidth = Math.min(mControlsAnimateWidth,
-                    (int)res.getDimension(R.dimen.max_portrait_calendar_controls_width));
-        }
-
-        mControlsAnimateHeight = (int)res.getDimension(R.dimen.calendar_controls_height);
-
-        mHideControls = true;
-        mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config);
-        mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config);
-        mShowCalendarControls =
-                Utils.getConfigBool(this, R.bool.show_calendar_controls);
-        mShowEventInfoFullScreen =
-            Utils.getConfigBool(this, R.bool.show_event_info_full_screen);
-        mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time);
-        Utils.setAllowWeekForDetailView(mIsMultipane);
-
-        // setContentView must be called before configureActionBar
-        setContentView(R.layout.all_in_one);
-
-        if (mIsTabletConfig) {
-            mDateRange = (TextView) findViewById(R.id.date_bar);
-            mWeekTextView = (TextView) findViewById(R.id.week_num);
-        } else {
-            mDateRange = (TextView) getLayoutInflater().inflate(R.layout.date_range_title, null);
-        }
-
-        // configureActionBar auto-selects the first tab you add, so we need to
-        // call it before we set up our own fragments to make sure it doesn't
-        // overwrite us
-        configureActionBar(viewType);
-
-        mHomeTime = (TextView) findViewById(R.id.home_time);
-        mMiniMonth = findViewById(R.id.mini_month);
-        if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) {
-            mMiniMonth.setLayoutParams(new RelativeLayout.LayoutParams(mControlsAnimateWidth,
-                    mControlsAnimateHeight));
-        }
-        mCalendarsList = findViewById(R.id.calendar_list);
-        mMiniMonthContainer = findViewById(R.id.mini_month_container);
-        mSecondaryPane = findViewById(R.id.secondary_pane);
-
-        // Must register as the first activity because this activity can modify
-        // the list of event handlers in it's handle method. This affects who
-        // the rest of the handlers the controller dispatches to are.
-        mController.registerFirstEventHandler(HANDLER_KEY, this);
-
-        initFragments(timeMillis, viewType, icicle);
-
-        // Listen for changes that would require this to be refreshed
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
-        prefs.registerOnSharedPreferenceChangeListener(this);
-
-        mContentResolver = getContentResolver();
-    }
-
-    private long parseViewAction(final Intent intent) {
-        long timeMillis = -1;
-        Uri data = intent.getData();
-        if (data != null && data.isHierarchical()) {
-            List<String> path = data.getPathSegments();
-            if (path.size() == 2 && path.get(0).equals("events")) {
-                try {
-                    mViewEventId = Long.valueOf(data.getLastPathSegment());
-                    if (mViewEventId != -1) {
-                        mIntentEventStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0);
-                        mIntentEventEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0);
-                        mIntentAttendeeResponse = intent.getIntExtra(
-                            ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
-                        mIntentAllDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false);
-                        timeMillis = mIntentEventStartMillis;
-                    }
-                } catch (NumberFormatException e) {
-                    // Ignore if mViewEventId can't be parsed
-                }
-            }
-        }
-        return timeMillis;
-    }
-
-    private void configureActionBar(int viewType) {
-        createButtonsSpinner(viewType, mIsTabletConfig);
-        if (mIsMultipane) {
-            mActionBar.setDisplayOptions(
-                    ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
-        } else {
-            mActionBar.setDisplayOptions(0);
-        }
-    }
-
-    private void createButtonsSpinner(int viewType, boolean tabletConfig) {
-        // If tablet configuration , show spinner with no dates
-        mActionBarMenuSpinnerAdapter = new CalendarViewAdapter (this, viewType, !tabletConfig);
-        mActionBar = getActionBar();
-        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
-        mActionBar.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this);
-        switch (viewType) {
-            case ViewType.AGENDA:
-                break;
-            case ViewType.DAY:
-                mActionBar.setSelectedNavigationItem(BUTTON_DAY_INDEX);
-                break;
-            case ViewType.WEEK:
-                mActionBar.setSelectedNavigationItem(BUTTON_WEEK_INDEX);
-                break;
-            case ViewType.MONTH:
-                mActionBar.setSelectedNavigationItem(BUTTON_MONTH_INDEX);
-                break;
-            default:
-                mActionBar.setSelectedNavigationItem(BUTTON_DAY_INDEX);
-                break;
-       }
-    }
-    // Clear buttons used in the agenda view
-    private void clearOptionsMenu() {
-        if (mOptionsMenu == null) {
-            return;
-        }
-        MenuItem cancelItem = mOptionsMenu.findItem(R.id.action_cancel);
-        if (cancelItem != null) {
-            cancelItem.setVisible(false);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        // Check if the upgrade code has ever been run. If not, force a sync just this one time.
-        Utils.trySyncAndDisableUpgradeReceiver(this);
-
-        // Must register as the first activity because this activity can modify
-        // the list of event handlers in it's handle method. This affects who
-        // the rest of the handlers the controller dispatches to are.
-        mController.registerFirstEventHandler(HANDLER_KEY, this);
-
-        mOnSaveInstanceStateCalled = false;
-        mContentResolver.registerContentObserver(CalendarContract.Events.CONTENT_URI,
-                true, mObserver);
-        if (mUpdateOnResume) {
-            initFragments(mController.getTime(), mController.getViewType(), null);
-            mUpdateOnResume = false;
-        }
-        Time t = new Time(mTimeZone);
-        t.set(mController.getTime());
-        mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT,
-                mController.getDateFlags(), null, null);
-        // Make sure the drop-down menu will get its date updated at midnight
-        if (mActionBarMenuSpinnerAdapter != null) {
-            mActionBarMenuSpinnerAdapter.refresh(this);
-        }
-
-        if (mControlsMenu != null) {
-            mControlsMenu.setTitle(mHideControls ? mShowString : mHideString);
-        }
-        mPaused = false;
-
-        if (mViewEventId != -1 && mIntentEventStartMillis != -1 && mIntentEventEndMillis != -1) {
-            long currentMillis = System.currentTimeMillis();
-            long selectedTime = -1;
-            if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) {
-                selectedTime = currentMillis;
-            }
-            mController.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, mViewEventId,
-                    mIntentEventStartMillis, mIntentEventEndMillis, -1, -1,
-                    EventInfo.buildViewExtraLong(mIntentAttendeeResponse,mIntentAllDay),
-                    selectedTime);
-            mViewEventId = -1;
-            mIntentEventStartMillis = -1;
-            mIntentEventEndMillis = -1;
-            mIntentAllDay = false;
-        }
-        Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone);
-        // Make sure the today icon is up to date
-        invalidateOptionsMenu();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        mController.deregisterEventHandler(HANDLER_KEY);
-        mPaused = true;
-        mHomeTime.removeCallbacks(mHomeTimeUpdater);
-        if (mActionBarMenuSpinnerAdapter != null) {
-            mActionBarMenuSpinnerAdapter.onPause();
-        }
-        mContentResolver.unregisterContentObserver(mObserver);
-        if (isFinishing()) {
-            // Stop listening for changes that would require this to be refreshed
-            SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
-            prefs.unregisterOnSharedPreferenceChangeListener(this);
-        }
-        // FRAG_TODO save highlighted days of the week;
-        if (mController.getViewType() != ViewType.EDIT) {
-            Utils.setDefaultView(this, mController.getViewType());
-        }
-        Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater);
-    }
-
-    @Override
-    protected void onUserLeaveHint() {
-        mController.sendEvent(this, EventType.USER_HOME, null, null, -1, ViewType.CURRENT);
-        super.onUserLeaveHint();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        mOnSaveInstanceStateCalled = true;
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
-        prefs.unregisterOnSharedPreferenceChangeListener(this);
-
-        mController.deregisterAllEventHandlers();
-
-        CalendarController.removeInstance(this);
-    }
-
-    private void initFragments(long timeMillis, int viewType, Bundle icicle) {
-        if (DEBUG) {
-            Log.d(TAG, "Initializing to " + timeMillis + " for view " + viewType);
-        }
-        FragmentTransaction ft = getFragmentManager().beginTransaction();
-
-        if (mShowCalendarControls) {
-            Fragment miniMonthFrag = new MonthByWeekFragment(timeMillis, true);
-            ft.replace(R.id.mini_month, miniMonthFrag);
-            mController.registerEventHandler(R.id.mini_month, (EventHandler) miniMonthFrag);
-        }
-        if (!mShowCalendarControls || viewType == ViewType.EDIT) {
-            mMiniMonth.setVisibility(View.GONE);
-            mCalendarsList.setVisibility(View.GONE);
-        }
-
-        EventInfo info = null;
-        if (viewType == ViewType.EDIT) {
-            mPreviousView = GeneralPreferences.getSharedPreferences(this).getInt(
-                    GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
-
-            long eventId = -1;
-            Intent intent = getIntent();
-            Uri data = intent.getData();
-            if (data != null) {
-                try {
-                    eventId = Long.parseLong(data.getLastPathSegment());
-                } catch (NumberFormatException e) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Create new event");
-                    }
-                }
-            } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) {
-                eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID);
-            }
-
-            long begin = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1);
-            long end = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1);
-            info = new EventInfo();
-            if (end != -1) {
-                info.endTime = new Time();
-                info.endTime.set(end);
-            }
-            if (begin != -1) {
-                info.startTime = new Time();
-                info.startTime.set(begin);
-            }
-            info.id = eventId;
-            // We set the viewtype so if the user presses back when they are
-            // done editing the controller knows we were in the Edit Event
-            // screen. Likewise for eventId
-            mController.setViewType(viewType);
-            mController.setEventId(eventId);
-        } else {
-            mPreviousView = viewType;
-        }
-
-        setMainPane(ft, R.id.main_pane, viewType, timeMillis, true);
-        ft.commit(); // this needs to be after setMainPane()
-
-        Time t = new Time(mTimeZone);
-        t.set(timeMillis);
-        if (viewType != ViewType.EDIT) {
-            mController.sendEvent(this, EventType.GO_TO, t, null, -1, viewType);
-        }
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mCurrentView == ViewType.EDIT || mBackToPreviousView) {
-            mController.sendEvent(this, EventType.GO_TO, null, null, -1, mPreviousView);
-        } else {
-            super.onBackPressed();
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-        mOptionsMenu = menu;
-        getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu);
-
-        // Hide the "show/hide controls" button if this is a phone
-        // or the view type is "Month".
-
-        mControlsMenu = menu.findItem(R.id.action_hide_controls);
-        if (!mShowCalendarControls) {
-            if (mControlsMenu != null) {
-                mControlsMenu.setVisible(false);
-                mControlsMenu.setEnabled(false);
-            }
-        } else if (mControlsMenu != null && mController != null
-                    && (mController.getViewType() == ViewType.MONTH)) {
-            mControlsMenu.setVisible(false);
-            mControlsMenu.setEnabled(false);
-        } else if (mControlsMenu != null){
-            mControlsMenu.setTitle(mHideControls ? mShowString : mHideString);
-        }
-
-        MenuItem menuItem = menu.findItem(R.id.action_today);
-        if (Utils.isJellybeanOrLater()) {
-            // replace the default top layer drawable of the today icon with a
-            // custom drawable that shows the day of the month of today
-            LayerDrawable icon = (LayerDrawable) menuItem.getIcon();
-            Utils.setTodayIcon(icon, this, mTimeZone);
-        } else {
-            menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Time t = null;
-        int viewType = ViewType.CURRENT;
-        long extras = CalendarController.EXTRA_GOTO_TIME;
-        final int itemId = item.getItemId();
-        if (itemId == R.id.action_today) {
-            viewType = ViewType.CURRENT;
-            t = new Time(mTimeZone);
-            t.setToNow();
-            extras |= CalendarController.EXTRA_GOTO_TODAY;
-        } else if (itemId == R.id.action_hide_controls) {
-            mHideControls = !mHideControls;
-            item.setTitle(mHideControls ? mShowString : mHideString);
-            if (!mHideControls) {
-                mMiniMonth.setVisibility(View.VISIBLE);
-                mCalendarsList.setVisibility(View.VISIBLE);
-                mMiniMonthContainer.setVisibility(View.VISIBLE);
-            }
-            final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this, "controlsOffset",
-                    mHideControls ? 0 : mControlsAnimateWidth,
-                    mHideControls ? mControlsAnimateWidth : 0);
-            slideAnimation.setDuration(mCalendarControlsAnimationTime);
-            ObjectAnimator.setFrameDelay(0);
-            slideAnimation.start();
-            return true;
-        } else {
-            Log.d(TAG, "Unsupported itemId: " + itemId);
-            return true;
-        }
-        mController.sendEvent(this, EventType.GO_TO, t, null, t, -1, viewType, extras, null, null);
-        return true;
-    }
-
-    /**
-     * Sets the offset of the controls on the right for animating them off/on
-     * screen. ProGuard strips this if it's not in proguard.flags
-     *
-     * @param controlsOffset The current offset in pixels
-     */
-    public void setControlsOffset(int controlsOffset) {
-        if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-            mMiniMonth.setTranslationX(controlsOffset);
-            mCalendarsList.setTranslationX(controlsOffset);
-            mControlsParams.width = Math.max(0, mControlsAnimateWidth - controlsOffset);
-            mMiniMonthContainer.setLayoutParams(mControlsParams);
-        } else {
-            mMiniMonth.setTranslationY(controlsOffset);
-            mCalendarsList.setTranslationY(controlsOffset);
-            if (mVerticalControlsParams == null) {
-                mVerticalControlsParams = new LinearLayout.LayoutParams(
-                        LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight);
-            }
-            mVerticalControlsParams.height = Math.max(0, mControlsAnimateHeight - controlsOffset);
-            mMiniMonthContainer.setLayoutParams(mVerticalControlsParams);
-        }
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) {
-            if (mPaused) {
-                mUpdateOnResume = true;
-            } else {
-                initFragments(mController.getTime(), mController.getViewType(), null);
-            }
-        }
-    }
-
-    private void setMainPane(
-            FragmentTransaction ft, int viewId, int viewType, long timeMillis, boolean force) {
-        if (mOnSaveInstanceStateCalled) {
-            return;
-        }
-        if (!force && mCurrentView == viewType) {
-            return;
-        }
-
-        // Remove this when transition to and from month view looks fine.
-        boolean doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH;
-        FragmentManager fragmentManager = getFragmentManager();
-
-        if (viewType != mCurrentView) {
-            // The rules for this previous view are different than the
-            // controller's and are used for intercepting the back button.
-            if (mCurrentView != ViewType.EDIT && mCurrentView > 0) {
-                mPreviousView = mCurrentView;
-            }
-            mCurrentView = viewType;
-        }
-        // Create new fragment
-        Fragment frag = null;
-        Fragment secFrag = null;
-        switch (viewType) {
-            case ViewType.AGENDA:
-                break;
-            case ViewType.DAY:
-                if (mActionBar != null && (mActionBar.getSelectedTab() != mDayTab)) {
-                    mActionBar.selectTab(mDayTab);
-                }
-                if (mActionBarMenuSpinnerAdapter != null) {
-                    mActionBar.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX);
-                }
-                frag = new DayFragment(timeMillis, 1);
-                break;
-            case ViewType.MONTH:
-                if (mActionBar != null && (mActionBar.getSelectedTab() != mMonthTab)) {
-                    mActionBar.selectTab(mMonthTab);
-                }
-                if (mActionBarMenuSpinnerAdapter != null) {
-                    mActionBar.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX);
-                }
-                frag = new MonthByWeekFragment(timeMillis, false);
-                break;
-            case ViewType.WEEK:
-            default:
-                if (mActionBar != null && (mActionBar.getSelectedTab() != mWeekTab)) {
-                    mActionBar.selectTab(mWeekTab);
-                }
-                if (mActionBarMenuSpinnerAdapter != null) {
-                    mActionBar.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX);
-                }
-                frag = new DayFragment(timeMillis, 7);
-                break;
-        }
-
-        // Update the current view so that the menu can update its look according to the
-        // current view.
-        if (mActionBarMenuSpinnerAdapter != null) {
-            mActionBarMenuSpinnerAdapter.setMainView(viewType);
-            if (!mIsTabletConfig) {
-                mActionBarMenuSpinnerAdapter.setTime(timeMillis);
-            }
-        }
-
-
-        // Show date only on tablet configurations in views different than Agenda
-        if (!mIsTabletConfig) {
-            mDateRange.setVisibility(View.GONE);
-        } else {
-            mDateRange.setVisibility(View.GONE);
-        }
-
-        // Clear unnecessary buttons from the option menu when switching from the agenda view
-        if (viewType != ViewType.AGENDA) {
-            clearOptionsMenu();
-        }
-
-        boolean doCommit = false;
-        if (ft == null) {
-            doCommit = true;
-            ft = fragmentManager.beginTransaction();
-        }
-
-        if (doTransition) {
-            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
-        }
-
-        ft.replace(viewId, frag);
-        if (DEBUG) {
-            Log.d(TAG, "Adding handler with viewId " + viewId + " and type " + viewType);
-        }
-        // If the key is already registered this will replace it
-        mController.registerEventHandler(viewId, (EventHandler) frag);
-
-        if (doCommit) {
-            if (DEBUG) {
-                Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing());
-            }
-            ft.commit();
-        }
-    }
-
-    private void setTitleInActionBar(EventInfo event) {
-        if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) {
-            return;
-        }
-
-        final long start = event.startTime.toMillis(false /* use isDst */);
-        final long end;
-        if (event.endTime != null) {
-            end = event.endTime.toMillis(false /* use isDst */);
-        } else {
-            end = start;
-        }
-
-        final String msg = Utils.formatDateRange(this, start, end, (int) event.extraLong);
-        CharSequence oldDate = mDateRange.getText();
-        mDateRange.setText(msg);
-        updateSecondaryTitleFields(event.selectedTime != null ? event.selectedTime.toMillis(true)
-                : start);
-        if (!TextUtils.equals(oldDate, msg)) {
-            mDateRange.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-            if (mShowWeekNum && mWeekTextView != null) {
-                mWeekTextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-            }
-        }
-    }
-
-    private void updateSecondaryTitleFields(long visibleMillisSinceEpoch) {
-        mShowWeekNum = Utils.getShowWeekNumber(this);
-        mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater);
-        if (visibleMillisSinceEpoch != -1) {
-            int weekNum = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this);
-            mWeekNum = weekNum;
-        }
-
-        if (mShowWeekNum && (mCurrentView == ViewType.WEEK) && mIsTabletConfig
-                && mWeekTextView != null) {
-            String weekString = getResources().getQuantityString(R.plurals.weekN, mWeekNum,
-                    mWeekNum);
-            mWeekTextView.setText(weekString);
-            mWeekTextView.setVisibility(View.VISIBLE);
-        } else if (visibleMillisSinceEpoch != -1 && mWeekTextView != null
-                && mCurrentView == ViewType.DAY && mIsTabletConfig) {
-            Time time = new Time(mTimeZone);
-            time.set(visibleMillisSinceEpoch);
-            int julianDay = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff);
-            time.setToNow();
-            int todayJulianDay = Time.getJulianDay(time.toMillis(false), time.gmtoff);
-            String dayString = Utils.getDayOfWeekString(julianDay, todayJulianDay,
-                    visibleMillisSinceEpoch, this);
-            mWeekTextView.setText(dayString);
-            mWeekTextView.setVisibility(View.VISIBLE);
-        } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) {
-            mWeekTextView.setVisibility(View.GONE);
-        }
-
-        if (mHomeTime != null
-                && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK)
-                && !TextUtils.equals(mTimeZone, Time.getCurrentTimezone())) {
-            Time time = new Time(mTimeZone);
-            time.setToNow();
-            long millis = time.toMillis(true);
-            boolean isDST = time.isDst != 0;
-            int flags = DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(this)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            // Formats the time as
-            String timeString = (new StringBuilder(
-                    Utils.formatDateRange(this, millis, millis, flags))).append(" ").append(
-                    TimeZone.getTimeZone(mTimeZone).getDisplayName(
-                            isDST, TimeZone.SHORT, Locale.getDefault())).toString();
-            mHomeTime.setText(timeString);
-            mHomeTime.setVisibility(View.VISIBLE);
-            // Update when the minute changes
-            mHomeTime.removeCallbacks(mHomeTimeUpdater);
-            mHomeTime.postDelayed(
-                    mHomeTimeUpdater,
-                    DateUtils.MINUTE_IN_MILLIS - (millis % DateUtils.MINUTE_IN_MILLIS));
-        } else if (mHomeTime != null) {
-            mHomeTime.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    public long getSupportedEventTypes() {
-        return EventType.GO_TO | EventType.UPDATE_TITLE;
-    }
-
-    @Override
-    public void handleEvent(EventInfo event) {
-        long displayTime = -1;
-        if (event.eventType == EventType.GO_TO) {
-            if ((event.extraLong & CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS) != 0) {
-                mBackToPreviousView = true;
-            } else if (event.viewType != mController.getPreviousViewType()
-                    && event.viewType != ViewType.EDIT) {
-                // Clear the flag is change to a different view type
-                mBackToPreviousView = false;
-            }
-
-            setMainPane(
-                    null, R.id.main_pane, event.viewType, event.startTime.toMillis(false), false);
-            if (mShowCalendarControls) {
-                int animationSize = (mOrientation == Configuration.ORIENTATION_LANDSCAPE) ?
-                        mControlsAnimateWidth : mControlsAnimateHeight;
-                boolean noControlsView = event.viewType == ViewType.MONTH;
-                if (mControlsMenu != null) {
-                    mControlsMenu.setVisible(!noControlsView);
-                    mControlsMenu.setEnabled(!noControlsView);
-                }
-                if (noControlsView || mHideControls) {
-                    // hide minimonth and calendar frag
-                    mShowSideViews = false;
-                    if (!mHideControls) {
-                            final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this,
-                                    "controlsOffset", 0, animationSize);
-                            slideAnimation.addListener(mSlideAnimationDoneListener);
-                            slideAnimation.setDuration(mCalendarControlsAnimationTime);
-                            ObjectAnimator.setFrameDelay(0);
-                            slideAnimation.start();
-                    } else {
-                        mMiniMonth.setVisibility(View.GONE);
-                        mCalendarsList.setVisibility(View.GONE);
-                        mMiniMonthContainer.setVisibility(View.GONE);
-                    }
-                } else {
-                    // show minimonth and calendar frag
-                    mShowSideViews = true;
-                    mMiniMonth.setVisibility(View.VISIBLE);
-                    mCalendarsList.setVisibility(View.VISIBLE);
-                    mMiniMonthContainer.setVisibility(View.VISIBLE);
-                    if (!mHideControls &&
-                            (mController.getPreviousViewType() == ViewType.MONTH)) {
-                        final ObjectAnimator slideAnimation = ObjectAnimator.ofInt(this,
-                                "controlsOffset", animationSize, 0);
-                        slideAnimation.setDuration(mCalendarControlsAnimationTime);
-                        ObjectAnimator.setFrameDelay(0);
-                        slideAnimation.start();
-                    }
-                }
-            }
-            displayTime = event.selectedTime != null ? event.selectedTime.toMillis(true)
-                    : event.startTime.toMillis(true);
-            if (!mIsTabletConfig) {
-                mActionBarMenuSpinnerAdapter.setTime(displayTime);
-            }
-        } else if (event.eventType == EventType.UPDATE_TITLE) {
-            setTitleInActionBar(event);
-            if (!mIsTabletConfig) {
-                mActionBarMenuSpinnerAdapter.setTime(mController.getTime());
-            }
-        }
-        updateSecondaryTitleFields(displayTime);
-    }
-
-    @Override
-    public void eventsChanged() {
-        mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
-    }
-
-    @Override
-    public void onTabSelected(Tab tab, FragmentTransaction ft) {
-        Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing());
-        if (tab == mDayTab && mCurrentView != ViewType.DAY) {
-            mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.DAY);
-        } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) {
-            mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.WEEK);
-        } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) {
-            mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.MONTH);
-        } else {
-            Log.w(TAG, "TabSelected event from unknown tab: "
-                    + (tab == null ? "null" : tab.getText()));
-            Log.w(TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab
-                    + " Week:" + mWeekTab + " Month:" + mMonthTab);
-        }
-    }
-
-    @Override
-    public void onTabReselected(Tab tab, FragmentTransaction ft) {
-    }
-
-    @Override
-    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
-    }
-
-
-    @Override
-    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
-        switch (itemPosition) {
-            case CalendarViewAdapter.DAY_BUTTON_INDEX:
-                if (mCurrentView != ViewType.DAY) {
-                    mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.DAY);
-                }
-                break;
-            case CalendarViewAdapter.WEEK_BUTTON_INDEX:
-                if (mCurrentView != ViewType.WEEK) {
-                    mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.WEEK);
-                }
-                break;
-            case CalendarViewAdapter.MONTH_BUTTON_INDEX:
-                if (mCurrentView != ViewType.MONTH) {
-                    mController.sendEvent(this, EventType.GO_TO, null, null, -1, ViewType.MONTH);
-                }
-                break;
-            case CalendarViewAdapter.AGENDA_BUTTON_INDEX:
-                break;
-            default:
-                Log.w(TAG, "ItemSelected event from unknown button: " + itemPosition);
-                Log.w(TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition +
-                        " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab);
-                break;
-        }
-        return false;
-    }
-}
diff --git a/src/com/android/calendar/AllInOneActivity.kt b/src/com/android/calendar/AllInOneActivity.kt
new file mode 100644
index 0000000..1747bf5
--- /dev/null
+++ b/src/com/android/calendar/AllInOneActivity.kt
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.accounts.AccountManager
+import android.accounts.AccountManagerCallback
+import android.accounts.AccountManagerFuture
+import android.animation.Animator
+import android.animation.Animator.AnimatorListener
+import android.animation.ObjectAnimator
+import android.app.ActionBar
+import android.app.ActionBar.Tab
+import android.app.Activity
+import android.app.Fragment
+import android.app.FragmentManager
+import android.app.FragmentTransaction
+import android.content.AsyncQueryHandler
+import android.content.ContentResolver
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.database.Cursor
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.provider.CalendarContract
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Events
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import android.widget.RelativeLayout.LayoutParams
+import android.widget.TextView
+import com.android.calendar.CalendarController.EventHandler
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.month.MonthByWeekFragment
+import java.util.Locale
+import java.util.TimeZone
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+
+class AllInOneActivity : Activity(), EventHandler, OnSharedPreferenceChangeListener,
+    ActionBar.TabListener, ActionBar.OnNavigationListener {
+    private var mController: CalendarController? = null
+    private var mOnSaveInstanceStateCalled = false
+    private var mBackToPreviousView = false
+    private var mContentResolver: ContentResolver? = null
+    private var mPreviousView = 0
+    private var mCurrentView = 0
+    private var mPaused = true
+    private var mUpdateOnResume = false
+    private var mHideControls = false
+    private var mShowSideViews = true
+    private var mShowWeekNum = false
+    private var mHomeTime: TextView? = null
+    private var mDateRange: TextView? = null
+    private var mWeekTextView: TextView? = null
+    private var mMiniMonth: View? = null
+    private var mCalendarsList: View? = null
+    private var mMiniMonthContainer: View? = null
+    private var mSecondaryPane: View? = null
+    private var mTimeZone: String? = null
+    private var mShowCalendarControls = false
+    private var mShowEventInfoFullScreen = false
+    private var mWeekNum = 0
+    private var mCalendarControlsAnimationTime = 0
+    private var mControlsAnimateWidth = 0
+    private var mControlsAnimateHeight = 0
+    private var mViewEventId: Long = -1
+    private var mIntentEventStartMillis: Long = -1
+    private var mIntentEventEndMillis: Long = -1
+    private var mIntentAttendeeResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+    private var mIntentAllDay = false
+
+    // Action bar and Navigation bar (left side of Action bar)
+    private var mActionBar: ActionBar? = null
+    private val mDayTab: Tab? = null
+    private val mWeekTab: Tab? = null
+    private val mMonthTab: Tab? = null
+    private var mControlsMenu: MenuItem? = null
+    private var mOptionsMenu: Menu? = null
+    private var mActionBarMenuSpinnerAdapter: CalendarViewAdapter? = null
+    private var mHandler: QueryHandler? = null
+    private var mCheckForAccounts = true
+    private var mHideString: String? = null
+    private var mShowString: String? = null
+    var mDayOfMonthIcon: DayOfMonthDrawable? = null
+    var mOrientation = 0
+
+    // Params for animating the controls on the right
+    private var mControlsParams: LayoutParams? = null
+    private var mVerticalControlsParams: LinearLayout.LayoutParams? = null
+    private val mSlideAnimationDoneListener: AnimatorListener = object : AnimatorListener {
+        @Override
+        override fun onAnimationCancel(animation: Animator?) {
+        }
+
+        @Override
+        override fun onAnimationEnd(animation: Animator?) {
+            val visibility: Int = if (mShowSideViews) View.VISIBLE else View.GONE
+            mMiniMonth?.setVisibility(visibility)
+            mCalendarsList?.setVisibility(visibility)
+            mMiniMonthContainer?.setVisibility(visibility)
+        }
+
+        @Override
+        override fun onAnimationRepeat(animation: Animator?) {
+        }
+
+        @Override
+        override fun onAnimationStart(animation: Animator?) {
+        }
+    }
+
+    private inner class QueryHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) {
+        @Override
+        protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) {
+            mCheckForAccounts = false
+            try {
+                // If the query didn't return a cursor for some reason return
+                if (cursor == null || cursor.getCount() > 0 || isFinishing()) {
+                    return
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close()
+                }
+            }
+            val options = Bundle()
+            options.putCharSequence(
+                "introMessage",
+                getResources().getString(R.string.create_an_account_desc)
+            )
+            options.putBoolean("allowSkip", true)
+            val am: AccountManager = AccountManager.get(this@AllInOneActivity)
+            am.addAccount("com.google", CalendarContract.AUTHORITY, null, options,
+                this@AllInOneActivity,
+                    object : AccountManagerCallback<Bundle?> {
+                        @Override
+                        override fun run(future: AccountManagerFuture<Bundle?>?) {
+                        }
+                    }, null
+            )
+        }
+    }
+
+    private val mHomeTimeUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            mTimeZone = Utils.getTimeZone(this@AllInOneActivity, this)
+            updateSecondaryTitleFields(-1)
+            this@AllInOneActivity.invalidateOptionsMenu()
+            Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone)
+        }
+    }
+
+    // runs every midnight/time changes and refreshes the today icon
+    private val mTimeChangesUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            mTimeZone = Utils.getTimeZone(this@AllInOneActivity, mHomeTimeUpdater)
+            this@AllInOneActivity.invalidateOptionsMenu()
+            Utils.setMidnightUpdater(mHandler, this, mTimeZone)
+        }
+    }
+
+    // Create an observer so that we can update the views whenever a
+    // Calendar event changes.
+    private val mObserver: ContentObserver = object : ContentObserver(Handler()) {
+        @Override
+        override fun deliverSelfNotifications(): Boolean {
+            return true
+        }
+
+        @Override
+        override fun onChange(selfChange: Boolean) {
+            eventsChanged()
+        }
+    }
+
+    @Override
+    protected override fun onNewIntent(intent: Intent) {
+        val action: String? = intent.getAction()
+        if (DEBUG) Log.d(TAG, "New intent received " + intent.toString())
+        // Don't change the date if we're just returning to the app's home
+        if (Intent.ACTION_VIEW.equals(action) &&
+            !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false)
+        ) {
+            var millis = parseViewAction(intent)
+            if (millis == -1L) {
+                millis = Utils.timeFromIntentInMillis(intent) as Long
+            }
+            if (millis != -1L && mViewEventId == -1L && mController != null) {
+                val time = Time(mTimeZone)
+                time.set(millis)
+                time.normalize(true)
+                mController?.sendEvent(this as Object?, EventType.GO_TO, time, time, -1,
+                    ViewType.CURRENT)
+            }
+        }
+    }
+
+    @Override
+    protected override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) {
+            mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS)
+        }
+        // Launch add google account if this is first time and there are no
+        // accounts yet
+        if (mCheckForAccounts) {
+            mHandler = QueryHandler(this.getContentResolver())
+            mHandler?.startQuery(
+                0, null, Calendars.CONTENT_URI, arrayOf<String>(
+                    Calendars._ID
+                ), null, null /* selection args */, null /* sort order */
+            )
+        }
+
+        // This needs to be created before setContentView
+        mController = CalendarController.getInstance(this)
+
+        // Get time from intent or icicle
+        var timeMillis: Long = -1
+        var viewType = -1
+        val intent: Intent = getIntent()
+        if (icicle != null) {
+            timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME)
+            viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1)
+        } else {
+            val action: String? = intent.getAction()
+            if (Intent.ACTION_VIEW.equals(action)) {
+                // Open EventInfo later
+                timeMillis = parseViewAction(intent)
+            }
+            if (timeMillis == -1L) {
+                timeMillis = Utils.timeFromIntentInMillis(intent) as Long
+            }
+        }
+        if (viewType == -1 || viewType > ViewType.MAX_VALUE) {
+            viewType = Utils.getViewTypeFromIntentAndSharedPref(this)
+        }
+        mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater)
+        val t = Time(mTimeZone)
+        t.set(timeMillis)
+        if (DEBUG) {
+            if (icicle != null && intent != null) {
+                Log.d(
+                    TAG,
+                    "both, icicle:" + icicle.toString().toString() + "  intent:" + intent.toString()
+                )
+            } else {
+                Log.d(TAG, "not both, icicle:$icicle intent:$intent")
+            }
+        }
+        val res: Resources = getResources()
+        mHideString = res.getString(R.string.hide_controls)
+        mShowString = res.getString(R.string.show_controls)
+        mOrientation = res.getConfiguration().orientation
+        if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            mControlsAnimateWidth = res.getDimension(R.dimen.calendar_controls_width).toInt()
+            if (mControlsParams == null) {
+                mControlsParams = LayoutParams(mControlsAnimateWidth, 0)
+            }
+            mControlsParams?.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                as RelativeLayout.LayoutParams
+        } else {
+            // Make sure width is in between allowed min and max width values
+            mControlsAnimateWidth = Math.max(
+                res.getDisplayMetrics().widthPixels * 45 / 100,
+                res.getDimension(R.dimen.min_portrait_calendar_controls_width).toInt()
+            )
+            mControlsAnimateWidth = Math.min(
+                mControlsAnimateWidth,
+                res.getDimension(R.dimen.max_portrait_calendar_controls_width).toInt()
+            )
+        }
+        mControlsAnimateHeight = res?.getDimension(R.dimen.calendar_controls_height).toInt()
+        mHideControls = true
+        mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config)
+        mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config)
+        mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls)
+        mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen)
+        mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time)
+        Utils.setAllowWeekForDetailView(mIsMultipane)
+
+        // setContentView must be called before configureActionBar
+        setContentView(R.layout.all_in_one)
+        if (mIsTabletConfig) {
+            mDateRange = findViewById(R.id.date_bar) as TextView?
+            mWeekTextView = findViewById(R.id.week_num) as TextView?
+        } else {
+            mDateRange = getLayoutInflater().inflate(R.layout.date_range_title, null) as TextView
+        }
+
+        // configureActionBar auto-selects the first tab you add, so we need to
+        // call it before we set up our own fragments to make sure it doesn't
+        // overwrite us
+        configureActionBar(viewType)
+        mHomeTime = findViewById(R.id.home_time) as TextView?
+        mMiniMonth = findViewById(R.id.mini_month)
+        if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) {
+            mMiniMonth?.setLayoutParams(
+                LayoutParams(
+                    mControlsAnimateWidth,
+                    mControlsAnimateHeight
+                )
+            )
+        }
+        mCalendarsList = findViewById(R.id.calendar_list)
+        mMiniMonthContainer = findViewById(R.id.mini_month_container)
+        mSecondaryPane = findViewById(R.id.secondary_pane)
+
+        // Must register as the first activity because this activity can modify
+        // the list of event handlers in it's handle method. This affects who
+        // the rest of the handlers the controller dispatches to are.
+        mController?.registerFirstEventHandler(HANDLER_KEY, this)
+        initFragments(timeMillis, viewType, icicle)
+
+        // Listen for changes that would require this to be refreshed
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+        prefs?.registerOnSharedPreferenceChangeListener(this)
+        mContentResolver = getContentResolver()
+    }
+
+    private fun parseViewAction(intent: Intent?): Long {
+        var timeMillis: Long = -1
+        val data: Uri? = intent?.getData()
+        if (data != null && data?.isHierarchical()) {
+            val path = data.getPathSegments()
+            if (path?.size == 2 && path!![0].equals("events")) {
+                try {
+                    mViewEventId = data.getLastPathSegment()?.toLong() as Long
+                    if (mViewEventId != -1L) {
+                        mIntentEventStartMillis = intent?.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0)
+                        mIntentEventEndMillis = intent?.getLongExtra(EXTRA_EVENT_END_TIME, 0)
+                        mIntentAttendeeResponse = intent?.getIntExtra(
+                            ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE
+                        )
+                        mIntentAllDay = intent?.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false)
+                            as Boolean
+                        timeMillis = mIntentEventStartMillis
+                    }
+                } catch (e: NumberFormatException) {
+                    // Ignore if mViewEventId can't be parsed
+                }
+            }
+        }
+        return timeMillis
+    }
+
+    private fun configureActionBar(viewType: Int) {
+        createButtonsSpinner(viewType, mIsTabletConfig)
+        if (mIsMultipane) {
+            mActionBar?.setDisplayOptions(
+                ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_SHOW_HOME
+            )
+        } else {
+            mActionBar?.setDisplayOptions(0)
+        }
+    }
+
+    private fun createButtonsSpinner(viewType: Int, tabletConfig: Boolean) {
+        // If tablet configuration , show spinner with no dates
+        mActionBarMenuSpinnerAdapter = CalendarViewAdapter(this, viewType, !tabletConfig)
+        mActionBar = getActionBar()
+        mActionBar?.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST)
+        mActionBar?.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this)
+        when (viewType) {
+            ViewType.AGENDA -> {
+            }
+            ViewType.DAY -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX)
+            ViewType.WEEK -> mActionBar?.setSelectedNavigationItem(BUTTON_WEEK_INDEX)
+            ViewType.MONTH -> mActionBar?.setSelectedNavigationItem(BUTTON_MONTH_INDEX)
+            else -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX)
+        }
+    }
+
+    // Clear buttons used in the agenda view
+    private fun clearOptionsMenu() {
+        if (mOptionsMenu == null) {
+            return
+        }
+        val cancelItem: MenuItem? = mOptionsMenu?.findItem(R.id.action_cancel)
+        if (cancelItem != null) {
+            cancelItem?.setVisible(false)
+        }
+    }
+
+    @Override
+    protected override fun onResume() {
+        super.onResume()
+
+        // Check if the upgrade code has ever been run. If not, force a sync just this one time.
+        Utils.trySyncAndDisableUpgradeReceiver(this)
+
+        // Must register as the first activity because this activity can modify
+        // the list of event handlers in it's handle method. This affects who
+        // the rest of the handlers the controller dispatches to are.
+        mController?.registerFirstEventHandler(HANDLER_KEY, this)
+        mOnSaveInstanceStateCalled = false
+        mContentResolver?.registerContentObserver(
+            CalendarContract.Events.CONTENT_URI,
+            true, mObserver
+        )
+        if (mUpdateOnResume) {
+            initFragments(mController?.time as Long, mController?.viewType as Int, null)
+            mUpdateOnResume = false
+        }
+        val t = Time(mTimeZone)
+        t.set(mController?.time as Long)
+        mController?.sendEvent(
+            this as Object?, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT,
+            mController?.dateFlags as Long, null, null
+        )
+        // Make sure the drop-down menu will get its date updated at midnight
+        if (mActionBarMenuSpinnerAdapter != null) {
+            mActionBarMenuSpinnerAdapter?.refresh(this)
+        }
+        if (mControlsMenu != null) {
+            mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString)
+        }
+        mPaused = false
+        if (mViewEventId != -1L && mIntentEventStartMillis != -1L && mIntentEventEndMillis != -1L) {
+            val currentMillis: Long = System.currentTimeMillis()
+            var selectedTime: Long = -1
+            if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) {
+                selectedTime = currentMillis
+            }
+            mController?.sendEventRelatedEventWithExtra(
+                this as Object?, EventType.VIEW_EVENT, mViewEventId,
+                mIntentEventStartMillis, mIntentEventEndMillis, -1, -1,
+                EventInfo.buildViewExtraLong(mIntentAttendeeResponse, mIntentAllDay),
+                selectedTime
+            )
+            mViewEventId = -1
+            mIntentEventStartMillis = -1
+            mIntentEventEndMillis = -1
+            mIntentAllDay = false
+        }
+        Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone)
+        // Make sure the today icon is up to date
+        invalidateOptionsMenu()
+    }
+
+    @Override
+    protected override fun onPause() {
+        super.onPause()
+        mController?.deregisterEventHandler(HANDLER_KEY)
+        mPaused = true
+        mHomeTime?.removeCallbacks(mHomeTimeUpdater)
+        if (mActionBarMenuSpinnerAdapter != null) {
+            mActionBarMenuSpinnerAdapter?.onPause()
+        }
+        mContentResolver?.unregisterContentObserver(mObserver)
+        if (isFinishing()) {
+            // Stop listening for changes that would require this to be refreshed
+            val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+            prefs?.unregisterOnSharedPreferenceChangeListener(this)
+        }
+        // FRAG_TODO save highlighted days of the week;
+        if (mController?.viewType != ViewType.EDIT) {
+            Utils.setDefaultView(this, mController?.viewType as Int)
+        }
+        Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater)
+    }
+
+    @Override
+    protected override fun onUserLeaveHint() {
+        mController?.sendEvent(this as Object?, EventType.USER_HOME, null, null, -1,
+            ViewType.CURRENT)
+        super.onUserLeaveHint()
+    }
+
+    @Override
+    override fun onSaveInstanceState(outState: Bundle) {
+        mOnSaveInstanceStateCalled = true
+        super.onSaveInstanceState(outState)
+    }
+
+    @Override
+    protected override fun onDestroy() {
+        super.onDestroy()
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this)
+        prefs?.unregisterOnSharedPreferenceChangeListener(this)
+        mController?.deregisterAllEventHandlers()
+        CalendarController.removeInstance(this)
+    }
+
+    private fun initFragments(timeMillis: Long, viewType: Int, icicle: Bundle?) {
+        if (DEBUG) {
+            Log.d(TAG, "Initializing to $timeMillis for view $viewType")
+        }
+        val ft: FragmentTransaction = getFragmentManager().beginTransaction()
+        if (mShowCalendarControls) {
+            val miniMonthFrag: Fragment = MonthByWeekFragment(timeMillis, true)
+            ft.replace(R.id.mini_month, miniMonthFrag)
+            mController?.registerEventHandler(R.id.mini_month, miniMonthFrag as EventHandler)
+        }
+        if (!mShowCalendarControls || viewType == ViewType.EDIT) {
+            mMiniMonth?.setVisibility(View.GONE)
+            mCalendarsList?.setVisibility(View.GONE)
+        }
+        var info: EventInfo? = null
+        if (viewType == ViewType.EDIT) {
+            mPreviousView = GeneralPreferences.getSharedPreferences(this)?.getInt(
+                GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW
+            ) as Int
+            var eventId: Long = -1
+            val intent: Intent = getIntent()
+            val data: Uri? = intent.getData()
+            if (data != null) {
+                try {
+                    eventId = data?.getLastPathSegment()?.toLong() as Long
+                } catch (e: NumberFormatException) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Create new event")
+                    }
+                }
+            } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) {
+                eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID)
+            }
+            val begin: Long = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1)
+            val end: Long = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1)
+            info = EventInfo()
+            if (end != -1L) {
+                info?.endTime = Time()
+                info?.endTime?.set(end)
+            }
+            if (begin != -1L) {
+                info?.startTime = Time()
+                info?.startTime?.set(begin)
+            }
+            info.id = eventId
+            // We set the viewtype so if the user presses back when they are
+            // done editing the controller knows we were in the Edit Event
+            // screen. Likewise for eventId
+            mController?.viewType = viewType
+            mController?.eventId = eventId
+        } else {
+            mPreviousView = viewType
+        }
+        setMainPane(ft, R.id.main_pane, viewType, timeMillis, true)
+        ft.commit() // this needs to be after setMainPane()
+        val t = Time(mTimeZone)
+        t.set(timeMillis)
+        if (viewType != ViewType.EDIT) {
+            mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, -1, viewType)
+        }
+    }
+
+    @Override
+    override fun onBackPressed() {
+        if (mCurrentView == ViewType.EDIT || mBackToPreviousView) {
+            mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, mPreviousView)
+        } else {
+            super.onBackPressed()
+        }
+    }
+
+    @Override
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
+        super.onCreateOptionsMenu(menu)
+        mOptionsMenu = menu
+        getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu)
+
+        // Hide the "show/hide controls" button if this is a phone
+        // or the view type is "Month".
+        mControlsMenu = menu.findItem(R.id.action_hide_controls)
+        if (!mShowCalendarControls) {
+            if (mControlsMenu != null) {
+                mControlsMenu?.setVisible(false)
+                mControlsMenu?.setEnabled(false)
+            }
+        } else if (mControlsMenu != null && mController != null &&
+            mController?.viewType == ViewType.MONTH) {
+            mControlsMenu?.setVisible(false)
+            mControlsMenu?.setEnabled(false)
+        } else if (mControlsMenu != null) {
+            mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString)
+        }
+        val menuItem: MenuItem = menu.findItem(R.id.action_today)
+        if (Utils.isJellybeanOrLater()) {
+            // replace the default top layer drawable of the today icon with a
+            // custom drawable that shows the day of the month of today
+            val icon: LayerDrawable = menuItem.getIcon() as LayerDrawable
+            Utils.setTodayIcon(icon, this, mTimeZone)
+        } else {
+            menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light)
+        }
+        return true
+    }
+
+    @Override
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        var t: Time? = null
+        var viewType: Int = ViewType.CURRENT
+        var extras: Long = CalendarController.EXTRA_GOTO_TIME
+        val itemId: Int = item.getItemId()
+        if (itemId == R.id.action_today) {
+            viewType = ViewType.CURRENT
+            t = Time(mTimeZone)
+            t.setToNow()
+            extras = extras or CalendarController.EXTRA_GOTO_TODAY
+        } else if (itemId == R.id.action_hide_controls) {
+            mHideControls = !mHideControls
+            item.setTitle(if (mHideControls) mShowString else mHideString)
+            if (!mHideControls) {
+                mMiniMonth?.setVisibility(View.VISIBLE)
+                mCalendarsList?.setVisibility(View.VISIBLE)
+                mMiniMonthContainer?.setVisibility(View.VISIBLE)
+            }
+            val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+                this, "controlsOffset",
+                if (mHideControls) 0 else mControlsAnimateWidth,
+                if (mHideControls) mControlsAnimateWidth else 0
+            )
+            slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+            ObjectAnimator.setFrameDelay(0)
+            slideAnimation.start()
+            return true
+        } else {
+            Log.d(TAG, "Unsupported itemId: $itemId")
+            return true
+        }
+        mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, t, -1,
+            viewType, extras, null, null)
+        return true
+    }
+
+    /**
+     * Sets the offset of the controls on the right for animating them off/on
+     * screen. ProGuard strips this if it's not in proguard.flags
+     *
+     * @param controlsOffset The current offset in pixels
+     */
+    fun setControlsOffset(controlsOffset: Int) {
+        if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            mMiniMonth?.setTranslationX(controlsOffset.toFloat())
+            mCalendarsList?.setTranslationX(controlsOffset.toFloat())
+            mControlsParams?.width = Math.max(0, mControlsAnimateWidth - controlsOffset)
+            mMiniMonthContainer?.setLayoutParams(mControlsParams)
+        } else {
+            mMiniMonth?.setTranslationY(controlsOffset.toFloat())
+            mCalendarsList?.setTranslationY(controlsOffset.toFloat())
+            if (mVerticalControlsParams == null) {
+                mVerticalControlsParams = LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight
+                ) as LinearLayout.LayoutParams?
+            }
+            mVerticalControlsParams?.height = Math.max(0, mControlsAnimateHeight - controlsOffset)
+            mMiniMonthContainer?.setLayoutParams(mVerticalControlsParams)
+        }
+    }
+
+    @Override
+    override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String) {
+        if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) {
+            if (mPaused) {
+                mUpdateOnResume = true
+            } else {
+                initFragments(mController?.time as Long, mController?.viewType as Int, null)
+            }
+        }
+    }
+
+    private fun setMainPane(
+        ft: FragmentTransaction?,
+        viewId: Int,
+        viewType: Int,
+        timeMillis: Long,
+        force: Boolean
+    ) {
+        var ft: FragmentTransaction? = ft
+        if (mOnSaveInstanceStateCalled) {
+            return
+        }
+        if (!force && mCurrentView == viewType) {
+            return
+        }
+
+        // Remove this when transition to and from month view looks fine.
+        val doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH
+        val fragmentManager: FragmentManager = getFragmentManager()
+        if (viewType != mCurrentView) {
+            // The rules for this previous view are different than the
+            // controller's and are used for intercepting the back button.
+            if (mCurrentView != ViewType.EDIT && mCurrentView > 0) {
+                mPreviousView = mCurrentView
+            }
+            mCurrentView = viewType
+        }
+        // Create new fragment
+        var frag: Fragment? = null
+        val secFrag: Fragment? = null
+        when (viewType) {
+            ViewType.AGENDA -> {
+            }
+            ViewType.DAY -> {
+                if (mActionBar != null && mActionBar?.getSelectedTab() != mDayTab) {
+                    mActionBar?.selectTab(mDayTab)
+                }
+                if (mActionBarMenuSpinnerAdapter != null) {
+                    mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX)
+                }
+                frag = DayFragment(timeMillis, 1)
+            }
+            ViewType.MONTH -> {
+                if (mActionBar != null && mActionBar?.getSelectedTab() != mMonthTab) {
+                    mActionBar?.selectTab(mMonthTab)
+                }
+                if (mActionBarMenuSpinnerAdapter != null) {
+                    mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX)
+                }
+                frag = MonthByWeekFragment(timeMillis, false)
+            }
+            ViewType.WEEK -> {
+                if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) {
+                    mActionBar?.selectTab(mWeekTab)
+                }
+                if (mActionBarMenuSpinnerAdapter != null) {
+                    mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX)
+                }
+                frag = DayFragment(timeMillis, 7)
+            }
+            else -> {
+                if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) {
+                    mActionBar?.selectTab(mWeekTab)
+                }
+                if (mActionBarMenuSpinnerAdapter != null) {
+                    mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX)
+                }
+                frag = DayFragment(timeMillis, 7)
+            }
+        }
+
+        // Update the current view so that the menu can update its look according to the
+        // current view.
+        if (mActionBarMenuSpinnerAdapter != null) {
+            mActionBarMenuSpinnerAdapter?.setMainView(viewType)
+            if (!mIsTabletConfig) {
+                mActionBarMenuSpinnerAdapter?.setTime(timeMillis)
+            }
+        }
+
+        // Show date only on tablet configurations in views different than Agenda
+        if (!mIsTabletConfig) {
+            mDateRange?.setVisibility(View.GONE)
+        } else {
+            mDateRange?.setVisibility(View.GONE)
+        }
+
+        // Clear unnecessary buttons from the option menu when switching from the agenda view
+        if (viewType != ViewType.AGENDA) {
+            clearOptionsMenu()
+        }
+        var doCommit = false
+        if (ft == null) {
+            doCommit = true
+            ft = fragmentManager.beginTransaction()
+        }
+        if (doTransition) {
+            ft?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+        }
+        ft?.replace(viewId, frag)
+        if (DEBUG) {
+            Log.d(TAG, "Adding handler with viewId $viewId and type $viewType")
+        }
+        // If the key is already registered this will replace it
+        mController?.registerEventHandler(viewId, frag as EventHandler?)
+        if (doCommit) {
+            if (DEBUG) {
+                Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing())
+            }
+            ft?.commit()
+        }
+    }
+
+    private fun setTitleInActionBar(event: EventInfo) {
+        if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) {
+            return
+        }
+        val start: Long? = event?.startTime?.toMillis(false /* use isDst */)
+        val end: Long?
+        end = if (event.endTime != null) {
+            event?.endTime?.toMillis(false /* use isDst */)
+        } else {
+            start
+        }
+        val msg: String? = Utils.formatDateRange(this,
+            start as Long,
+            end as Long,
+            event.extraLong.toInt()
+        )
+        val oldDate: CharSequence? = mDateRange?.getText()
+        mDateRange?.setText(msg)
+        updateSecondaryTitleFields(if (event?.selectedTime != null)
+            event?.selectedTime?.toMillis(true) as Long else start)
+        if (!TextUtils.equals(oldDate, msg)) {
+            mDateRange?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+            if (mShowWeekNum && mWeekTextView != null) {
+                mWeekTextView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+            }
+        }
+    }
+
+    private fun updateSecondaryTitleFields(visibleMillisSinceEpoch: Long) {
+        mShowWeekNum = Utils.getShowWeekNumber(this)
+        mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater)
+        if (visibleMillisSinceEpoch != -1L) {
+            val weekNum: Int = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this)
+            mWeekNum = weekNum
+        }
+        if (mShowWeekNum && mCurrentView == ViewType.WEEK && mIsTabletConfig &&
+            mWeekTextView != null
+        ) {
+            val weekString: String = getResources().getQuantityString(
+                R.plurals.weekN, mWeekNum,
+                mWeekNum
+            )
+            mWeekTextView?.setText(weekString)
+            mWeekTextView?.setVisibility(View.VISIBLE)
+        } else if (visibleMillisSinceEpoch != -1L && mWeekTextView != null &&
+            mCurrentView == ViewType.DAY && mIsTabletConfig) {
+            val time = Time(mTimeZone)
+            time.set(visibleMillisSinceEpoch)
+            val julianDay: Int = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff)
+            time.setToNow()
+            val todayJulianDay: Int = Time.getJulianDay(time.toMillis(false), time.gmtoff)
+            val dayString: String = Utils.getDayOfWeekString(
+                julianDay,
+                todayJulianDay,
+                visibleMillisSinceEpoch,
+                this
+            )
+            mWeekTextView?.setText(dayString)
+            mWeekTextView?.setVisibility(View.VISIBLE)
+        } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) {
+            mWeekTextView?.setVisibility(View.GONE)
+        }
+        if (mHomeTime != null && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) &&
+            !TextUtils.equals(mTimeZone, Time.getCurrentTimezone())
+        ) {
+            val time = Time(mTimeZone)
+            time.setToNow()
+            val millis: Long = time.toMillis(true)
+            val isDST = time.isDst !== 0
+            var flags: Int = DateUtils.FORMAT_SHOW_TIME
+            if (DateFormat.is24HourFormat(this)) {
+                flags = flags or DateUtils.FORMAT_24HOUR
+            }
+            // Formats the time as
+            val timeString: String = StringBuilder(
+                Utils.formatDateRange(this, millis, millis, flags)
+            ).append(" ").append(
+                TimeZone.getTimeZone(mTimeZone).getDisplayName(
+                    isDST, TimeZone.SHORT, Locale.getDefault()
+                )
+            ).toString()
+            mHomeTime?.setText(timeString)
+            mHomeTime?.setVisibility(View.VISIBLE)
+            // Update when the minute changes
+            mHomeTime?.removeCallbacks(mHomeTimeUpdater)
+            mHomeTime?.postDelayed(
+                mHomeTimeUpdater,
+                DateUtils.MINUTE_IN_MILLIS - millis % DateUtils.MINUTE_IN_MILLIS
+            )
+        } else if (mHomeTime != null) {
+            mHomeTime?.setVisibility(View.GONE)
+        }
+    }
+
+    @get:Override override val supportedEventTypes: Long
+        get() = EventType.GO_TO or EventType.UPDATE_TITLE
+
+    @Override
+    override fun handleEvent(event: EventInfo?) {
+        var displayTime: Long = -1
+        if (event?.eventType == EventType.GO_TO) {
+            if (event?.extraLong and CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS != 0L) {
+                mBackToPreviousView = true
+            } else if (event?.viewType != mController?.previousViewType &&
+                event?.viewType != ViewType.EDIT
+            ) {
+                // Clear the flag is change to a different view type
+                mBackToPreviousView = false
+            }
+            setMainPane(
+                null, R.id.main_pane, event?.viewType, event?.startTime?.toMillis(false)
+                    as Long, false
+            )
+            if (mShowCalendarControls) {
+                val animationSize =
+                    if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) mControlsAnimateWidth
+                    else mControlsAnimateHeight
+                val noControlsView = event?.viewType == ViewType.MONTH
+                if (mControlsMenu != null) {
+                    mControlsMenu?.setVisible(!noControlsView)
+                    mControlsMenu?.setEnabled(!noControlsView)
+                }
+                if (noControlsView || mHideControls) {
+                    // hide minimonth and calendar frag
+                    mShowSideViews = false
+                    if (!mHideControls) {
+                        val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+                            this,
+                            "controlsOffset", 0, animationSize
+                        )
+                        slideAnimation.addListener(mSlideAnimationDoneListener)
+                        slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+                        ObjectAnimator.setFrameDelay(0)
+                        slideAnimation.start()
+                    } else {
+                        mMiniMonth?.setVisibility(View.GONE)
+                        mCalendarsList?.setVisibility(View.GONE)
+                        mMiniMonthContainer?.setVisibility(View.GONE)
+                    }
+                } else {
+                    // show minimonth and calendar frag
+                    mShowSideViews = true
+                    mMiniMonth?.setVisibility(View.VISIBLE)
+                    mCalendarsList?.setVisibility(View.VISIBLE)
+                    mMiniMonthContainer?.setVisibility(View.VISIBLE)
+                    if (!mHideControls &&
+                        mController?.previousViewType == ViewType.MONTH
+                    ) {
+                        val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt(
+                            this,
+                            "controlsOffset", animationSize, 0
+                        )
+                        slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong())
+                        ObjectAnimator.setFrameDelay(0)
+                        slideAnimation.start()
+                    }
+                }
+            }
+            displayTime =
+                if (event?.selectedTime != null) event?.selectedTime?.toMillis(true) as Long
+                else event?.startTime?.toMillis(true) as Long
+            if (!mIsTabletConfig) {
+                mActionBarMenuSpinnerAdapter?.setTime(displayTime)
+            }
+        } else if (event?.eventType == EventType.UPDATE_TITLE) {
+            setTitleInActionBar(event as CalendarController.EventInfo)
+            if (!mIsTabletConfig) {
+                mActionBarMenuSpinnerAdapter?.setTime(mController?.time as Long)
+            }
+        }
+        updateSecondaryTitleFields(displayTime)
+    }
+
+    @Override
+    override fun eventsChanged() {
+        mController?.sendEvent(this as Object?, EventType.EVENTS_CHANGED, null, null, -1,
+            ViewType.CURRENT)
+    }
+
+    @Override
+    override fun onTabSelected(tab: Tab?, ft: FragmentTransaction?) {
+        Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing())
+        if (tab == mDayTab && mCurrentView != ViewType.DAY) {
+            mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY)
+        } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) {
+            mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK)
+        } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) {
+            mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH)
+        } else {
+            Log.w(
+                TAG, "TabSelected event from unknown tab: " +
+                    if (tab == null) "null" else tab.getText()
+            )
+            Log.w(
+                TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab +
+                    " Week:" + mWeekTab + " Month:" + mMonthTab
+            )
+        }
+    }
+
+    @Override
+    override fun onTabReselected(tab: Tab?, ft: FragmentTransaction?) {
+    }
+
+    @Override
+    override fun onTabUnselected(tab: Tab?, ft: FragmentTransaction?) {
+    }
+
+    @Override
+    override fun onNavigationItemSelected(itemPosition: Int, itemId: Long): Boolean {
+        when (itemPosition) {
+            CalendarViewAdapter.DAY_BUTTON_INDEX -> if (mCurrentView != ViewType.DAY) {
+                mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+                    ViewType.DAY)
+            }
+            CalendarViewAdapter.WEEK_BUTTON_INDEX -> if (mCurrentView != ViewType.WEEK) {
+                mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+                    ViewType.WEEK)
+            }
+            CalendarViewAdapter.MONTH_BUTTON_INDEX -> if (mCurrentView != ViewType.MONTH) {
+                mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1,
+                    ViewType.MONTH)
+            }
+            CalendarViewAdapter.AGENDA_BUTTON_INDEX -> {
+            }
+            else -> {
+                Log.w(TAG, "ItemSelected event from unknown button: $itemPosition")
+                Log.w(
+                    TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition +
+                        " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab
+                )
+            }
+        }
+        return false
+    }
+
+    companion object {
+        private const val TAG = "AllInOneActivity"
+        private const val DEBUG = false
+        private const val EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment"
+        private const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time"
+        private const val BUNDLE_KEY_EVENT_ID = "key_event_id"
+        private const val BUNDLE_KEY_RESTORE_VIEW = "key_restore_view"
+        private const val BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts"
+        private const val HANDLER_KEY = 0
+
+        // Indices of buttons for the drop down menu (tabs replacement)
+        // Must match the strings in the array buttons_list in arrays.xml and the
+        // OnNavigationListener
+        private const val BUTTON_DAY_INDEX = 0
+        private const val BUTTON_WEEK_INDEX = 1
+        private const val BUTTON_MONTH_INDEX = 2
+        private const val BUTTON_AGENDA_INDEX = 3
+        private var mIsMultipane = false
+        private var mIsTabletConfig = false
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.java b/src/com/android/calendar/AsyncQueryServiceHelper.java
deleted file mode 100644
index c6e0a2b..0000000
--- a/src/com/android/calendar/AsyncQueryServiceHelper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.app.IntentService;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.PriorityQueue;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.TimeUnit;
-
-public class AsyncQueryServiceHelper extends IntentService {
-    private static final String TAG = "AsyncQuery";
-
-    public AsyncQueryServiceHelper(String name) {
-        super(name);
-    }
-
-    public AsyncQueryServiceHelper() {
-        super("AsyncQueryServiceHelper");
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-    }
-
-    @Override
-    public void onStart(Intent intent, int startId) {
-        super.onStart(intent, startId);
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-    }
-}
diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.kt b/src/com/android/calendar/AsyncQueryServiceHelper.kt
new file mode 100644
index 0000000..4797330
--- /dev/null
+++ b/src/com/android/calendar/AsyncQueryServiceHelper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.app.IntentService
+import android.content.ContentProviderOperation
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.OperationApplicationException
+import android.database.Cursor
+import android.net.Uri
+import android.os.Handler
+import android.os.Message
+import android.os.RemoteException
+import android.os.SystemClock
+import android.util.Log
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Iterator
+import java.util.PriorityQueue
+import java.util.concurrent.Delayed
+import java.util.concurrent.TimeUnit
+
+class AsyncQueryServiceHelper : IntentService {
+    constructor(name: String?) : super(name) {}
+    constructor() : super("AsyncQueryServiceHelper") {}
+
+    protected override fun onHandleIntent(intent: Intent?) {
+    }
+
+    override fun onStart(intent: Intent?, startId: Int) {
+        super.onStart(intent, startId)
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+    }
+
+    companion object {
+        private const val TAG = "AsyncQuery"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/CalendarApplication.java b/src/com/android/calendar/CalendarApplication.kt
similarity index 70%
rename from src/com/android/calendar/CalendarApplication.java
rename to src/com/android/calendar/CalendarApplication.kt
index d0ca469..445d725 100644
--- a/src/com/android/calendar/CalendarApplication.java
+++ b/src/com/android/calendar/CalendarApplication.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,20 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.calendar
 
-package com.android.calendar;
+import android.app.Application
 
-import android.app.Application;
-
-public class CalendarApplication extends Application {
-    @Override
-    public void onCreate() {
-        super.onCreate();
+class CalendarApplication : Application() {
+    override fun onCreate() {
+        super.onCreate()
 
         /*
          * Ensure the default values are set for any receiver, activity,
          * service, etc. of Calendar
          */
-        GeneralPreferences.setDefaultValues(this);
+        GeneralPreferences.setDefaultValues(this)
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/CalendarBackupAgent.java b/src/com/android/calendar/CalendarBackupAgent.java
deleted file mode 100644
index 02456fd..0000000
--- a/src/com/android/calendar/CalendarBackupAgent.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.app.backup.BackupAgentHelper;
-import android.app.backup.BackupDataInput;
-import android.app.backup.SharedPreferencesBackupHelper;
-import android.content.Context;
-import android.content.SharedPreferences.Editor;
-import android.os.ParcelFileDescriptor;
-
-import java.io.IOException;
-
-public class CalendarBackupAgent extends BackupAgentHelper
-{
-    static final String SHARED_KEY = "shared_pref";
-
-    @Override
-    public void onCreate() {
-    }
-
-    @Override
-    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
-            throws IOException {
-        super.onRestore(data, appVersionCode, newState);
-    }
-}
diff --git a/src/com/android/calendar/CalendarBackupAgent.kt b/src/com/android/calendar/CalendarBackupAgent.kt
new file mode 100644
index 0000000..f3e230a
--- /dev/null
+++ b/src/com/android/calendar/CalendarBackupAgent.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.app.backup.BackupAgentHelper
+import android.app.backup.BackupDataInput
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import android.content.SharedPreferences.Editor
+import android.os.ParcelFileDescriptor
+
+import java.io.IOException
+
+class CalendarBackupAgent : BackupAgentHelper() {
+    override fun onCreate() {
+    }
+
+    @Throws(IOException::class)
+    override fun onRestore(data: BackupDataInput?, appVersionCode: Int,
+                           newState: ParcelFileDescriptor?) {
+        super.onRestore(data, appVersionCode, newState)
+    }
+
+    companion object {
+        const val SHARED_KEY = "shared_pref"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/CalendarController.java b/src/com/android/calendar/CalendarController.java
deleted file mode 100644
index 37286f2..0000000
--- a/src/com/android/calendar/CalendarController.java
+++ /dev/null
@@ -1,713 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.text.format.Time;
-import android.util.Log;
-import android.util.Pair;
-
-import java.lang.ref.WeakReference;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.Map.Entry;
-import java.util.WeakHashMap;
-
-public class CalendarController {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "CalendarController";
-
-    public static final String EVENT_EDIT_ON_LAUNCH = "editMode";
-
-    public static final int MIN_CALENDAR_YEAR = 1970;
-    public static final int MAX_CALENDAR_YEAR = 2036;
-    public static final int MIN_CALENDAR_WEEK = 0;
-    public static final int MAX_CALENDAR_WEEK = 3497; // weeks between 1/1/1970 and 1/1/2037
-
-    private final Context mContext;
-
-    // This uses a LinkedHashMap so that we can replace fragments based on the
-    // view id they are being expanded into since we can't guarantee a reference
-    // to the handler will be findable
-    private final LinkedHashMap<Integer,EventHandler> eventHandlers =
-            new LinkedHashMap<Integer,EventHandler>(5);
-    private final LinkedList<Integer> mToBeRemovedEventHandlers = new LinkedList<Integer>();
-    private final LinkedHashMap<Integer, EventHandler> mToBeAddedEventHandlers = new LinkedHashMap<
-            Integer, EventHandler>();
-    private Pair<Integer, EventHandler> mFirstEventHandler;
-    private Pair<Integer, EventHandler> mToBeAddedFirstEventHandler;
-    private volatile int mDispatchInProgressCounter = 0;
-
-    private static WeakHashMap<Context, WeakReference<CalendarController>> instances =
-        new WeakHashMap<Context, WeakReference<CalendarController>>();
-
-    private final WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1);
-
-    private int mViewType = -1;
-    private int mDetailViewType = -1;
-    private int mPreviousViewType = -1;
-    private long mEventId = -1;
-    private final Time mTime = new Time();
-    private long mDateFlags = 0;
-
-    private final Runnable mUpdateTimezone = new Runnable() {
-        @Override
-        public void run() {
-            mTime.switchTimezone(Utils.getTimeZone(mContext, this));
-        }
-    };
-
-    /**
-     * One of the event types that are sent to or from the controller
-     */
-    public interface EventType {
-        // Simple view of an event
-        final long VIEW_EVENT = 1L << 1;
-
-        // Full detail view in read only mode
-        final long VIEW_EVENT_DETAILS = 1L << 2;
-
-        // full detail view in edit mode
-        final long EDIT_EVENT = 1L << 3;
-
-        final long GO_TO = 1L << 5;
-
-        final long EVENTS_CHANGED = 1L << 7;
-
-        final long USER_HOME = 1L << 9;
-
-        // date range has changed, update the title
-        final long UPDATE_TITLE = 1L << 10;
-    }
-
-    /**
-     * One of the Agenda/Day/Week/Month view types
-     */
-    public interface ViewType {
-        final int DETAIL = -1;
-        final int CURRENT = 0;
-        final int AGENDA = 1;
-        final int DAY = 2;
-        final int WEEK = 3;
-        final int MONTH = 4;
-        final int EDIT = 5;
-        final int MAX_VALUE = 5;
-    }
-
-    public static class EventInfo {
-
-        private static final long ATTENTEE_STATUS_MASK = 0xFF;
-        private static final long ALL_DAY_MASK = 0x100;
-        private static final int ATTENDEE_STATUS_NONE_MASK = 0x01;
-        private static final int ATTENDEE_STATUS_ACCEPTED_MASK = 0x02;
-        private static final int ATTENDEE_STATUS_DECLINED_MASK = 0x04;
-        private static final int ATTENDEE_STATUS_TENTATIVE_MASK = 0x08;
-
-        public long eventType; // one of the EventType
-        public int viewType; // one of the ViewType
-        public long id; // event id
-        public Time selectedTime; // the selected time in focus
-
-        // Event start and end times.  All-day events are represented in:
-        // - local time for GO_TO commands
-        // - UTC time for VIEW_EVENT and other event-related commands
-        public Time startTime;
-        public Time endTime;
-
-        public int x; // x coordinate in the activity space
-        public int y; // y coordinate in the activity space
-        public String query; // query for a user search
-        public ComponentName componentName;  // used in combination with query
-        public String eventTitle;
-        public long calendarId;
-
-        /**
-         * For EventType.VIEW_EVENT:
-         * It is the default attendee response and an all day event indicator.
-         * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
-         * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE.
-         * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response.
-         * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay().
-         * <p>
-         * For EventType.GO_TO:
-         * Set to {@link #EXTRA_GOTO_TIME} to go to the specified date/time.
-         * Set to {@link #EXTRA_GOTO_DATE} to consider the date but ignore the time.
-         * Set to {@link #EXTRA_GOTO_BACK_TO_PREVIOUS} if back should bring back previous view.
-         * Set to {@link #EXTRA_GOTO_TODAY} if this is a user request to go to the current time.
-         * <p>
-         * For EventType.UPDATE_TITLE:
-         * Set formatting flags for Utils.formatDateRange
-         */
-        public long extraLong;
-
-        public boolean isAllDay() {
-            if (eventType != EventType.VIEW_EVENT) {
-                Log.wtf(TAG, "illegal call to isAllDay , wrong event type " + eventType);
-                return false;
-            }
-            return ((extraLong & ALL_DAY_MASK) != 0) ? true : false;
-        }
-
-        public  int getResponse() {
-            if (eventType != EventType.VIEW_EVENT) {
-                Log.wtf(TAG, "illegal call to getResponse , wrong event type " + eventType);
-                return Attendees.ATTENDEE_STATUS_NONE;
-            }
-
-            int response = (int)(extraLong & ATTENTEE_STATUS_MASK);
-            switch (response) {
-                case ATTENDEE_STATUS_NONE_MASK:
-                    return Attendees.ATTENDEE_STATUS_NONE;
-                case ATTENDEE_STATUS_ACCEPTED_MASK:
-                    return Attendees.ATTENDEE_STATUS_ACCEPTED;
-                case ATTENDEE_STATUS_DECLINED_MASK:
-                    return Attendees.ATTENDEE_STATUS_DECLINED;
-                case ATTENDEE_STATUS_TENTATIVE_MASK:
-                    return Attendees.ATTENDEE_STATUS_TENTATIVE;
-                default:
-                    Log.wtf(TAG,"Unknown attendee response " + response);
-            }
-            return ATTENDEE_STATUS_NONE_MASK;
-        }
-
-        // Used to build the extra long for a VIEW event.
-        public static long buildViewExtraLong(int response, boolean allDay) {
-            long extra = allDay ? ALL_DAY_MASK : 0;
-
-            switch (response) {
-                case Attendees.ATTENDEE_STATUS_NONE:
-                    extra |= ATTENDEE_STATUS_NONE_MASK;
-                    break;
-                case Attendees.ATTENDEE_STATUS_ACCEPTED:
-                    extra |= ATTENDEE_STATUS_ACCEPTED_MASK;
-                    break;
-                case Attendees.ATTENDEE_STATUS_DECLINED:
-                    extra |= ATTENDEE_STATUS_DECLINED_MASK;
-                    break;
-                case Attendees.ATTENDEE_STATUS_TENTATIVE:
-                    extra |= ATTENDEE_STATUS_TENTATIVE_MASK;
-                    break;
-                default:
-                    Log.wtf(TAG,"Unknown attendee response " + response);
-                    extra |= ATTENDEE_STATUS_NONE_MASK;
-                    break;
-            }
-            return extra;
-        }
-    }
-
-    /**
-     * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time
-     * can be ignored
-     */
-    public static final long EXTRA_GOTO_DATE = 1;
-    public static final long EXTRA_GOTO_TIME = 2;
-    public static final long EXTRA_GOTO_BACK_TO_PREVIOUS = 4;
-    public static final long EXTRA_GOTO_TODAY = 8;
-
-    public interface EventHandler {
-        long getSupportedEventTypes();
-        void handleEvent(EventInfo event);
-
-        /**
-         * This notifies the handler that the database has changed and it should
-         * update its view.
-         */
-        void eventsChanged();
-    }
-
-    /**
-     * Creates and/or returns an instance of CalendarController associated with
-     * the supplied context. It is best to pass in the current Activity.
-     *
-     * @param context The activity if at all possible.
-     */
-    public static CalendarController getInstance(Context context) {
-        synchronized (instances) {
-            CalendarController controller = null;
-            WeakReference<CalendarController> weakController = instances.get(context);
-            if (weakController != null) {
-                controller = weakController.get();
-            }
-
-            if (controller == null) {
-                controller = new CalendarController(context);
-                instances.put(context, new WeakReference(controller));
-            }
-            return controller;
-        }
-    }
-
-    /**
-     * Removes an instance when it is no longer needed. This should be called in
-     * an activity's onDestroy method.
-     *
-     * @param context The activity used to create the controller
-     */
-    public static void removeInstance(Context context) {
-        instances.remove(context);
-    }
-
-    private CalendarController(Context context) {
-        mContext = context;
-        mUpdateTimezone.run();
-        mTime.setToNow();
-        mDetailViewType = Utils.getSharedPreference(mContext,
-                GeneralPreferences.KEY_DETAILED_VIEW,
-                GeneralPreferences.DEFAULT_DETAILED_VIEW);
-    }
-
-    public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis,
-            long endMillis, int x, int y, long selectedMillis) {
-        // TODO: pass the real allDay status or at least a status that says we don't know the
-        // status and have the receiver query the data.
-        // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo
-        // so currently the missing allDay status has no effect.
-        sendEventRelatedEventWithExtra(sender, eventType, eventId, startMillis, endMillis, x, y,
-                EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false),
-                selectedMillis);
-    }
-
-    /**
-     * Helper for sending New/View/Edit/Delete events
-     *
-     * @param sender object of the caller
-     * @param eventType one of {@link EventType}
-     * @param eventId event id
-     * @param startMillis start time
-     * @param endMillis end time
-     * @param x x coordinate in the activity space
-     * @param y y coordinate in the activity space
-     * @param extraLong default response value for the "simple event view" and all day indication.
-     *        Use Attendees.ATTENDEE_STATUS_NONE for no response.
-     * @param selectedMillis The time to specify as selected
-     */
-    public void sendEventRelatedEventWithExtra(Object sender, long eventType, long eventId,
-            long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis) {
-        sendEventRelatedEventWithExtraWithTitleWithCalendarId(sender, eventType, eventId,
-            startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1);
-    }
-
-    /**
-     * Helper for sending New/View/Edit/Delete events
-     *
-     * @param sender object of the caller
-     * @param eventType one of {@link EventType}
-     * @param eventId event id
-     * @param startMillis start time
-     * @param endMillis end time
-     * @param x x coordinate in the activity space
-     * @param y y coordinate in the activity space
-     * @param extraLong default response value for the "simple event view" and all day indication.
-     *        Use Attendees.ATTENDEE_STATUS_NONE for no response.
-     * @param selectedMillis The time to specify as selected
-     * @param title The title of the event
-     * @param calendarId The id of the calendar which the event belongs to
-     */
-    public void sendEventRelatedEventWithExtraWithTitleWithCalendarId(Object sender, long eventType,
-          long eventId, long startMillis, long endMillis, int x, int y, long extraLong,
-          long selectedMillis, String title, long calendarId) {
-        EventInfo info = new EventInfo();
-        info.eventType = eventType;
-        if (eventType == EventType.VIEW_EVENT_DETAILS) {
-            info.viewType = ViewType.CURRENT;
-        }
-
-        info.id = eventId;
-        info.startTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
-        info.startTime.set(startMillis);
-        if (selectedMillis != -1) {
-            info.selectedTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
-            info.selectedTime.set(selectedMillis);
-        } else {
-            info.selectedTime = info.startTime;
-        }
-        info.endTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
-        info.endTime.set(endMillis);
-        info.x = x;
-        info.y = y;
-        info.extraLong = extraLong;
-        info.eventTitle = title;
-        info.calendarId = calendarId;
-        this.sendEvent(sender, info);
-    }
-    /**
-     * Helper for sending non-calendar-event events
-     *
-     * @param sender object of the caller
-     * @param eventType one of {@link EventType}
-     * @param start start time
-     * @param end end time
-     * @param eventId event id
-     * @param viewType {@link ViewType}
-     */
-    public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
-            int viewType) {
-        sendEvent(sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null,
-                null);
-    }
-
-    /**
-     * sendEvent() variant with extraLong, search query, and search component name.
-     */
-    public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
-            int viewType, long extraLong, String query, ComponentName componentName) {
-        sendEvent(sender, eventType, start, end, start, eventId, viewType, extraLong, query,
-                componentName);
-    }
-
-    public void sendEvent(Object sender, long eventType, Time start, Time end, Time selected,
-            long eventId, int viewType, long extraLong, String query, ComponentName componentName) {
-        EventInfo info = new EventInfo();
-        info.eventType = eventType;
-        info.startTime = start;
-        info.selectedTime = selected;
-        info.endTime = end;
-        info.id = eventId;
-        info.viewType = viewType;
-        info.query = query;
-        info.componentName = componentName;
-        info.extraLong = extraLong;
-        this.sendEvent(sender, info);
-    }
-
-    public void sendEvent(Object sender, final EventInfo event) {
-        // TODO Throw exception on invalid events
-
-        if (DEBUG) {
-            Log.d(TAG, eventInfoToString(event));
-        }
-
-        Long filteredTypes = filters.get(sender);
-        if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) {
-            // Suppress event per filter
-            if (DEBUG) {
-                Log.d(TAG, "Event suppressed");
-            }
-            return;
-        }
-
-        mPreviousViewType = mViewType;
-
-        // Fix up view if not specified
-        if (event.viewType == ViewType.DETAIL) {
-            event.viewType = mDetailViewType;
-            mViewType = mDetailViewType;
-        } else if (event.viewType == ViewType.CURRENT) {
-            event.viewType = mViewType;
-        } else if (event.viewType != ViewType.EDIT) {
-            mViewType = event.viewType;
-
-            if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY
-                    || (Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK)) {
-                mDetailViewType = mViewType;
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "vvvvvvvvvvvvvvv");
-            Log.d(TAG, "Start  " + (event.startTime == null ? "null" : event.startTime.toString()));
-            Log.d(TAG, "End    " + (event.endTime == null ? "null" : event.endTime.toString()));
-            Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
-            Log.d(TAG, "mTime  " + (mTime == null ? "null" : mTime.toString()));
-        }
-
-        long startMillis = 0;
-        if (event.startTime != null) {
-            startMillis = event.startTime.toMillis(false);
-        }
-
-        // Set mTime if selectedTime is set
-        if (event.selectedTime != null && event.selectedTime.toMillis(false) != 0) {
-            mTime.set(event.selectedTime);
-        } else {
-            if (startMillis != 0) {
-                // selectedTime is not set so set mTime to startTime iff it is not
-                // within start and end times
-                long mtimeMillis = mTime.toMillis(false);
-                if (mtimeMillis < startMillis
-                        || (event.endTime != null && mtimeMillis > event.endTime.toMillis(false))) {
-                    mTime.set(event.startTime);
-                }
-            }
-            event.selectedTime = mTime;
-        }
-        // Store the formatting flags if this is an update to the title
-        if (event.eventType == EventType.UPDATE_TITLE) {
-            mDateFlags = event.extraLong;
-        }
-
-        // Fix up start time if not specified
-        if (startMillis == 0) {
-            event.startTime = mTime;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Start  " + (event.startTime == null ? "null" : event.startTime.toString()));
-            Log.d(TAG, "End    " + (event.endTime == null ? "null" : event.endTime.toString()));
-            Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
-            Log.d(TAG, "mTime  " + (mTime == null ? "null" : mTime.toString()));
-            Log.d(TAG, "^^^^^^^^^^^^^^^");
-        }
-
-        // Store the eventId if we're entering edit event
-        if ((event.eventType
-                & (EventType.VIEW_EVENT_DETAILS))
-                != 0) {
-            if (event.id > 0) {
-                mEventId = event.id;
-            } else {
-                mEventId = -1;
-            }
-        }
-
-        boolean handled = false;
-        synchronized (this) {
-            mDispatchInProgressCounter ++;
-
-            if (DEBUG) {
-                Log.d(TAG, "sendEvent: Dispatching to " + eventHandlers.size() + " handlers");
-            }
-            // Dispatch to event handler(s)
-            if (mFirstEventHandler != null) {
-                // Handle the 'first' one before handling the others
-                EventHandler handler = mFirstEventHandler.second;
-                if (handler != null && (handler.getSupportedEventTypes() & event.eventType) != 0
-                        && !mToBeRemovedEventHandlers.contains(mFirstEventHandler.first)) {
-                    handler.handleEvent(event);
-                    handled = true;
-                }
-            }
-            for (Iterator<Entry<Integer, EventHandler>> handlers =
-                    eventHandlers.entrySet().iterator(); handlers.hasNext();) {
-                Entry<Integer, EventHandler> entry = handlers.next();
-                int key = entry.getKey();
-                if (mFirstEventHandler != null && key == mFirstEventHandler.first) {
-                    // If this was the 'first' handler it was already handled
-                    continue;
-                }
-                EventHandler eventHandler = entry.getValue();
-                if (eventHandler != null
-                        && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) {
-                    if (mToBeRemovedEventHandlers.contains(key)) {
-                        continue;
-                    }
-                    eventHandler.handleEvent(event);
-                    handled = true;
-                }
-            }
-
-            mDispatchInProgressCounter --;
-
-            if (mDispatchInProgressCounter == 0) {
-
-                // Deregister removed handlers
-                if (mToBeRemovedEventHandlers.size() > 0) {
-                    for (Integer zombie : mToBeRemovedEventHandlers) {
-                        eventHandlers.remove(zombie);
-                        if (mFirstEventHandler != null && zombie.equals(mFirstEventHandler.first)) {
-                            mFirstEventHandler = null;
-                        }
-                    }
-                    mToBeRemovedEventHandlers.clear();
-                }
-                // Add new handlers
-                if (mToBeAddedFirstEventHandler != null) {
-                    mFirstEventHandler = mToBeAddedFirstEventHandler;
-                    mToBeAddedFirstEventHandler = null;
-                }
-                if (mToBeAddedEventHandlers.size() > 0) {
-                    for (Entry<Integer, EventHandler> food : mToBeAddedEventHandlers.entrySet()) {
-                        eventHandlers.put(food.getKey(), food.getValue());
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds or updates an event handler. This uses a LinkedHashMap so that we can
-     * replace fragments based on the view id they are being expanded into.
-     *
-     * @param key The view id or placeholder for this handler
-     * @param eventHandler Typically a fragment or activity in the calendar app
-     */
-    public void registerEventHandler(int key, EventHandler eventHandler) {
-        synchronized (this) {
-            if (mDispatchInProgressCounter > 0) {
-                mToBeAddedEventHandlers.put(key, eventHandler);
-            } else {
-                eventHandlers.put(key, eventHandler);
-            }
-        }
-    }
-
-    public void registerFirstEventHandler(int key, EventHandler eventHandler) {
-        synchronized (this) {
-            registerEventHandler(key, eventHandler);
-            if (mDispatchInProgressCounter > 0) {
-                mToBeAddedFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
-            } else {
-                mFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
-            }
-        }
-    }
-
-    public void deregisterEventHandler(Integer key) {
-        synchronized (this) {
-            if (mDispatchInProgressCounter > 0) {
-                // To avoid ConcurrencyException, stash away the event handler for now.
-                mToBeRemovedEventHandlers.add(key);
-            } else {
-                eventHandlers.remove(key);
-                if (mFirstEventHandler != null && mFirstEventHandler.first == key) {
-                    mFirstEventHandler = null;
-                }
-            }
-        }
-    }
-
-    public void deregisterAllEventHandlers() {
-        synchronized (this) {
-            if (mDispatchInProgressCounter > 0) {
-                // To avoid ConcurrencyException, stash away the event handler for now.
-                mToBeRemovedEventHandlers.addAll(eventHandlers.keySet());
-            } else {
-                eventHandlers.clear();
-                mFirstEventHandler = null;
-            }
-        }
-    }
-
-    // FRAG_TODO doesn't work yet
-    public void filterBroadcasts(Object sender, long eventTypes) {
-        filters.put(sender, eventTypes);
-    }
-
-    /**
-     * @return the time that this controller is currently pointed at
-     */
-    public long getTime() {
-        return mTime.toMillis(false);
-    }
-
-    /**
-     * @return the last set of date flags sent with
-     *         {@link EventType#UPDATE_TITLE}
-     */
-    public long getDateFlags() {
-        return mDateFlags;
-    }
-
-    /**
-     * Set the time this controller is currently pointed at
-     *
-     * @param millisTime Time since epoch in millis
-     */
-    public void setTime(long millisTime) {
-        mTime.set(millisTime);
-    }
-
-    /**
-     * @return the last event ID the edit view was launched with
-     */
-    public long getEventId() {
-        return mEventId;
-    }
-
-    public int getViewType() {
-        return mViewType;
-    }
-
-    public int getPreviousViewType() {
-        return mPreviousViewType;
-    }
-
-    public void launchViewEvent(long eventId, long startMillis, long endMillis, int response) {
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        intent.setData(eventUri);
-        intent.setClass(mContext, AllInOneActivity.class);
-        intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
-        intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
-        intent.putExtra(ATTENDEE_STATUS, response);
-        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        mContext.startActivity(intent);
-    }
-
-    // Forces the viewType. Should only be used for initialization.
-    public void setViewType(int viewType) {
-        mViewType = viewType;
-    }
-
-    // Sets the eventId. Should only be used for initialization.
-    public void setEventId(long eventId) {
-        mEventId = eventId;
-    }
-
-    private String eventInfoToString(EventInfo eventInfo) {
-        String tmp = "Unknown";
-
-        StringBuilder builder = new StringBuilder();
-        if ((eventInfo.eventType & EventType.GO_TO) != 0) {
-            tmp = "Go to time/event";
-        } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) {
-            tmp = "View event";
-        } else if ((eventInfo.eventType & EventType.VIEW_EVENT_DETAILS) != 0) {
-            tmp = "View details";
-        } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) {
-            tmp = "Refresh events";
-        } else if ((eventInfo.eventType & EventType.USER_HOME) != 0) {
-            tmp = "Gone home";
-        } else if ((eventInfo.eventType & EventType.UPDATE_TITLE) != 0) {
-            tmp = "Update title";
-        }
-        builder.append(tmp);
-        builder.append(": id=");
-        builder.append(eventInfo.id);
-        builder.append(", selected=");
-        builder.append(eventInfo.selectedTime);
-        builder.append(", start=");
-        builder.append(eventInfo.startTime);
-        builder.append(", end=");
-        builder.append(eventInfo.endTime);
-        builder.append(", viewType=");
-        builder.append(eventInfo.viewType);
-        builder.append(", x=");
-        builder.append(eventInfo.x);
-        builder.append(", y=");
-        builder.append(eventInfo.y);
-        return builder.toString();
-    }
-}
diff --git a/src/com/android/calendar/CalendarController.kt b/src/com/android/calendar/CalendarController.kt
new file mode 100644
index 0000000..16ee8fd
--- /dev/null
+++ b/src/com/android/calendar/CalendarController.kt
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.content.ComponentName
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Events
+import android.text.format.Time
+import android.util.Log
+import android.util.Pair
+import java.lang.ref.WeakReference
+import java.util.LinkedHashMap
+import java.util.LinkedList
+import java.util.WeakHashMap
+
+class CalendarController private constructor(context: Context?) {
+    private var mContext: Context? = null
+
+    // This uses a LinkedHashMap so that we can replace fragments based on the
+    // view id they are being expanded into since we can't guarantee a reference
+    // to the handler will be findable
+    private val eventHandlers: LinkedHashMap<Int, EventHandler> =
+        LinkedHashMap<Int, EventHandler>(5)
+    private val mToBeRemovedEventHandlers: LinkedList<Int> = LinkedList<Int>()
+    private val mToBeAddedEventHandlers: LinkedHashMap<Int, EventHandler> =
+        LinkedHashMap<Int, EventHandler>()
+    private var mFirstEventHandler: Pair<Int, EventHandler>? = null
+    private var mToBeAddedFirstEventHandler: Pair<Int, EventHandler>? = null
+
+    @Volatile
+    private var mDispatchInProgressCounter = 0
+    private val filters: WeakHashMap<Object, Long> = WeakHashMap<Object, Long>(1)
+
+    // Forces the viewType. Should only be used for initialization.
+    var viewType = -1
+    private var mDetailViewType = -1
+    var previousViewType = -1
+        private set
+
+    // The last event ID the edit view was launched with
+    var eventId: Long = -1
+    private val mTime: Time? = Time()
+
+    // The last set of date flags sent with
+    var dateFlags: Long = 0
+        private set
+    private val mUpdateTimezone: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            mTime?.switchTimezone(Utils.getTimeZone(mContext, this))
+        }
+    }
+
+    /**
+     * One of the event types that are sent to or from the controller
+     */
+    interface EventType {
+        companion object {
+            // Simple view of an event
+            const val VIEW_EVENT = 1L shl 1
+
+            // Full detail view in read only mode
+            const val VIEW_EVENT_DETAILS = 1L shl 2
+
+            // full detail view in edit mode
+            const val EDIT_EVENT = 1L shl 3
+            const val GO_TO = 1L shl 5
+            const val EVENTS_CHANGED = 1L shl 7
+            const val USER_HOME = 1L shl 9
+
+            // date range has changed, update the title
+            const val UPDATE_TITLE = 1L shl 10
+        }
+    }
+
+    /**
+     * One of the Agenda/Day/Week/Month view types
+     */
+    interface ViewType {
+        companion object {
+            const val DETAIL = -1
+            const val CURRENT = 0
+            const val AGENDA = 1
+            const val DAY = 2
+            const val WEEK = 3
+            const val MONTH = 4
+            const val EDIT = 5
+            const val MAX_VALUE = 5
+        }
+    }
+
+    class EventInfo {
+        @JvmField var eventType: Long = 0 // one of the EventType
+        @JvmField var viewType = 0 // one of the ViewType
+        @JvmField var id: Long = 0 // event id
+        @JvmField var selectedTime: Time? = null // the selected time in focus
+
+        // Event start and end times.  All-day events are represented in:
+        // - local time for GO_TO commands
+        // - UTC time for VIEW_EVENT and other event-related commands
+        @JvmField var startTime: Time? = null
+        @JvmField var endTime: Time? = null
+        @JvmField var x = 0 // x coordinate in the activity space
+        @JvmField var y = 0 // y coordinate in the activity space
+        @JvmField var query: String? = null // query for a user search
+        @JvmField var componentName: ComponentName? = null // used in combination with query
+        @JvmField var eventTitle: String? = null
+        @JvmField var calendarId: Long = 0
+
+        /**
+         * For EventType.VIEW_EVENT:
+         * It is the default attendee response and an all day event indicator.
+         * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+         * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE.
+         * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response.
+         * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay().
+         *
+         *
+         * For EventType.GO_TO:
+         * Set to [.EXTRA_GOTO_TIME] to go to the specified date/time.
+         * Set to [.EXTRA_GOTO_DATE] to consider the date but ignore the time.
+         * Set to [.EXTRA_GOTO_BACK_TO_PREVIOUS] if back should bring back previous view.
+         * Set to [.EXTRA_GOTO_TODAY] if this is a user request to go to the current time.
+         *
+         *
+         * For EventType.UPDATE_TITLE:
+         * Set formatting flags for Utils.formatDateRange
+         */
+        @JvmField var extraLong: Long = 0
+        val isAllDay: Boolean
+            get() {
+                if (eventType != EventType.VIEW_EVENT) {
+                    Log.wtf(TAG, "illegal call to isAllDay , wrong event type $eventType")
+                    return false
+                }
+                return if (extraLong and ALL_DAY_MASK != 0L) true else false
+            }
+        val response: Int
+            get() {
+                if (eventType != EventType.VIEW_EVENT) {
+                    Log.wtf(TAG, "illegal call to getResponse , wrong event type $eventType")
+                    return Attendees.ATTENDEE_STATUS_NONE
+                }
+                val response = (extraLong and ATTENTEE_STATUS_MASK).toInt()
+                when (response) {
+                    ATTENDEE_STATUS_NONE_MASK -> return Attendees.ATTENDEE_STATUS_NONE
+                    ATTENDEE_STATUS_ACCEPTED_MASK -> return Attendees.ATTENDEE_STATUS_ACCEPTED
+                    ATTENDEE_STATUS_DECLINED_MASK -> return Attendees.ATTENDEE_STATUS_DECLINED
+                    ATTENDEE_STATUS_TENTATIVE_MASK -> return Attendees.ATTENDEE_STATUS_TENTATIVE
+                    else -> Log.wtf(TAG, "Unknown attendee response $response")
+                }
+                return ATTENDEE_STATUS_NONE_MASK
+            }
+
+        companion object {
+            private const val ATTENTEE_STATUS_MASK: Long = 0xFF
+            private const val ALL_DAY_MASK: Long = 0x100
+            private const val ATTENDEE_STATUS_NONE_MASK = 0x01
+            private const val ATTENDEE_STATUS_ACCEPTED_MASK = 0x02
+            private const val ATTENDEE_STATUS_DECLINED_MASK = 0x04
+            private const val ATTENDEE_STATUS_TENTATIVE_MASK = 0x08
+
+            // Used to build the extra long for a VIEW event.
+            @JvmStatic fun buildViewExtraLong(response: Int, allDay: Boolean): Long {
+                var extra = if (allDay) ALL_DAY_MASK else 0
+                extra = when (response) {
+                    Attendees.ATTENDEE_STATUS_NONE -> extra or
+                            ATTENDEE_STATUS_NONE_MASK.toLong()
+                    Attendees.ATTENDEE_STATUS_ACCEPTED -> extra or
+                            ATTENDEE_STATUS_ACCEPTED_MASK.toLong()
+                    Attendees.ATTENDEE_STATUS_DECLINED -> extra or
+                            ATTENDEE_STATUS_DECLINED_MASK.toLong()
+                    Attendees.ATTENDEE_STATUS_TENTATIVE -> extra or
+                            ATTENDEE_STATUS_TENTATIVE_MASK.toLong()
+                    else -> {
+                        Log.wtf(
+                                TAG,
+                                "Unknown attendee response $response"
+                        )
+                        extra or ATTENDEE_STATUS_NONE_MASK.toLong()
+                    }
+                }
+                return extra
+            }
+        }
+    }
+
+    interface EventHandler {
+        val supportedEventTypes: Long
+        fun handleEvent(event: EventInfo?)
+
+        /**
+         * This notifies the handler that the database has changed and it should
+         * update its view.
+         */
+        fun eventsChanged()
+    }
+
+    fun sendEventRelatedEvent(
+        sender: Object?,
+        eventType: Long,
+        eventId: Long,
+        startMillis: Long,
+        endMillis: Long,
+        x: Int,
+        y: Int,
+        selectedMillis: Long
+    ) {
+        // TODO: pass the real allDay status or at least a status that says we don't know the
+        // status and have the receiver query the data.
+        // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo
+        // so currently the missing allDay status has no effect.
+        sendEventRelatedEventWithExtra(
+                sender, eventType, eventId, startMillis, endMillis, x, y,
+                EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false),
+                selectedMillis
+        )
+    }
+
+    /**
+     * Helper for sending New/View/Edit/Delete events
+     *
+     * @param sender object of the caller
+     * @param eventType one of [EventType]
+     * @param eventId event id
+     * @param startMillis start time
+     * @param endMillis end time
+     * @param x x coordinate in the activity space
+     * @param y y coordinate in the activity space
+     * @param extraLong default response value for the "simple event view" and all day indication.
+     * Use Attendees.ATTENDEE_STATUS_NONE for no response.
+     * @param selectedMillis The time to specify as selected
+     */
+    fun sendEventRelatedEventWithExtra(
+        sender: Object?,
+        eventType: Long,
+        eventId: Long,
+        startMillis: Long,
+        endMillis: Long,
+        x: Int,
+        y: Int,
+        extraLong: Long,
+        selectedMillis: Long
+    ) {
+        sendEventRelatedEventWithExtraWithTitleWithCalendarId(
+                sender, eventType, eventId,
+                startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1
+        )
+    }
+
+    /**
+     * Helper for sending New/View/Edit/Delete events
+     *
+     * @param sender object of the caller
+     * @param eventType one of [EventType]
+     * @param eventId event id
+     * @param startMillis start time
+     * @param endMillis end time
+     * @param x x coordinate in the activity space
+     * @param y y coordinate in the activity space
+     * @param extraLong default response value for the "simple event view" and all day indication.
+     * Use Attendees.ATTENDEE_STATUS_NONE for no response.
+     * @param selectedMillis The time to specify as selected
+     * @param title The title of the event
+     * @param calendarId The id of the calendar which the event belongs to
+     */
+    fun sendEventRelatedEventWithExtraWithTitleWithCalendarId(
+        sender: Object?,
+        eventType: Long,
+        eventId: Long,
+        startMillis: Long,
+        endMillis: Long,
+        x: Int,
+        y: Int,
+        extraLong: Long,
+        selectedMillis: Long,
+        title: String?,
+        calendarId: Long
+    ) {
+        val info = EventInfo()
+        info.eventType = eventType
+        if (eventType == EventType.VIEW_EVENT_DETAILS) {
+            info.viewType = ViewType.CURRENT
+        }
+        info.id = eventId
+        info.startTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+        (info.startTime as Time).set(startMillis)
+        if (selectedMillis != -1L) {
+            info.selectedTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+            (info.selectedTime as Time).set(selectedMillis)
+        } else {
+            info.selectedTime = info.startTime
+        }
+        info.endTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone))
+        (info.endTime as Time).set(endMillis)
+        info.x = x
+        info.y = y
+        info.extraLong = extraLong
+        info.eventTitle = title
+        info.calendarId = calendarId
+        this.sendEvent(sender, info)
+    }
+
+    /**
+     * Helper for sending non-calendar-event events
+     *
+     * @param sender object of the caller
+     * @param eventType one of [EventType]
+     * @param start start time
+     * @param end end time
+     * @param eventId event id
+     * @param viewType [ViewType]
+     */
+    fun sendEvent(
+        sender: Object?,
+        eventType: Long,
+        start: Time?,
+        end: Time?,
+        eventId: Long,
+        viewType: Int
+    ) {
+        sendEvent(
+                sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null,
+                null
+        )
+    }
+
+    /**
+     * sendEvent() variant with extraLong, search query, and search component name.
+     */
+    fun sendEvent(
+        sender: Object?,
+        eventType: Long,
+        start: Time?,
+        end: Time?,
+        eventId: Long,
+        viewType: Int,
+        extraLong: Long,
+        query: String?,
+        componentName: ComponentName?
+    ) {
+        sendEvent(
+                sender, eventType, start, end, start, eventId, viewType, extraLong, query,
+                componentName
+        )
+    }
+
+    fun sendEvent(
+        sender: Object?,
+        eventType: Long,
+        start: Time?,
+        end: Time?,
+        selected: Time?,
+        eventId: Long,
+        viewType: Int,
+        extraLong: Long,
+        query: String?,
+        componentName: ComponentName?
+    ) {
+        val info = EventInfo()
+        info.eventType = eventType
+        info.startTime = start
+        info.selectedTime = selected
+        info.endTime = end
+        info.id = eventId
+        info.viewType = viewType
+        info.query = query
+        info.componentName = componentName
+        info.extraLong = extraLong
+        this.sendEvent(sender, info)
+    }
+
+    fun sendEvent(sender: Object?, event: EventInfo) {
+        // TODO Throw exception on invalid events
+        if (DEBUG) {
+            Log.d(TAG, eventInfoToString(event))
+        }
+        val filteredTypes: Long? = filters.get(sender)
+        if (filteredTypes != null && filteredTypes.toLong() and event.eventType != 0L) {
+            // Suppress event per filter
+            if (DEBUG) {
+                Log.d(TAG, "Event suppressed")
+            }
+            return
+        }
+        previousViewType = viewType
+
+        // Fix up view if not specified
+        if (event.viewType == ViewType.DETAIL) {
+            event.viewType = mDetailViewType
+            viewType = mDetailViewType
+        } else if (event.viewType == ViewType.CURRENT) {
+            event.viewType = viewType
+        } else if (event.viewType != ViewType.EDIT) {
+            viewType = event.viewType
+            if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY ||
+                    Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK) {
+                mDetailViewType = viewType
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "vvvvvvvvvvvvvvv")
+            Log.d(
+                    TAG,
+                    "Start  " + if (event.startTime == null) "null" else event.startTime.toString()
+            )
+            Log.d(TAG, "End    " + if (event.endTime == null) "null" else event.endTime.toString())
+            Log.d(
+                    TAG,
+                    "Select " + if (event.selectedTime == null) "null"
+                    else event.selectedTime.toString()
+            )
+            Log.d(TAG, "mTime  " + if (mTime == null) "null" else mTime.toString())
+        }
+        var startMillis: Long = 0
+        val temp = event.startTime
+        if (temp != null) {
+            startMillis = (event.startTime as Time).toMillis(false)
+        }
+
+        // Set mTime if selectedTime is set
+        val temp1 = event.selectedTime
+        if (temp1 != null && temp1?.toMillis(false) != 0L) {
+            mTime?.set(event.selectedTime)
+        } else {
+            if (startMillis != 0L) {
+                // selectedTime is not set so set mTime to startTime iff it is not
+                // within start and end times
+                val mtimeMillis: Long = mTime?.toMillis(false) as Long
+                val temp2 = event.endTime
+                if (mtimeMillis < startMillis ||
+                        temp2 != null && mtimeMillis > temp2.toMillis(false)) {
+                    mTime?.set(event.startTime)
+                }
+            }
+            event.selectedTime = mTime
+        }
+        // Store the formatting flags if this is an update to the title
+        if (event.eventType == EventType.UPDATE_TITLE) {
+            dateFlags = event.extraLong
+        }
+
+        // Fix up start time if not specified
+        if (startMillis == 0L) {
+            event.startTime = mTime
+        }
+        if (DEBUG) {
+            Log.d(
+                    TAG,
+                    "Start  " + if (event.startTime == null) "null" else
+                        event.startTime.toString()
+            )
+            Log.d(TAG, "End    " + if (event.endTime == null) "null" else
+                event.endTime.toString())
+            Log.d(
+                    TAG,
+                    "Select " + if (event.selectedTime == null) "null" else
+                        event.selectedTime.toString()
+            )
+            Log.d(TAG, "mTime  " + if (mTime == null) "null" else mTime.toString())
+            Log.d(TAG, "^^^^^^^^^^^^^^^")
+        }
+
+        // Store the eventId if we're entering edit event
+        if ((event.eventType and EventType.VIEW_EVENT_DETAILS) != 0L) {
+            if (event.id > 0) {
+                eventId = event.id
+            } else {
+                eventId = -1
+            }
+        }
+        var handled = false
+        synchronized(this) {
+            mDispatchInProgressCounter++
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        "sendEvent: Dispatching to " + eventHandlers.size.toString() + " handlers"
+                )
+            }
+            // Dispatch to event handler(s)
+            val temp3 = mFirstEventHandler
+            if (temp3 != null) {
+                // Handle the 'first' one before handling the others
+                val handler: EventHandler? = mFirstEventHandler?.second
+                if (handler != null && handler.supportedEventTypes and event.eventType != 0L &&
+                        !mToBeRemovedEventHandlers.contains(mFirstEventHandler?.first)) {
+                    handler.handleEvent(event)
+                    handled = true
+                }
+            }
+            val handlers: MutableIterator<MutableMap.MutableEntry<Int,
+                CalendarController.EventHandler>> = eventHandlers.entries.iterator()
+            while (handlers.hasNext()) {
+                val entry: MutableMap.MutableEntry<Int,
+                    CalendarController.EventHandler> = handlers.next()
+                val key: Int = entry.key.toInt()
+                val temp4 = mFirstEventHandler
+                if (temp4 != null && key.toInt() == temp4.first.toInt()) {
+                    // If this was the 'first' handler it was already handled
+                    continue
+                }
+                val eventHandler: EventHandler = entry.value
+                if (eventHandler != null &&
+                    eventHandler.supportedEventTypes and event.eventType != 0L) {
+                    if (mToBeRemovedEventHandlers.contains(key)) {
+                        continue
+                    }
+                    eventHandler.handleEvent(event)
+                    handled = true
+                }
+            }
+            mDispatchInProgressCounter--
+            if (mDispatchInProgressCounter == 0) {
+
+                // Deregister removed handlers
+                if (mToBeRemovedEventHandlers.size > 0) {
+                    for (zombie in mToBeRemovedEventHandlers) {
+                        eventHandlers.remove(zombie)
+                        val temp5 = mFirstEventHandler
+                        if (temp5 != null && zombie.equals(temp5.first)) {
+                            mFirstEventHandler = null
+                        }
+                    }
+                    mToBeRemovedEventHandlers.clear()
+                }
+                // Add new handlers
+                if (mToBeAddedFirstEventHandler != null) {
+                    mFirstEventHandler = mToBeAddedFirstEventHandler
+                    mToBeAddedFirstEventHandler = null
+                }
+                if (mToBeAddedEventHandlers.size > 0) {
+                    for (food in mToBeAddedEventHandlers.entries) {
+                        eventHandlers.put(food.key, food.value)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds or updates an event handler. This uses a LinkedHashMap so that we can
+     * replace fragments based on the view id they are being expanded into.
+     *
+     * @param key The view id or placeholder for this handler
+     * @param eventHandler Typically a fragment or activity in the calendar app
+     */
+    fun registerEventHandler(key: Int, eventHandler: EventHandler?) {
+        synchronized(this) {
+            if (mDispatchInProgressCounter > 0) {
+                mToBeAddedEventHandlers.put(key,
+                        eventHandler as CalendarController.EventHandler)
+            } else {
+                eventHandlers.put(key, eventHandler as CalendarController.EventHandler)
+            }
+        }
+    }
+
+    fun registerFirstEventHandler(key: Int, eventHandler: EventHandler?) {
+        synchronized(this) {
+            registerEventHandler(key, eventHandler)
+            if (mDispatchInProgressCounter > 0) {
+                mToBeAddedFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler)
+            } else {
+                mFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler)
+            }
+        }
+    }
+
+    fun deregisterEventHandler(key: Int) {
+        synchronized(this) {
+            if (mDispatchInProgressCounter > 0) {
+                // To avoid ConcurrencyException, stash away the event handler for now.
+                mToBeRemovedEventHandlers.add(key)
+            } else {
+                eventHandlers.remove(key)
+                val temp6 = mFirstEventHandler
+                if (temp6 != null && temp6.first == key) {
+                    mFirstEventHandler = null
+                } else {}
+            }
+        }
+    }
+
+    fun deregisterAllEventHandlers() {
+        synchronized(this) {
+            if (mDispatchInProgressCounter > 0) {
+                // To avoid ConcurrencyException, stash away the event handler for now.
+                mToBeRemovedEventHandlers.addAll(eventHandlers.keys)
+            } else {
+                eventHandlers.clear()
+                mFirstEventHandler = null
+            }
+        }
+    }
+
+    // FRAG_TODO doesn't work yet
+    fun filterBroadcasts(sender: Object?, eventTypes: Long) {
+        filters.put(sender, eventTypes)
+    }
+    /**
+     * @return the time that this controller is currently pointed at
+     */
+    /**
+     * Set the time this controller is currently pointed at
+     *
+     * @param millisTime Time since epoch in millis
+     */
+    var time: Long?
+        get() = mTime?.toMillis(false)
+        set(millisTime) {
+            mTime?.set(millisTime as Long)
+        }
+
+    fun launchViewEvent(eventId: Long, startMillis: Long, endMillis: Long, response: Int) {
+        val intent = Intent(Intent.ACTION_VIEW)
+        val eventUri: Uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId)
+        intent.setData(eventUri)
+        intent.setClass(mContext as Context, AllInOneActivity::class.java)
+        intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis)
+        intent.putExtra(EXTRA_EVENT_END_TIME, endMillis)
+        intent.putExtra(ATTENDEE_STATUS, response)
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        mContext?.startActivity(intent)
+    }
+
+    private fun eventInfoToString(eventInfo: EventInfo): String {
+        var tmp = "Unknown"
+        val builder = StringBuilder()
+        if (eventInfo.eventType and EventType.GO_TO != 0L) {
+            tmp = "Go to time/event"
+        } else if (eventInfo.eventType and EventType.VIEW_EVENT != 0L) {
+            tmp = "View event"
+        } else if (eventInfo.eventType and EventType.VIEW_EVENT_DETAILS != 0L) {
+            tmp = "View details"
+        } else if (eventInfo.eventType and EventType.EVENTS_CHANGED != 0L) {
+            tmp = "Refresh events"
+        } else if (eventInfo.eventType and EventType.USER_HOME != 0L) {
+            tmp = "Gone home"
+        } else if (eventInfo.eventType and EventType.UPDATE_TITLE != 0L) {
+            tmp = "Update title"
+        }
+        builder.append(tmp)
+        builder.append(": id=")
+        builder.append(eventInfo.id)
+        builder.append(", selected=")
+        builder.append(eventInfo.selectedTime)
+        builder.append(", start=")
+        builder.append(eventInfo.startTime)
+        builder.append(", end=")
+        builder.append(eventInfo.endTime)
+        builder.append(", viewType=")
+        builder.append(eventInfo.viewType)
+        builder.append(", x=")
+        builder.append(eventInfo.x)
+        builder.append(", y=")
+        builder.append(eventInfo.y)
+        return builder.toString()
+    }
+
+    companion object {
+        private const val DEBUG = false
+        private const val TAG = "CalendarController"
+        const val EVENT_EDIT_ON_LAUNCH = "editMode"
+        const val MIN_CALENDAR_YEAR = 1970
+        const val MAX_CALENDAR_YEAR = 2036
+        const val MIN_CALENDAR_WEEK = 0
+        const val MAX_CALENDAR_WEEK = 3497 // weeks between 1/1/1970 and 1/1/2037
+        private val instances: WeakHashMap<Context, WeakReference<CalendarController>> =
+                WeakHashMap<Context, WeakReference<CalendarController>>()
+
+        /**
+         * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time
+         * can be ignored
+         */
+        const val EXTRA_GOTO_DATE: Long = 1
+        const val EXTRA_GOTO_TIME: Long = 2
+        const val EXTRA_GOTO_BACK_TO_PREVIOUS: Long = 4
+        const val EXTRA_GOTO_TODAY: Long = 8
+
+        /**
+         * Creates and/or returns an instance of CalendarController associated with
+         * the supplied context. It is best to pass in the current Activity.
+         *
+         * @param context The activity if at all possible.
+         */
+        @JvmStatic fun getInstance(context: Context?): CalendarController? {
+            synchronized(instances) {
+                var controller: CalendarController? = null
+                val weakController: WeakReference<CalendarController>? = instances.get(context)
+                if (weakController != null) {
+                    controller = weakController.get()
+                }
+                if (controller == null) {
+                    controller = CalendarController(context)
+                    instances.put(context, WeakReference(controller))
+                }
+                return controller
+            }
+        }
+
+        /**
+         * Removes an instance when it is no longer needed. This should be called in
+         * an activity's onDestroy method.
+         *
+         * @param context The activity used to create the controller
+         */
+        @JvmStatic fun removeInstance(context: Context?) {
+            instances.remove(context)
+        }
+    }
+
+    init {
+        mContext = context
+        mUpdateTimezone.run()
+        mTime?.setToNow()
+        mDetailViewType = Utils.getSharedPreference(
+                mContext,
+                GeneralPreferences.KEY_DETAILED_VIEW,
+                GeneralPreferences.DEFAULT_DETAILED_VIEW
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/CalendarData.java b/src/com/android/calendar/CalendarData.java
deleted file mode 100644
index 5c8456f..0000000
--- a/src/com/android/calendar/CalendarData.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-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
new file mode 100644
index 0000000..7370f2e
--- /dev/null
+++ b/src/com/android/calendar/CalendarData.kt
@@ -0,0 +1,29 @@
+/*
+ * 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
deleted file mode 100644
index 0238c32..0000000
--- a/src/com/android/calendar/CalendarUtils.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.Looper;
-import android.provider.CalendarContract.CalendarCache;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import java.util.Formatter;
-import java.util.HashSet;
-import java.util.Locale;
-
-/**
- * A class containing utility methods related to Calendar apps.
- *
- * This class is expected to move into the app framework eventually.
- */
-public class CalendarUtils {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "CalendarUtils";
-
-    /**
-     * This class contains methods specific to reading and writing time zone
-     * values.
-     */
-    public static class TimeZoneUtils {
-        private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.KEY_TIMEZONE_TYPE };
-        private static final String[] TIMEZONE_INSTANCES_ARGS =
-                { CalendarCache.KEY_TIMEZONE_INSTANCES };
-        public static final String[] CALENDAR_CACHE_POJECTION = {
-                CalendarCache.KEY, CalendarCache.VALUE
-        };
-
-        private static StringBuilder mSB = new StringBuilder(50);
-        private static Formatter mF = new Formatter(mSB, Locale.getDefault());
-        private volatile static boolean mFirstTZRequest = true;
-        private volatile static boolean mTZQueryInProgress = false;
-
-        private volatile static boolean mUseHomeTZ = false;
-        private volatile static String mHomeTZ = Time.getCurrentTimezone();
-
-        private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
-        private static int mToken = 1;
-        private static AsyncTZHandler mHandler;
-
-        // The name of the shared preferences file. This name must be maintained for historical
-        // reasons, as it's what PreferenceManager assigned the first time the file was created.
-        private final String mPrefsName;
-
-        /**
-         * This is the key used for writing whether or not a home time zone should
-         * be used in the Calendar app to the Calendar Preferences.
-         */
-        public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
-        /**
-         * This is the key used for writing the time zone that should be used if
-         * home time zones are enabled for the Calendar app.
-         */
-        public static final String KEY_HOME_TZ = "preferences_home_tz";
-
-        /**
-         * This is a helper class for handling the async queries and updates for the
-         * time zone settings in Calendar.
-         */
-        private class AsyncTZHandler extends AsyncQueryHandler {
-            public AsyncTZHandler(ContentResolver cr) {
-                super(cr);
-            }
-
-            @Override
-            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                synchronized (mTZCallbacks) {
-                    if (cursor == null) {
-                        mTZQueryInProgress = false;
-                        mFirstTZRequest = true;
-                        return;
-                    }
-
-                    boolean writePrefs = false;
-                    // Check the values in the db
-                    int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
-                    int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
-                    while(cursor.moveToNext()) {
-                        String key = cursor.getString(keyColumn);
-                        String value = cursor.getString(valueColumn);
-                        if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
-                            boolean useHomeTZ = !TextUtils.equals(
-                                    value, CalendarCache.TIMEZONE_TYPE_AUTO);
-                            if (useHomeTZ != mUseHomeTZ) {
-                                writePrefs = true;
-                                mUseHomeTZ = useHomeTZ;
-                            }
-                        } else if (TextUtils.equals(
-                                key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
-                            if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
-                                writePrefs = true;
-                                mHomeTZ = value;
-                            }
-                        }
-                    }
-                    cursor.close();
-                    if (writePrefs) {
-                        SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
-                        // Write the prefs
-                        setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
-                        setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
-                    }
-
-                    mTZQueryInProgress = false;
-                    for (Runnable callback : mTZCallbacks) {
-                        if (callback != null) {
-                            callback.run();
-                        }
-                    }
-                    mTZCallbacks.clear();
-                }
-            }
-        }
-
-        /**
-         * The name of the file where the shared prefs for Calendar are stored
-         * must be provided. All activities within an app should provide the
-         * same preferences name or behavior may become erratic.
-         *
-         * @param prefsName
-         */
-        public TimeZoneUtils(String prefsName) {
-            mPrefsName = prefsName;
-        }
-
-        /**
-         * Formats a date or a time range according to the local conventions.
-         *
-         * This formats a date/time range using Calendar's time zone and the
-         * local conventions for the region of the device.
-         *
-         * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
-         * the UTC time zone instead.
-         *
-         * @param context the context is required only if the time is shown
-         * @param startMillis the start time in UTC milliseconds
-         * @param endMillis the end time in UTC milliseconds
-         * @param flags a bit mask of options See
-         * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
-         * @return a string containing the formatted date/time range.
-         */
-        public String formatDateRange(Context context, long startMillis,
-                long endMillis, int flags) {
-            String date;
-            String tz;
-            if ((flags & DateUtils.FORMAT_UTC) != 0) {
-                tz = Time.TIMEZONE_UTC;
-            } else {
-                tz = getTimeZone(context, null);
-            }
-            synchronized (mSB) {
-                mSB.setLength(0);
-                date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
-                        tz).toString();
-            }
-            return date;
-        }
-
-        /**
-         * Writes a new home time zone to the db.
-         *
-         * Updates the home time zone in the db asynchronously and updates
-         * the local cache. Sending a time zone of
-         * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
-         * to the device's time zone. null or empty tz will be ignored.
-         *
-         * @param context The calling activity
-         * @param timeZone The time zone to set Calendar to, or
-         * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
-         */
-        public void setTimeZone(Context context, String timeZone) {
-            if (TextUtils.isEmpty(timeZone)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Empty time zone, nothing to be done.");
-                }
-                return;
-            }
-            boolean updatePrefs = false;
-            synchronized (mTZCallbacks) {
-                if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
-                    if (mUseHomeTZ) {
-                        updatePrefs = true;
-                    }
-                    mUseHomeTZ = false;
-                } else {
-                    if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
-                        updatePrefs = true;
-                    }
-                    mUseHomeTZ = true;
-                    mHomeTZ = timeZone;
-                }
-            }
-            if (updatePrefs) {
-                // Write the prefs
-                SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
-                setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
-                setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
-
-                // Update the db
-                ContentValues values = new ContentValues();
-                if (mHandler != null) {
-                    mHandler.cancelOperation(mToken);
-                }
-
-                mHandler = new AsyncTZHandler(context.getContentResolver());
-
-                // skip 0 so query can use it
-                if (++mToken == 0) {
-                    mToken = 1;
-                }
-
-                // Write the use home tz setting
-                values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
-                        : CalendarCache.TIMEZONE_TYPE_AUTO);
-                mHandler.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
-                        TIMEZONE_TYPE_ARGS);
-
-                // If using a home tz write it to the db
-                if (mUseHomeTZ) {
-                    ContentValues values2 = new ContentValues();
-                    values2.put(CalendarCache.VALUE, mHomeTZ);
-                    mHandler.startUpdate(mToken, null, CalendarCache.URI, values2,
-                            "key=?", TIMEZONE_INSTANCES_ARGS);
-                }
-            }
-        }
-
-        /**
-         * Gets the time zone that Calendar should be displayed in
-         *
-         * This is a helper method to get the appropriate time zone for Calendar. If this
-         * is the first time this method has been called it will initiate an asynchronous
-         * query to verify that the data in preferences is correct. The callback supplied
-         * will only be called if this query returns a value other than what is stored in
-         * preferences and should cause the calling activity to refresh anything that
-         * depends on calling this method.
-         *
-         * @param context The calling activity
-         * @param callback The runnable that should execute if a query returns new values
-         * @return The string value representing the time zone Calendar should display
-         */
-        public String getTimeZone(Context context, Runnable callback) {
-            synchronized (mTZCallbacks){
-                if (mFirstTZRequest) {
-                    SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
-                    mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
-                    mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
-
-                    // Only check content resolver if we have a looper to attach to use
-                    if (Looper.myLooper() != null) {
-                        mTZQueryInProgress = true;
-                        mFirstTZRequest = false;
-
-                        // When the async query returns it should synchronize on
-                        // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
-                        // preferences, set mTZQueryInProgress to false, and call all
-                        // the runnables in mTZCallbacks.
-                        if (mHandler == null) {
-                            mHandler = new AsyncTZHandler(context.getContentResolver());
-                        }
-                        mHandler.startQuery(0, context, CalendarCache.URI, CALENDAR_CACHE_POJECTION,
-                                null, null, null);
-                    }
-                }
-                if (mTZQueryInProgress) {
-                    mTZCallbacks.add(callback);
-                }
-            }
-            return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
-        }
-
-        /**
-         * Forces a query of the database to check for changes to the time zone.
-         * This should be called if another app may have modified the db. If a
-         * query is already in progress the callback will be added to the list
-         * of callbacks to be called when it returns.
-         *
-         * @param context The calling activity
-         * @param callback The runnable that should execute if a query returns
-         *            new values
-         */
-        public void forceDBRequery(Context context, Runnable callback) {
-            synchronized (mTZCallbacks){
-                if (mTZQueryInProgress) {
-                    mTZCallbacks.add(callback);
-                    return;
-                }
-                mFirstTZRequest = true;
-                getTimeZone(context, callback);
-            }
-        }
-    }
-
-        /**
-         * A helper method for writing a String value to the preferences
-         * asynchronously.
-         *
-         * @param context A context with access to the correct preferences
-         * @param key The preference to write to
-         * @param value The value to write
-         */
-        public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
-//            SharedPreferences prefs = getSharedPreferences(context);
-            SharedPreferences.Editor editor = prefs.edit();
-            editor.putString(key, value);
-            editor.apply();
-        }
-
-        /**
-         * A helper method for writing a boolean value to the preferences
-         * asynchronously.
-         *
-         * @param context A context with access to the correct preferences
-         * @param key The preference to write to
-         * @param value The value to write
-         */
-        public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
-//            SharedPreferences prefs = getSharedPreferences(context, prefsName);
-            SharedPreferences.Editor editor = prefs.edit();
-            editor.putBoolean(key, value);
-            editor.apply();
-        }
-
-        /** Return a properly configured SharedPreferences instance */
-        public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
-            return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
-        }
-}
diff --git a/src/com/android/calendar/CalendarUtils.kt b/src/com/android/calendar/CalendarUtils.kt
new file mode 100644
index 0000000..94ca723
--- /dev/null
+++ b/src/com/android/calendar/CalendarUtils.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.AsyncQueryHandler
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.SharedPreferences
+import android.database.Cursor
+import android.os.Looper
+import android.provider.CalendarContract.CalendarCache
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+
+import java.util.Formatter
+import java.util.HashSet
+import java.util.Locale
+
+/**
+ * A class containing utility methods related to Calendar apps.
+ *
+ * This class is expected to move into the app framework eventually.
+ */
+class CalendarUtils {
+
+    companion object {
+        private const val DEBUG = false
+        private const val TAG = "CalendarUtils"
+
+        /**
+         * A helper method for writing a boolean value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        @JvmStatic
+        fun setSharedPreference(prefs: SharedPreferences, key: String?, value: Boolean) {
+            val editor: SharedPreferences.Editor = prefs.edit()
+            editor.putBoolean(key, value)
+            editor.apply()
+        }
+
+        /** Return a properly configured SharedPreferences instance  */
+        @JvmStatic
+        fun getSharedPreferences(context: Context, prefsName: String?): SharedPreferences {
+            return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
+        }
+
+        /**
+         * A helper method for writing a String value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        @JvmStatic
+        fun setSharedPreference(prefs: SharedPreferences, key: String?, value: String?) {
+            val editor: SharedPreferences.Editor = prefs.edit()
+            editor.putString(key, value)
+            editor.apply()
+        }
+    }
+
+    /**
+     * This class contains methods specific to reading and writing time zone
+     * values.
+     */
+    class TimeZoneUtils
+    /**
+     * The name of the file where the shared prefs for Calendar are stored
+     * must be provided. All activities within an app should provide the
+     * same preferences name or behavior may become erratic.
+     *
+     * @param prefsName
+     */(  // The name of the shared preferences file. This name must be maintained for historical
+            // reasons, as it's what PreferenceManager assigned the first time the file was created.
+            private val mPrefsName: String) {
+        /**
+         * This is a helper class for handling the async queries and updates for the
+         * time zone settings in Calendar.
+         */
+        private inner class AsyncTZHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) {
+            protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) {
+                synchronized(mTZCallbacks) {
+                    if (cursor == null) {
+                        mTZQueryInProgress = false
+                        mFirstTZRequest = true
+                        return
+                    }
+                    var writePrefs = false
+                    // Check the values in the db
+                    val keyColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.KEY)
+                    val valueColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.VALUE)
+                    while (cursor.moveToNext()) {
+                        val key: String = cursor.getString(keyColumn)
+                        val value: String = cursor.getString(valueColumn)
+                        if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
+                            val useHomeTZ: Boolean = !TextUtils.equals(
+                                    value, CalendarCache.TIMEZONE_TYPE_AUTO)
+                            if (useHomeTZ != mUseHomeTZ) {
+                                writePrefs = true
+                                mUseHomeTZ = useHomeTZ
+                            }
+                        } else if (TextUtils.equals(
+                                        key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
+                            if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
+                                writePrefs = true
+                                mHomeTZ = value
+                            }
+                        }
+                    }
+                    cursor.close()
+                    if (writePrefs) {
+                        val prefs: SharedPreferences =
+                        getSharedPreferences(cookie as Context, mPrefsName)
+                        // Write the prefs
+                        setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ)
+                        setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ)
+                    }
+                    mTZQueryInProgress = false
+                    for (callback in mTZCallbacks) {
+                        if (callback != null) {
+                            callback.run()
+                        }
+                    }
+                    mTZCallbacks.clear()
+                }
+            }
+        }
+
+        /**
+         * Formats a date or a time range according to the local conventions.
+         *
+         * This formats a date/time range using Calendar's time zone and the
+         * local conventions for the region of the device.
+         *
+         * If the [DateUtils.FORMAT_UTC] flag is used it will pass in
+         * the UTC time zone instead.
+         *
+         * @param context the context is required only if the time is shown
+         * @param startMillis the start time in UTC milliseconds
+         * @param endMillis the end time in UTC milliseconds
+         * @param flags a bit mask of options See
+         * [formatDateRange][DateUtils.formatDateRange]
+         * @return a string containing the formatted date/time range.
+         */
+        fun formatDateRange(context: Context, startMillis: Long,
+                            endMillis: Long, flags: Int): String {
+            var date: String
+            val tz: String
+            tz = if (flags and DateUtils.FORMAT_UTC !== 0) {
+                Time.TIMEZONE_UTC
+            } else {
+                getTimeZone(context, null)
+            }
+            synchronized(mSB) {
+                mSB.setLength(0)
+                date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
+                        tz).toString()
+            }
+            return date
+        }
+
+        /**
+         * Writes a new home time zone to the db.
+         *
+         * Updates the home time zone in the db asynchronously and updates
+         * the local cache. Sending a time zone of
+         * [CalendarCache.TIMEZONE_TYPE_AUTO] will cause it to be set
+         * to the device's time zone. null or empty tz will be ignored.
+         *
+         * @param context The calling activity
+         * @param timeZone The time zone to set Calendar to, or
+         * [CalendarCache.TIMEZONE_TYPE_AUTO]
+         */
+        fun setTimeZone(context: Context, timeZone: String) {
+            if (TextUtils.isEmpty(timeZone)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Empty time zone, nothing to be done.")
+                }
+                return
+            }
+            var updatePrefs = false
+            synchronized(mTZCallbacks) {
+                if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
+                    if (mUseHomeTZ) {
+                        updatePrefs = true
+                    }
+                    mUseHomeTZ = false
+                } else {
+                    if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
+                        updatePrefs = true
+                    }
+                    mUseHomeTZ = true
+                    mHomeTZ = timeZone
+                }
+            }
+            if (updatePrefs) {
+                // Write the prefs
+                val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName)
+                setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ)
+                setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ)
+
+                // Update the db
+                val values = ContentValues()
+                if (mHandler != null) {
+                    mHandler?.cancelOperation(mToken)
+                }
+                mHandler = AsyncTZHandler(context.getContentResolver())
+
+                // skip 0 so query can use it
+                if (++mToken == 0) {
+                    mToken = 1
+                }
+
+                // Write the use home tz setting
+                values.put(CalendarCache.VALUE, if (mUseHomeTZ) CalendarCache.TIMEZONE_TYPE_HOME
+                           else CalendarCache.TIMEZONE_TYPE_AUTO)
+                mHandler?.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
+                        TIMEZONE_TYPE_ARGS)
+
+                // If using a home tz write it to the db
+                if (mUseHomeTZ) {
+                    val values2 = ContentValues()
+                    values2.put(CalendarCache.VALUE, mHomeTZ)
+                    mHandler?.startUpdate(mToken, null, CalendarCache.URI, values2,
+                            "key=?", TIMEZONE_INSTANCES_ARGS)
+                }
+            }
+        }
+
+        /**
+         * Gets the time zone that Calendar should be displayed in
+         *
+         * This is a helper method to get the appropriate time zone for Calendar. If this
+         * is the first time this method has been called it will initiate an asynchronous
+         * query to verify that the data in preferences is correct. The callback supplied
+         * will only be called if this query returns a value other than what is stored in
+         * preferences and should cause the calling activity to refresh anything that
+         * depends on calling this method.
+         *
+         * @param context The calling activity
+         * @param callback The runnable that should execute if a query returns new values
+         * @return The string value representing the time zone Calendar should display
+         */
+        fun getTimeZone(context: Context, callback: Runnable?): String {
+            synchronized(mTZCallbacks) {
+                if (mFirstTZRequest) {
+                    val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName)
+                    mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)
+                    mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone()) ?: String()
+
+                    // Only check content resolver if we have a looper to attach to use
+                    if (Looper.myLooper() != null) {
+                        mTZQueryInProgress = true
+                        mFirstTZRequest = false
+
+                        // When the async query returns it should synchronize on
+                        // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
+                        // preferences, set mTZQueryInProgress to false, and call all
+                        // the runnables in mTZCallbacks.
+                        if (mHandler == null) {
+                            mHandler = AsyncTZHandler(context.getContentResolver())
+                        }
+                        mHandler?.startQuery(0, context, CalendarCache.URI,
+                                             CALENDAR_CACHE_POJECTION, null, null, null)
+                    }
+                }
+                if (mTZQueryInProgress && callback != null) {
+                    mTZCallbacks.add(callback)
+                }
+            }
+            return if (mUseHomeTZ) mHomeTZ else Time.getCurrentTimezone()
+        }
+
+        /**
+         * Forces a query of the database to check for changes to the time zone.
+         * This should be called if another app may have modified the db. If a
+         * query is already in progress the callback will be added to the list
+         * of callbacks to be called when it returns.
+         *
+         * @param context The calling activity
+         * @param callback The runnable that should execute if a query returns
+         * new values
+         */
+        fun forceDBRequery(context: Context, callback: Runnable) {
+            synchronized(mTZCallbacks) {
+                if (mTZQueryInProgress) {
+                    mTZCallbacks.add(callback)
+                    return
+                }
+                mFirstTZRequest = true
+                getTimeZone(context, callback)
+            }
+        }
+
+        companion object {
+            private val TIMEZONE_TYPE_ARGS = arrayOf<String>(CalendarCache.KEY_TIMEZONE_TYPE)
+            private val TIMEZONE_INSTANCES_ARGS =
+            arrayOf<String>(CalendarCache.KEY_TIMEZONE_INSTANCES)
+            val CALENDAR_CACHE_POJECTION = arrayOf<String>(
+                    CalendarCache.KEY, CalendarCache.VALUE
+            )
+            private val mSB: StringBuilder = StringBuilder(50)
+            private val mF: Formatter = Formatter(mSB, Locale.getDefault())
+
+            @Volatile
+            private var mFirstTZRequest = true
+
+            @Volatile
+            private var mTZQueryInProgress = false
+
+            @Volatile
+            private var mUseHomeTZ = false
+
+            @Volatile
+            private var mHomeTZ: String = Time.getCurrentTimezone()
+            private val mTZCallbacks: HashSet<Runnable> = HashSet<Runnable>()
+            private var mToken = 1
+            private var mHandler: AsyncTZHandler? = null
+
+            /**
+             * This is the key used for writing whether or not a home time zone should
+             * be used in the Calendar app to the Calendar Preferences.
+             */
+            const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled"
+
+            /**
+             * This is the key used for writing the time zone that should be used if
+             * home time zones are enabled for the Calendar app.
+             */
+            const val KEY_HOME_TZ = "preferences_home_tz"
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/CalendarViewAdapter.java b/src/com/android/calendar/CalendarViewAdapter.java
deleted file mode 100644
index 524268f..0000000
--- a/src/com/android/calendar/CalendarViewAdapter.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import com.android.calendar.CalendarController.ViewType;
-
-import android.content.Context;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import java.util.Formatter;
-import java.util.Locale;
-
-
-/*
- * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu
- * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts
- *
- * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu.
- */
-
-public class CalendarViewAdapter extends BaseAdapter {
-
-    private static final String TAG = "MenuSpinnerAdapter";
-
-    private final String mButtonNames [];           // Text on buttons
-
-    // Used to define the look of the menu button according to the current view:
-    // Day view: show day of the week + full date underneath
-    // Week view: show the month + year
-    // Month view: show the month + year
-    // Agenda view: show day of the week + full date underneath
-    private int mCurrentMainView;
-
-    private final LayoutInflater mInflater;
-
-    // Defines the types of view returned by this spinner
-    private static final int BUTTON_VIEW_TYPE = 0;
-    static final int VIEW_TYPE_NUM = 1;  // Increase this if you add more view types
-
-    public static final int DAY_BUTTON_INDEX = 0;
-    public static final int WEEK_BUTTON_INDEX = 1;
-    public static final int MONTH_BUTTON_INDEX = 2;
-    public static final int AGENDA_BUTTON_INDEX = 3;
-
-    // The current selected event's time, used to calculate the date and day of the week
-    // for the buttons.
-    private long mMilliTime;
-    private String mTimeZone;
-    private long mTodayJulianDay;
-
-    private final Context mContext;
-    private final Formatter mFormatter;
-    private final StringBuilder mStringBuilder;
-    private Handler mMidnightHandler = null; // Used to run a time update every midnight
-    private final boolean mShowDate;   // Spinner mode indicator (view name or view name with date)
-
-    // Updates time specific variables (time-zone, today's Julian day).
-    private final Runnable mTimeUpdater = new Runnable() {
-        @Override
-        public void run() {
-            refresh(mContext);
-        }
-    };
-
-    public CalendarViewAdapter(Context context, int viewType, boolean showDate) {
-        super();
-
-        mMidnightHandler = new Handler();
-        mCurrentMainView = viewType;
-        mContext = context;
-        mShowDate = showDate;
-
-        // Initialize
-        mButtonNames = context.getResources().getStringArray(R.array.buttons_list);
-        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mStringBuilder = new StringBuilder(50);
-        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
-        // Sets time specific variables and starts a thread for midnight updates
-        if (showDate) {
-            refresh(context);
-        }
-    }
-
-
-    // Sets the time zone and today's Julian day to be used by the adapter.
-    // Also, notify listener on the change and resets the midnight update thread.
-    public void refresh(Context context) {
-        mTimeZone = Utils.getTimeZone(context, mTimeUpdater);
-        Time time = new Time(mTimeZone);
-        long now = System.currentTimeMillis();
-        time.set(now);
-        mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
-        notifyDataSetChanged();
-        setMidnightHandler();
-    }
-
-    // Sets a thread to run 1 second after midnight and update the current date
-    // This is used to display correctly the date of yesterday/today/tomorrow
-    private void setMidnightHandler() {
-        mMidnightHandler.removeCallbacks(mTimeUpdater);
-        // Set the time updater to run at 1 second after midnight
-        long now = System.currentTimeMillis();
-        Time time = new Time(mTimeZone);
-        time.set(now);
-        long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 -
-                time.second + 1) * 1000;
-        mMidnightHandler.postDelayed(mTimeUpdater, runInMillis);
-    }
-
-    // Stops the midnight update thread, called by the activity when it is paused.
-    public void onPause() {
-        mMidnightHandler.removeCallbacks(mTimeUpdater);
-    }
-
-    // Returns the amount of buttons in the menu
-    @Override
-    public int getCount() {
-        return mButtonNames.length;
-    }
-
-
-    @Override
-    public Object getItem(int position) {
-        if (position < mButtonNames.length) {
-            return mButtonNames[position];
-        }
-        return null;
-    }
-
-    @Override
-    public long getItemId(int position) {
-        // Item ID is its location in the list
-        return position;
-    }
-
-    @Override
-    public boolean hasStableIds() {
-        return false;
-    }
-
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-
-        View v;
-
-        if (mShowDate) {
-            // Check if can recycle the view
-            if (convertView == null || ((Integer) convertView.getTag()).intValue()
-                    != R.layout.actionbar_pulldown_menu_top_button) {
-                v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false);
-                // Set the tag to make sure you can recycle it when you get it
-                // as a convert view
-                v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button));
-            } else {
-                v = convertView;
-            }
-            TextView weekDay = (TextView) v.findViewById(R.id.top_button_weekday);
-            TextView date = (TextView) v.findViewById(R.id.top_button_date);
-
-            switch (mCurrentMainView) {
-                case ViewType.DAY:
-                    weekDay.setVisibility(View.VISIBLE);
-                    weekDay.setText(buildDayOfWeek());
-                    date.setText(buildFullDate());
-                    break;
-                case ViewType.WEEK:
-                    if (Utils.getShowWeekNumber(mContext)) {
-                        weekDay.setVisibility(View.VISIBLE);
-                        weekDay.setText(buildWeekNum());
-                    } else {
-                        weekDay.setVisibility(View.GONE);
-                    }
-                    date.setText(buildMonthYearDate());
-                    break;
-                case ViewType.MONTH:
-                    weekDay.setVisibility(View.GONE);
-                    date.setText(buildMonthYearDate());
-                    break;
-                default:
-                    v = null;
-                    break;
-            }
-        } else {
-            if (convertView == null || ((Integer) convertView.getTag()).intValue()
-                    != R.layout.actionbar_pulldown_menu_top_button_no_date) {
-                v = mInflater.inflate(
-                        R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false);
-                // Set the tag to make sure you can recycle it when you get it
-                // as a convert view
-                v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button_no_date));
-            } else {
-                v = convertView;
-            }
-            TextView title = (TextView) v;
-            switch (mCurrentMainView) {
-                case ViewType.DAY:
-                    title.setText(mButtonNames [DAY_BUTTON_INDEX]);
-                    break;
-                case ViewType.WEEK:
-                    title.setText(mButtonNames [WEEK_BUTTON_INDEX]);
-                    break;
-                case ViewType.MONTH:
-                    title.setText(mButtonNames [MONTH_BUTTON_INDEX]);
-                    break;
-                default:
-                    v = null;
-                    break;
-            }
-        }
-        return v;
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        // Only one kind of view is used
-        return BUTTON_VIEW_TYPE;
-    }
-
-    @Override
-    public int getViewTypeCount() {
-        return VIEW_TYPE_NUM;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return (mButtonNames.length == 0);
-    }
-
-    @Override
-    public View getDropDownView(int position, View convertView, ViewGroup parent) {
-        View v = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false);
-        TextView viewType = (TextView)v.findViewById(R.id.button_view);
-        TextView date = (TextView)v.findViewById(R.id.button_date);
-        switch (position) {
-            case DAY_BUTTON_INDEX:
-                viewType.setText(mButtonNames [DAY_BUTTON_INDEX]);
-                if (mShowDate) {
-                    date.setText(buildMonthDayDate());
-                }
-                break;
-            case WEEK_BUTTON_INDEX:
-                viewType.setText(mButtonNames [WEEK_BUTTON_INDEX]);
-                if (mShowDate) {
-                    date.setText(buildWeekDate());
-                }
-                break;
-            case MONTH_BUTTON_INDEX:
-                viewType.setText(mButtonNames [MONTH_BUTTON_INDEX]);
-                if (mShowDate) {
-                    date.setText(buildMonthDate());
-                }
-                break;
-            default:
-                v = convertView;
-                break;
-        }
-        return v;
-    }
-
-    // Updates the current viewType
-    // Used to match the label on the menu button with the calendar view
-    public void setMainView(int viewType) {
-        mCurrentMainView = viewType;
-        notifyDataSetChanged();
-    }
-
-    // Update the date that is displayed on buttons
-    // Used when the user selects a new day/week/month to watch
-    public void setTime(long time) {
-        mMilliTime = time;
-        notifyDataSetChanged();
-    }
-
-    // Builds a string with the day of the week and the word yesterday/today/tomorrow
-    // before it if applicable.
-    private String buildDayOfWeek() {
-
-        Time t = new Time(mTimeZone);
-        t.set(mMilliTime);
-        long julianDay = Time.getJulianDay(mMilliTime,t.gmtoff);
-        String dayOfWeek = null;
-        mStringBuilder.setLength(0);
-
-        if (julianDay == mTodayJulianDay) {
-            dayOfWeek = mContext.getString(R.string.agenda_today,
-                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
-        } else if (julianDay == mTodayJulianDay - 1) {
-            dayOfWeek = mContext.getString(R.string.agenda_yesterday,
-                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
-        } else if (julianDay == mTodayJulianDay + 1) {
-            dayOfWeek = mContext.getString(R.string.agenda_tomorrow,
-                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString());
-        } else {
-            dayOfWeek = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                    DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString();
-        }
-        return dayOfWeek.toUpperCase();
-    }
-
-    // Builds strings with different formats:
-    // Full date: Month,day Year
-    // Month year
-    // Month day
-    // Month
-    // Week:  month day-day or month day - month day
-    private String buildFullDate() {
-        mStringBuilder.setLength(0);
-        String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString();
-        return date;
-    }
-
-    private String buildMonthYearDate() {
-        mStringBuilder.setLength(0);
-        String date = DateUtils.formatDateRange(
-                mContext,
-                mFormatter,
-                mMilliTime,
-                mMilliTime,
-                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
-                        | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString();
-        return date;
-    }
-
-    private String buildMonthDayDate() {
-        mStringBuilder.setLength(0);
-        String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
-                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR, mTimeZone).toString();
-        return date;
-    }
-
-    private String buildMonthDate() {
-        mStringBuilder.setLength(0);
-        String date = DateUtils.formatDateRange(
-                mContext,
-                mFormatter,
-                mMilliTime,
-                mMilliTime,
-                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR
-                        | DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString();
-        return date;
-    }
-
-    private String buildWeekDate() {
-        // Calculate the start of the week, taking into account the "first day of the week"
-        // setting.
-
-        Time t = new Time(mTimeZone);
-        t.set(mMilliTime);
-        int firstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
-        int dayOfWeek = t.weekDay;
-        int diff = dayOfWeek - firstDayOfWeek;
-        if (diff != 0) {
-            if (diff < 0) {
-                diff += 7;
-            }
-            t.monthDay -= diff;
-            t.normalize(true /* ignore isDst */);
-        }
-
-        long weekStartTime = t.toMillis(true);
-        // The end of the week is 6 days after the start of the week
-        long weekEndTime = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS;
-
-        // If week start and end is in 2 different months, use short months names
-        Time t1 = new Time(mTimeZone);
-        t.set(weekEndTime);
-        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
-        if (t.month != t1.month) {
-            flags |= DateUtils.FORMAT_ABBREV_MONTH;
-        }
-
-        mStringBuilder.setLength(0);
-        String date = DateUtils.formatDateRange(mContext, mFormatter, weekStartTime,
-                weekEndTime, flags, mTimeZone).toString();
-         return date;
-    }
-
-    private String buildWeekNum() {
-        int week = Utils.getWeekNumberFromTime(mMilliTime, mContext);
-        return mContext.getResources().getQuantityString(R.plurals.weekN, week, week);
-    }
-
-}
diff --git a/src/com/android/calendar/CalendarViewAdapter.kt b/src/com/android/calendar/CalendarViewAdapter.kt
new file mode 100644
index 0000000..2fe1027
--- /dev/null
+++ b/src/com/android/calendar/CalendarViewAdapter.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import com.android.calendar.CalendarController.ViewType
+import android.content.Context
+import android.os.Handler
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.TextView
+import java.util.Formatter
+import java.util.Locale
+
+/*
+ * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu
+ * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts
+ *
+ * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu.
+ */
+class CalendarViewAdapter(context: Context, viewType: Int, showDate: Boolean) : BaseAdapter() {
+    private val mButtonNames: Array<String> // Text on buttons
+
+    // Used to define the look of the menu button according to the current view:
+    // Day view: show day of the week + full date underneath
+    // Week view: show the month + year
+    // Month view: show the month + year
+    // Agenda view: show day of the week + full date underneath
+    private var mCurrentMainView: Int
+    private val mInflater: LayoutInflater
+
+    // The current selected event's time, used to calculate the date and day of the week
+    // for the buttons.
+    private var mMilliTime: Long = 0
+    private var mTimeZone: String? = null
+    private var mTodayJulianDay: Long = 0
+    private val mContext: Context = context
+    private val mFormatter: Formatter
+    private val mStringBuilder: StringBuilder
+    private var mMidnightHandler: Handler? = null // Used to run a time update every midnight
+    private val mShowDate: Boolean // Spinner mode indicator (view name or view name with date)
+
+    // Updates time specific variables (time-zone, today's Julian day).
+    private val mTimeUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            refresh(mContext)
+        }
+    }
+
+    // Sets the time zone and today's Julian day to be used by the adapter.
+    // Also, notify listener on the change and resets the midnight update thread.
+    fun refresh(context: Context?) {
+        mTimeZone = Utils.getTimeZone(context, mTimeUpdater)
+        val time = Time(mTimeZone)
+        val now: Long = System.currentTimeMillis()
+        time.set(now)
+        mTodayJulianDay = Time.getJulianDay(now, time.gmtoff).toLong()
+        notifyDataSetChanged()
+        setMidnightHandler()
+    }
+
+    // Sets a thread to run 1 second after midnight and update the current date
+    // This is used to display correctly the date of yesterday/today/tomorrow
+    private fun setMidnightHandler() {
+        mMidnightHandler?.removeCallbacks(mTimeUpdater)
+        // Set the time updater to run at 1 second after midnight
+        val now: Long = System.currentTimeMillis()
+        val time = Time(mTimeZone)
+        time.set(now)
+        val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 -
+                time.second + 1) * 1000).toLong()
+        mMidnightHandler?.postDelayed(mTimeUpdater, runInMillis)
+    }
+
+    // Stops the midnight update thread, called by the activity when it is paused.
+    fun onPause() {
+        mMidnightHandler?.removeCallbacks(mTimeUpdater)
+    }
+
+    // Returns the amount of buttons in the menu
+    @Override
+    override fun getCount(): Int {
+        return mButtonNames.size
+    }
+
+    @Override
+    override fun getItem(position: Int): Any? {
+        return if (position < mButtonNames.size) {
+            mButtonNames[position]
+        } else null
+    }
+
+    @Override
+    override fun getItemId(position: Int): Long {
+        // Item ID is its location in the list
+        return position.toLong()
+    }
+
+    @Override
+    override fun hasStableIds(): Boolean {
+        return false
+    }
+
+    @Override
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+        var v: View?
+        if (mShowDate) {
+            // Check if can recycle the view
+            if (convertView == null || (convertView.getTag() as Int)
+                    != R.layout.actionbar_pulldown_menu_top_button as Int) {
+                v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false)
+                // Set the tag to make sure you can recycle it when you get it
+                // as a convert view
+                v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button))
+            } else {
+                v = convertView
+            }
+            val weekDay: TextView = v?.findViewById(R.id.top_button_weekday) as TextView
+            val date: TextView = v?.findViewById(R.id.top_button_date) as TextView
+            when (mCurrentMainView) {
+                ViewType.DAY -> {
+                    weekDay.setVisibility(View.VISIBLE)
+                    weekDay.setText(buildDayOfWeek())
+                    date.setText(buildFullDate())
+                }
+                ViewType.WEEK -> {
+                    if (Utils.getShowWeekNumber(mContext)) {
+                        weekDay.setVisibility(View.VISIBLE)
+                        weekDay.setText(buildWeekNum())
+                    } else {
+                        weekDay.setVisibility(View.GONE)
+                    }
+                    date.setText(buildMonthYearDate())
+                }
+                ViewType.MONTH -> {
+                    weekDay.setVisibility(View.GONE)
+                    date.setText(buildMonthYearDate())
+                }
+                else -> v = null
+            }
+        } else {
+            if (convertView == null || (convertView.getTag() as Int)
+                    != R.layout.actionbar_pulldown_menu_top_button_no_date as Int) {
+                v = mInflater.inflate(
+                        R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false)
+                // Set the tag to make sure you can recycle it when you get it
+                // as a convert view
+                v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button_no_date))
+            } else {
+                v = convertView
+            }
+            val title: TextView? = v as TextView?
+            when (mCurrentMainView) {
+                ViewType.DAY -> title?.setText(mButtonNames[DAY_BUTTON_INDEX])
+                ViewType.WEEK -> title?.setText(mButtonNames[WEEK_BUTTON_INDEX])
+                ViewType.MONTH -> title?.setText(mButtonNames[MONTH_BUTTON_INDEX])
+                else -> v = null
+            }
+        }
+        return v
+    }
+
+    @Override
+    override fun getItemViewType(position: Int): Int {
+        // Only one kind of view is used
+        return BUTTON_VIEW_TYPE
+    }
+
+    @Override
+    override fun getViewTypeCount(): Int {
+        return VIEW_TYPE_NUM
+    }
+
+    @Override
+    override fun isEmpty(): Boolean {
+        return mButtonNames.size == 0
+    }
+
+    @Override
+    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+        var v: View? = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false)
+        val viewType: TextView? = v?.findViewById(R.id.button_view) as? TextView
+        val date: TextView? = v?.findViewById(R.id.button_date) as? TextView
+        when (position) {
+            DAY_BUTTON_INDEX -> {
+                viewType?.setText(mButtonNames[DAY_BUTTON_INDEX])
+                if (mShowDate) {
+                    date?.setText(buildMonthDayDate())
+                }
+            }
+            WEEK_BUTTON_INDEX -> {
+                viewType?.setText(mButtonNames[WEEK_BUTTON_INDEX])
+                if (mShowDate) {
+                    date?.setText(buildWeekDate())
+                }
+            }
+            MONTH_BUTTON_INDEX -> {
+                viewType?.setText(mButtonNames[MONTH_BUTTON_INDEX])
+                if (mShowDate) {
+                    date?.setText(buildMonthDate())
+                }
+            }
+            else -> v = convertView
+        }
+        return v
+    }
+
+    // Updates the current viewType
+    // Used to match the label on the menu button with the calendar view
+    fun setMainView(viewType: Int) {
+        mCurrentMainView = viewType
+        notifyDataSetChanged()
+    }
+
+    // Update the date that is displayed on buttons
+    // Used when the user selects a new day/week/month to watch
+    fun setTime(time: Long) {
+        mMilliTime = time
+        notifyDataSetChanged()
+    }
+
+    // Builds a string with the day of the week and the word yesterday/today/tomorrow
+    // before it if applicable.
+    private fun buildDayOfWeek(): String {
+        val t = Time(mTimeZone)
+        t.set(mMilliTime)
+        val julianDay: Long = Time.getJulianDay(mMilliTime, t.gmtoff).toLong()
+        var dayOfWeek: String? = null
+        mStringBuilder.setLength(0)
+        dayOfWeek = if (julianDay == mTodayJulianDay) {
+            mContext.getString(R.string.agenda_today,
+                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+        } else if (julianDay == mTodayJulianDay - 1) {
+            mContext.getString(R.string.agenda_yesterday,
+                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+        } else if (julianDay == mTodayJulianDay + 1) {
+            mContext.getString(R.string.agenda_tomorrow,
+                    DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                            DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString())
+        } else {
+            DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                    DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()
+        }
+        return dayOfWeek.toUpperCase()
+    }
+
+    // Builds strings with different formats:
+    // Full date: Month,day Year
+    // Month year
+    // Month day
+    // Month
+    // Week:  month day-day or month day - month day
+    private fun buildFullDate(): String {
+        mStringBuilder.setLength(0)
+        return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString()
+    }
+
+    private fun buildMonthYearDate(): String {
+        mStringBuilder.setLength(0)
+        return DateUtils.formatDateRange(
+                mContext,
+                mFormatter,
+                mMilliTime,
+                mMilliTime,
+                DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY
+                        or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString()
+    }
+
+    private fun buildMonthDayDate(): String {
+        mStringBuilder.setLength(0)
+        return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime,
+                DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR, mTimeZone).toString()
+    }
+
+    private fun buildMonthDate(): String {
+        mStringBuilder.setLength(0)
+        return DateUtils.formatDateRange(
+                mContext,
+                mFormatter,
+                mMilliTime,
+                mMilliTime,
+                DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR
+                        or DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString()
+    }
+
+    private fun buildWeekDate(): String {
+        // Calculate the start of the week, taking into account the "first day of the week"
+        // setting.
+        val t = Time(mTimeZone)
+        t.set(mMilliTime)
+        val firstDayOfWeek: Int = Utils.getFirstDayOfWeek(mContext)
+        val dayOfWeek: Int = t.weekDay
+        var diff = dayOfWeek - firstDayOfWeek
+        if (diff != 0) {
+            if (diff < 0) {
+                diff += 7
+            }
+            t.monthDay -= diff
+            t.normalize(true /* ignore isDst */)
+        }
+        val weekStartTime: Long = t.toMillis(true)
+        // The end of the week is 6 days after the start of the week
+        val weekEndTime: Long = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS
+
+        // If week start and end is in 2 different months, use short months names
+        val t1 = Time(mTimeZone)
+        t.set(weekEndTime)
+        var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR
+        if (t.month !== t1.month) {
+            flags = flags or DateUtils.FORMAT_ABBREV_MONTH
+        }
+        mStringBuilder.setLength(0)
+        return DateUtils.formatDateRange(mContext, mFormatter, weekStartTime,
+                weekEndTime, flags, mTimeZone).toString()
+    }
+
+    private fun buildWeekNum(): String {
+        val week: Int = Utils.getWeekNumberFromTime(mMilliTime, mContext)
+        return mContext.getResources().getQuantityString(R.plurals.weekN, week, week)
+    }
+
+    companion object {
+        private const val TAG = "MenuSpinnerAdapter"
+
+        // Defines the types of view returned by this spinner
+        private const val BUTTON_VIEW_TYPE = 0
+        const val VIEW_TYPE_NUM = 1 // Increase this if you add more view types
+        const val DAY_BUTTON_INDEX = 0
+        const val WEEK_BUTTON_INDEX = 1
+        const val MONTH_BUTTON_INDEX = 2
+        const val AGENDA_BUTTON_INDEX = 3
+    }
+
+    init {
+        mMidnightHandler = Handler()
+        mCurrentMainView = viewType
+        mShowDate = showDate
+
+        // Initialize
+        mButtonNames = context.getResources().getStringArray(R.array.buttons_list)
+        mInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        mStringBuilder = StringBuilder(50)
+        mFormatter = Formatter(mStringBuilder, Locale.getDefault())
+
+        // Sets time specific variables and starts a thread for midnight updates
+        if (showDate) {
+            refresh(context)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/DayFragment.java b/src/com/android/calendar/DayFragment.java
deleted file mode 100644
index a9fb39e..0000000
--- a/src/com/android/calendar/DayFragment.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import com.android.calendar.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.text.format.Time;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.ProgressBar;
-import android.widget.ViewSwitcher;
-import android.widget.ViewSwitcher.ViewFactory;
-
-/**
- * This is the base class for Day and Week Activities.
- */
-public class DayFragment extends Fragment implements CalendarController.EventHandler, ViewFactory {
-    /**
-     * The view id used for all the views we create. It's OK to have all child
-     * views have the same ID. This ID is used to pick which view receives
-     * focus when a view hierarchy is saved / restore
-     */
-    private static final int VIEW_ID = 1;
-
-    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
-
-    protected ProgressBar mProgressBar;
-    protected ViewSwitcher mViewSwitcher;
-    protected Animation mInAnimationForward;
-    protected Animation mOutAnimationForward;
-    protected Animation mInAnimationBackward;
-    protected Animation mOutAnimationBackward;
-    EventLoader mEventLoader;
-
-    Time mSelectedDay = new Time();
-
-    private final Runnable mTZUpdater = new Runnable() {
-        @Override
-        public void run() {
-            if (!DayFragment.this.isAdded()) {
-                return;
-            }
-            String tz = Utils.getTimeZone(getActivity(), mTZUpdater);
-            mSelectedDay.timezone = tz;
-            mSelectedDay.normalize(true);
-        }
-    };
-
-    private int mNumDays;
-
-    public DayFragment() {
-        mSelectedDay.setToNow();
-    }
-
-    public DayFragment(long timeMillis, int numOfDays) {
-        mNumDays = numOfDays;
-        if (timeMillis == 0) {
-            mSelectedDay.setToNow();
-        } else {
-            mSelectedDay.set(timeMillis);
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        Context context = getActivity();
-
-        mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in);
-        mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out);
-        mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in);
-        mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out);
-
-        mEventLoader = new EventLoader(context);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View v = inflater.inflate(R.layout.day_activity, null);
-
-        mViewSwitcher = (ViewSwitcher) v.findViewById(R.id.switcher);
-        mViewSwitcher.setFactory(this);
-        mViewSwitcher.getCurrentView().requestFocus();
-        ((DayView) mViewSwitcher.getCurrentView()).updateTitle();
-
-        return v;
-    }
-
-    public View makeView() {
-        mTZUpdater.run();
-        DayView view = new DayView(getActivity(), CalendarController
-                .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays);
-        view.setId(VIEW_ID);
-        view.setLayoutParams(new ViewSwitcher.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        view.setSelected(mSelectedDay, false, false);
-        return view;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mEventLoader.startBackgroundThread();
-        mTZUpdater.run();
-        eventsChanged();
-        DayView view = (DayView) mViewSwitcher.getCurrentView();
-        view.handleOnResume();
-        view.restartCurrentTimeUpdates();
-
-        view = (DayView) mViewSwitcher.getNextView();
-        view.handleOnResume();
-        view.restartCurrentTimeUpdates();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        DayView view = (DayView) mViewSwitcher.getCurrentView();
-        view.cleanup();
-        view = (DayView) mViewSwitcher.getNextView();
-        view.cleanup();
-        mEventLoader.stopBackgroundThread();
-
-        // Stop events cross-fade animation
-        view.stopEventsAnimation();
-        ((DayView) mViewSwitcher.getNextView()).stopEventsAnimation();
-    }
-
-    void startProgressSpinner() {
-        // start the progress spinner
-        mProgressBar.setVisibility(View.VISIBLE);
-    }
-
-    void stopProgressSpinner() {
-        // stop the progress spinner
-        mProgressBar.setVisibility(View.GONE);
-    }
-
-    private void goTo(Time goToTime, boolean ignoreTime, boolean animateToday) {
-        if (mViewSwitcher == null) {
-            // The view hasn't been set yet. Just save the time and use it later.
-            mSelectedDay.set(goToTime);
-            return;
-        }
-
-        DayView currentView = (DayView) mViewSwitcher.getCurrentView();
-
-        // How does goTo time compared to what's already displaying?
-        int diff = currentView.compareToVisibleTimeRange(goToTime);
-
-        if (diff == 0) {
-            // In visible range. No need to switch view
-            currentView.setSelected(goToTime, ignoreTime, animateToday);
-        } else {
-            // Figure out which way to animate
-            if (diff > 0) {
-                mViewSwitcher.setInAnimation(mInAnimationForward);
-                mViewSwitcher.setOutAnimation(mOutAnimationForward);
-            } else {
-                mViewSwitcher.setInAnimation(mInAnimationBackward);
-                mViewSwitcher.setOutAnimation(mOutAnimationBackward);
-            }
-
-            DayView next = (DayView) mViewSwitcher.getNextView();
-            if (ignoreTime) {
-                next.setFirstVisibleHour(currentView.getFirstVisibleHour());
-            }
-
-            next.setSelected(goToTime, ignoreTime, animateToday);
-            next.reloadEvents();
-            mViewSwitcher.showNext();
-            next.requestFocus();
-            next.updateTitle();
-            next.restartCurrentTimeUpdates();
-        }
-    }
-
-    /**
-     * Returns the selected time in milliseconds. The milliseconds are measured
-     * in UTC milliseconds from the epoch and uniquely specifies any selectable
-     * time.
-     *
-     * @return the selected time in milliseconds
-     */
-    public long getSelectedTimeInMillis() {
-        if (mViewSwitcher == null) {
-            return -1;
-        }
-        DayView view = (DayView) mViewSwitcher.getCurrentView();
-        if (view == null) {
-            return -1;
-        }
-        return view.getSelectedTimeInMillis();
-    }
-
-    public void eventsChanged() {
-        if (mViewSwitcher == null) {
-            return;
-        }
-        DayView view = (DayView) mViewSwitcher.getCurrentView();
-        view.clearCachedEvents();
-        view.reloadEvents();
-
-        view = (DayView) mViewSwitcher.getNextView();
-        view.clearCachedEvents();
-    }
-
-    public DayView getNextView() {
-        return (DayView) mViewSwitcher.getNextView();
-    }
-
-    public long getSupportedEventTypes() {
-        return EventType.GO_TO | EventType.EVENTS_CHANGED;
-    }
-
-    public void handleEvent(EventInfo msg) {
-        if (msg.eventType == EventType.GO_TO) {
-// TODO support a range of time
-// TODO support event_id
-// TODO support select message
-            goTo(msg.selectedTime, (msg.extraLong & CalendarController.EXTRA_GOTO_DATE) != 0,
-                    (msg.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0);
-        } else if (msg.eventType == EventType.EVENTS_CHANGED) {
-            eventsChanged();
-        }
-    }
-}
diff --git a/src/com/android/calendar/DayFragment.kt b/src/com/android/calendar/DayFragment.kt
new file mode 100644
index 0000000..39e92f5
--- /dev/null
+++ b/src/com/android/calendar/DayFragment.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import android.app.Fragment
+import android.content.Context
+import android.os.Bundle
+import android.text.format.Time
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout.LayoutParams
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.ProgressBar
+import android.widget.ViewSwitcher
+import android.widget.ViewSwitcher.ViewFactory
+
+/**
+ * This is the base class for Day and Week Activities.
+ */
+class DayFragment : Fragment, CalendarController.EventHandler, ViewFactory {
+    protected var mProgressBar: ProgressBar? = null
+    protected var mViewSwitcher: ViewSwitcher? = null
+    protected var mInAnimationForward: Animation? = null
+    protected var mOutAnimationForward: Animation? = null
+    protected var mInAnimationBackward: Animation? = null
+    protected var mOutAnimationBackward: Animation? = null
+    var mEventLoader: EventLoader? = null
+    var mSelectedDay: Time = Time()
+    private val mTZUpdater: Runnable = object : Runnable {
+        override fun run() {
+            if (!this@DayFragment.isAdded()) {
+                return
+            }
+            val tz: String? = Utils.getTimeZone(getActivity(), this)
+            mSelectedDay.timezone = tz
+            mSelectedDay.normalize(true)
+        }
+    }
+    private var mNumDays = 0
+
+    constructor() {
+        mSelectedDay.setToNow()
+    }
+
+    constructor(timeMillis: Long, numOfDays: Int) {
+        mNumDays = numOfDays
+        if (timeMillis == 0L) {
+            mSelectedDay.setToNow()
+        } else {
+            mSelectedDay.set(timeMillis)
+        }
+    }
+
+    override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        val context: Context = getActivity()
+        mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in)
+        mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out)
+        mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in)
+        mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out)
+        mEventLoader = EventLoader(context)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater?,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val v: View? = inflater?.inflate(R.layout.day_activity, null)
+        mViewSwitcher = v?.findViewById(R.id.switcher) as? ViewSwitcher
+        mViewSwitcher?.setFactory(this)
+        mViewSwitcher?.getCurrentView()?.requestFocus()
+        (mViewSwitcher?.getCurrentView() as? DayView)?.updateTitle()
+        return v
+    }
+
+    override fun makeView(): View {
+        mTZUpdater.run()
+        val view = DayView(getActivity(), CalendarController
+                .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays)
+        view.setId(DayFragment.Companion.VIEW_ID)
+        view.setLayoutParams(LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
+        view.setSelected(mSelectedDay, false, false)
+        return view
+    }
+
+    override fun onResume() {
+        super.onResume()
+        mEventLoader!!.startBackgroundThread()
+        mTZUpdater.run()
+        eventsChanged()
+        var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+        view?.handleOnResume()
+        view?.restartCurrentTimeUpdates()
+        view = mViewSwitcher?.getNextView() as? DayView
+        view?.handleOnResume()
+        view?.restartCurrentTimeUpdates()
+    }
+
+    override fun onSaveInstanceState(outState: Bundle?) {
+        super.onSaveInstanceState(outState)
+    }
+
+    override fun onPause() {
+        super.onPause()
+        var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+        view?.cleanup()
+        view = mViewSwitcher?.getNextView() as? DayView
+        view?.cleanup()
+        mEventLoader!!.stopBackgroundThread()
+
+        // Stop events cross-fade animation
+        view?.stopEventsAnimation()
+        (mViewSwitcher?.getNextView() as? DayView)?.stopEventsAnimation()
+    }
+
+    fun startProgressSpinner() {
+        // start the progress spinner
+        mProgressBar?.setVisibility(View.VISIBLE)
+    }
+
+    fun stopProgressSpinner() {
+        // stop the progress spinner
+        mProgressBar?.setVisibility(View.GONE)
+    }
+
+    private fun goTo(goToTime: Time?, ignoreTime: Boolean, animateToday: Boolean) {
+        if (mViewSwitcher == null) {
+            // The view hasn't been set yet. Just save the time and use it later.
+            mSelectedDay.set(goToTime)
+            return
+        }
+        val currentView: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+
+        // How does goTo time compared to what's already displaying?
+        val diff: Int = currentView?.compareToVisibleTimeRange(goToTime as Time) as Int
+        if (diff == 0) {
+            // In visible range. No need to switch view
+            currentView?.setSelected(goToTime, ignoreTime, animateToday)
+        } else {
+            // Figure out which way to animate
+            if (diff > 0) {
+                mViewSwitcher?.setInAnimation(mInAnimationForward)
+                mViewSwitcher?.setOutAnimation(mOutAnimationForward)
+            } else {
+                mViewSwitcher?.setInAnimation(mInAnimationBackward)
+                mViewSwitcher?.setOutAnimation(mOutAnimationBackward)
+            }
+            val next: DayView? = mViewSwitcher?.getNextView() as? DayView
+            if (ignoreTime) {
+                next!!.firstVisibleHour = currentView.firstVisibleHour
+            }
+            next?.setSelected(goToTime, ignoreTime, animateToday)
+            next?.reloadEvents()
+            mViewSwitcher?.showNext()
+            next?.requestFocus()
+            next?.updateTitle()
+            next?.restartCurrentTimeUpdates()
+        }
+    }
+
+    /**
+     * Returns the selected time in milliseconds. The milliseconds are measured
+     * in UTC milliseconds from the epoch and uniquely specifies any selectable
+     * time.
+     *
+     * @return the selected time in milliseconds
+     */
+    val selectedTimeInMillis: Long
+        get() {
+            if (mViewSwitcher == null) {
+                return -1
+            }
+            val view: DayView = mViewSwitcher?.getCurrentView() as DayView ?: return -1
+            return view.selectedTimeInMillis
+        }
+
+    override fun eventsChanged() {
+        if (mViewSwitcher == null) {
+            return
+        }
+        var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
+        view?.clearCachedEvents()
+        view?.reloadEvents()
+        view = mViewSwitcher?.getNextView() as? DayView
+        view?.clearCachedEvents()
+    }
+
+    val nextView: DayView?
+        get() = mViewSwitcher?.getNextView() as? DayView
+    override val supportedEventTypes: Long
+        get() = CalendarController.EventType.GO_TO or CalendarController.EventType.EVENTS_CHANGED
+
+    override fun handleEvent(msg: CalendarController.EventInfo?) {
+        if (msg?.eventType == CalendarController.EventType.GO_TO) {
+// TODO support a range of time
+// TODO support event_id
+// TODO support select message
+            goTo(msg?.selectedTime, msg?.extraLong and CalendarController.EXTRA_GOTO_DATE != 0L,
+                    msg?.extraLong and CalendarController.EXTRA_GOTO_TODAY != 0L)
+        } else if (msg?.eventType == CalendarController.EventType.EVENTS_CHANGED) {
+            eventsChanged()
+        }
+    }
+
+    companion object {
+        /**
+         * The view id used for all the views we create. It's OK to have all child
+         * views have the same ID. This ID is used to pick which view receives
+         * focus when a view hierarchy is saved / restore
+         */
+        private const val VIEW_ID = 1
+        protected const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/DayOfMonthDrawable.java b/src/com/android/calendar/DayOfMonthDrawable.java
deleted file mode 100644
index 461ab31..0000000
--- a/src/com/android/calendar/DayOfMonthDrawable.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-
-/**
- * A custom view to draw the day of the month in the today button in the options menu
- */
-
-public class DayOfMonthDrawable extends Drawable {
-
-    private String mDayOfMonth = "1";
-    private final Paint mPaint;
-    private final Rect mTextBounds = new Rect();
-    private static float mTextSize = 14;
-
-    public DayOfMonthDrawable(Context c) {
-        mTextSize = c.getResources().getDimension(R.dimen.today_icon_text_size);
-        mPaint = new Paint();
-        mPaint.setAlpha(255);
-        mPaint.setColor(0xFF777777);
-        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
-        mPaint.setTextSize(mTextSize);
-        mPaint.setTextAlign(Paint.Align.CENTER);
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        mPaint.getTextBounds(mDayOfMonth, 0, mDayOfMonth.length(), mTextBounds);
-        int textHeight = mTextBounds.bottom - mTextBounds.top;
-        Rect bounds = getBounds();
-        canvas.drawText(mDayOfMonth, bounds.right / 2, ((float) bounds.bottom + textHeight + 1) / 2,
-                mPaint);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mPaint.setAlpha(alpha);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        // Ignore
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.UNKNOWN;
-    }
-
-    public void setDayOfMonth(int day) {
-        mDayOfMonth = Integer.toString(day);
-        invalidateSelf();
-    }
-}
diff --git a/src/com/android/calendar/DayOfMonthDrawable.kt b/src/com/android/calendar/DayOfMonthDrawable.kt
new file mode 100644
index 0000000..e348b5a
--- /dev/null
+++ b/src/com/android/calendar/DayOfMonthDrawable.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+
+/**
+ * A custom view to draw the day of the month in the today button in the options menu
+ */
+class DayOfMonthDrawable(c: Context) : Drawable() {
+    private var mDayOfMonth = "1"
+    private val mPaint: Paint
+    private val mTextBounds: Rect = Rect()
+    override fun draw(canvas: Canvas) {
+        mPaint.getTextBounds(mDayOfMonth, 0, mDayOfMonth.length, mTextBounds)
+        val textHeight: Int = mTextBounds.bottom - mTextBounds.top
+        val bounds: Rect = getBounds()
+        canvas.drawText(
+            mDayOfMonth, (bounds.right).toFloat() / 2f, ((bounds.bottom).toFloat() +
+                textHeight + 1) / 2f, mPaint
+        )
+    }
+
+    override fun setAlpha(alpha: Int) {
+        mPaint.setAlpha(alpha)
+    }
+
+    override fun setColorFilter(cf: ColorFilter?) {
+        // Ignore
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.UNKNOWN
+    }
+
+    fun setDayOfMonth(day: Int) {
+        mDayOfMonth = Integer.toString(day)
+        invalidateSelf()
+    }
+
+    companion object {
+        private var mTextSize = 14f
+    }
+
+    init {
+        mTextSize = c.getResources().getDimension(R.dimen.today_icon_text_size)
+        mPaint = Paint()
+        mPaint.setAlpha(255)
+        mPaint.setColor(-0x888889)
+        mPaint.setTypeface(Typeface.DEFAULT_BOLD)
+        mPaint.setTextSize(mTextSize)
+        mPaint.setTextAlign(Paint.Align.CENTER)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/DayView.java b/src/com/android/calendar/DayView.java
deleted file mode 100644
index 2fc00b3..0000000
--- a/src/com/android/calendar/DayView.java
+++ /dev/null
@@ -1,4008 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.app.AlertDialog;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.Interpolator;
-import android.view.animation.TranslateAnimation;
-import android.widget.EdgeEffect;
-import android.widget.ImageView;
-import android.widget.OverScroller;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-import android.widget.ViewSwitcher;
-
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * View for multi-day view. So far only 1 and 7 day have been tested.
- */
-public class DayView extends View implements View.OnCreateContextMenuListener,
-        ScaleGestureDetector.OnScaleGestureListener, View.OnClickListener, View.OnLongClickListener
-        {
-    private static String TAG = "DayView";
-    private static boolean DEBUG = false;
-    private static boolean DEBUG_SCALING = false;
-    private static final String PERIOD_SPACE = ". ";
-
-    private static float mScale = 0; // Used for supporting different screen densities
-    private static final long INVALID_EVENT_ID = -1; //This is used for remembering a null event
-    // Duration of the allday expansion
-    private static final long ANIMATION_DURATION = 400;
-    // duration of the more allday event text fade
-    private static final long ANIMATION_SECONDARY_DURATION = 200;
-    // duration of the scroll to go to a specified time
-    private static final int GOTO_SCROLL_DURATION = 200;
-    // duration for events' cross-fade animation
-    private static final int EVENTS_CROSS_FADE_DURATION = 400;
-    // duration to show the event clicked
-    private static final int CLICK_DISPLAY_DURATION = 50;
-
-    private static final int MENU_DAY = 3;
-    private static final int MENU_EVENT_VIEW = 5;
-    private static final int MENU_EVENT_CREATE = 6;
-    private static final int MENU_EVENT_EDIT = 7;
-    private static final int MENU_EVENT_DELETE = 8;
-
-    private static int DEFAULT_CELL_HEIGHT = 64;
-    private static int MAX_CELL_HEIGHT = 150;
-    private static int MIN_Y_SPAN = 100;
-
-    private boolean mOnFlingCalled;
-    private boolean mStartingScroll = false;
-    protected boolean mPaused = true;
-    private Handler mHandler;
-    /**
-     * ID of the last event which was displayed with the toast popup.
-     *
-     * This is used to prevent popping up multiple quick views for the same event, especially
-     * during calendar syncs. This becomes valid when an event is selected, either by default
-     * on starting calendar or by scrolling to an event. It becomes invalid when the user
-     * explicitly scrolls to an empty time slot, changes views, or deletes the event.
-     */
-    private long mLastPopupEventID;
-
-    protected Context mContext;
-
-    private static final String[] CALENDARS_PROJECTION = new String[] {
-        Calendars._ID,          // 0
-        Calendars.CALENDAR_ACCESS_LEVEL, // 1
-        Calendars.OWNER_ACCOUNT, // 2
-    };
-    private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
-    private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
-    private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
-
-    private static final int FROM_NONE = 0;
-    private static final int FROM_ABOVE = 1;
-    private static final int FROM_BELOW = 2;
-    private static final int FROM_LEFT = 4;
-    private static final int FROM_RIGHT = 8;
-
-    private static final int ACCESS_LEVEL_NONE = 0;
-    private static final int ACCESS_LEVEL_DELETE = 1;
-    private static final int ACCESS_LEVEL_EDIT = 2;
-
-    private static int mHorizontalSnapBackThreshold = 128;
-
-    private final ContinueScroll mContinueScroll = new ContinueScroll();
-
-    // Make this visible within the package for more informative debugging
-    Time mBaseDate;
-    private Time mCurrentTime;
-    //Update the current time line every five minutes if the window is left open that long
-    private static final int UPDATE_CURRENT_TIME_DELAY = 300000;
-    private final UpdateCurrentTime mUpdateCurrentTime = new UpdateCurrentTime();
-    private int mTodayJulianDay;
-
-    private final Typeface mBold = Typeface.DEFAULT_BOLD;
-    private int mFirstJulianDay;
-    private int mLoadedFirstJulianDay = -1;
-    private int mLastJulianDay;
-
-    private int mMonthLength;
-    private int mFirstVisibleDate;
-    private int mFirstVisibleDayOfWeek;
-    private int[] mEarliestStartHour;    // indexed by the week day offset
-    private boolean[] mHasAllDayEvent;   // indexed by the week day offset
-    private String mEventCountTemplate;
-    private Event mClickedEvent;           // The event the user clicked on
-    private Event mSavedClickedEvent;
-    private static int mOnDownDelay;
-    private int mClickedYLocation;
-    private long mDownTouchTime;
-
-    private int mEventsAlpha = 255;
-    private ObjectAnimator mEventsCrossFadeAnimation;
-
-    protected static StringBuilder mStringBuilder = new StringBuilder(50);
-    // TODO recreate formatter when locale changes
-    protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
-    private final Runnable mTZUpdater = new Runnable() {
-        @Override
-        public void run() {
-            String tz = Utils.getTimeZone(mContext, this);
-            mBaseDate.timezone = tz;
-            mBaseDate.normalize(true);
-            mCurrentTime.switchTimezone(tz);
-            invalidate();
-        }
-    };
-
-    // Sets the "clicked" color from the clicked event
-    private final Runnable mSetClick = new Runnable() {
-        @Override
-        public void run() {
-                mClickedEvent = mSavedClickedEvent;
-                mSavedClickedEvent = null;
-                DayView.this.invalidate();
-        }
-    };
-
-    // Clears the "clicked" color from the clicked event and launch the event
-    private final Runnable mClearClick = new Runnable() {
-        @Override
-        public void run() {
-            if (mClickedEvent != null) {
-                mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, mClickedEvent.id,
-                        mClickedEvent.startMillis, mClickedEvent.endMillis,
-                        DayView.this.getWidth() / 2, mClickedYLocation,
-                        getSelectedTimeInMillis());
-            }
-            mClickedEvent = null;
-            DayView.this.invalidate();
-        }
-    };
-
-    private final TodayAnimatorListener mTodayAnimatorListener = new TodayAnimatorListener();
-
-    class TodayAnimatorListener extends AnimatorListenerAdapter {
-        private volatile Animator mAnimator = null;
-        private volatile boolean mFadingIn = false;
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            synchronized (this) {
-                if (mAnimator != animation) {
-                    animation.removeAllListeners();
-                    animation.cancel();
-                    return;
-                }
-                if (mFadingIn) {
-                    if (mTodayAnimator != null) {
-                        mTodayAnimator.removeAllListeners();
-                        mTodayAnimator.cancel();
-                    }
-                    mTodayAnimator = ObjectAnimator
-                            .ofInt(DayView.this, "animateTodayAlpha", 255, 0);
-                    mAnimator = mTodayAnimator;
-                    mFadingIn = false;
-                    mTodayAnimator.addListener(this);
-                    mTodayAnimator.setDuration(600);
-                    mTodayAnimator.start();
-                } else {
-                    mAnimateToday = false;
-                    mAnimateTodayAlpha = 0;
-                    mAnimator.removeAllListeners();
-                    mAnimator = null;
-                    mTodayAnimator = null;
-                    invalidate();
-                }
-            }
-        }
-
-        public void setAnimator(Animator animation) {
-            mAnimator = animation;
-        }
-
-        public void setFadingIn(boolean fadingIn) {
-            mFadingIn = fadingIn;
-        }
-
-    }
-
-    AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationStart(Animator animation) {
-            mScrolling = true;
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-            mScrolling = false;
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mScrolling = false;
-            resetSelectedHour();
-            invalidate();
-        }
-    };
-
-    /**
-     * This variable helps to avoid unnecessarily reloading events by keeping
-     * track of the start millis parameter used for the most recent loading
-     * of events.  If the next reload matches this, then the events are not
-     * reloaded.  To force a reload, set this to zero (this is set to zero
-     * in the method clearCachedEvents()).
-     */
-    private long mLastReloadMillis;
-
-    private ArrayList<Event> mEvents = new ArrayList<Event>();
-    private ArrayList<Event> mAllDayEvents = new ArrayList<Event>();
-    private StaticLayout[] mLayouts = null;
-    private StaticLayout[] mAllDayLayouts = null;
-    private int mSelectionDay;        // Julian day
-    private int mSelectionHour;
-
-    boolean mSelectionAllday;
-
-    // Current selection info for accessibility
-    private int mSelectionDayForAccessibility;        // Julian day
-    private int mSelectionHourForAccessibility;
-    private Event mSelectedEventForAccessibility;
-    // Last selection info for accessibility
-    private int mLastSelectionDayForAccessibility;
-    private int mLastSelectionHourForAccessibility;
-    private Event mLastSelectedEventForAccessibility;
-
-
-    /** Width of a day or non-conflicting event */
-    private int mCellWidth;
-
-    // Pre-allocate these objects and re-use them
-    private final Rect mRect = new Rect();
-    private final Rect mDestRect = new Rect();
-    private final Rect mSelectionRect = new Rect();
-    // This encloses the more allDay events icon
-    private final Rect mExpandAllDayRect = new Rect();
-    // TODO Clean up paint usage
-    private final Paint mPaint = new Paint();
-    private final Paint mEventTextPaint = new Paint();
-    private final Paint mSelectionPaint = new Paint();
-    private float[] mLines;
-
-    private int mFirstDayOfWeek; // First day of the week
-
-    private PopupWindow mPopup;
-    private View mPopupView;
-
-    // The number of milliseconds to show the popup window
-    private static final int POPUP_DISMISS_DELAY = 3000;
-    private final DismissPopup mDismissPopup = new DismissPopup();
-
-    private boolean mRemeasure = true;
-
-    private final EventLoader mEventLoader;
-    protected final EventGeometry mEventGeometry;
-
-    private static float GRID_LINE_LEFT_MARGIN = 0;
-    private static final float GRID_LINE_INNER_WIDTH = 1;
-
-    private static final int DAY_GAP = 1;
-    private static final int HOUR_GAP = 1;
-    // This is the standard height of an allday event with no restrictions
-    private static int SINGLE_ALLDAY_HEIGHT = 34;
-    /**
-    * This is the minimum desired height of a allday event.
-    * When unexpanded, allday events will use this height.
-    * When expanded allDay events will attempt to grow to fit all
-    * events at this height.
-    */
-    private static float MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = 28.0F; // in pixels
-    /**
-     * This is how big the unexpanded allday height is allowed to be.
-     * It will get adjusted based on screen size
-     */
-    private static int MAX_UNEXPANDED_ALLDAY_HEIGHT =
-            (int) (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4);
-    /**
-     * This is the minimum size reserved for displaying regular events.
-     * The expanded allDay region can't expand into this.
-     */
-    private static int MIN_HOURS_HEIGHT = 180;
-    private static int ALLDAY_TOP_MARGIN = 1;
-    // The largest a single allDay event will become.
-    private static int MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34;
-
-    private static int HOURS_TOP_MARGIN = 2;
-    private static int HOURS_LEFT_MARGIN = 2;
-    private static int HOURS_RIGHT_MARGIN = 4;
-    private static int HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
-    private static int NEW_EVENT_MARGIN = 4;
-    private static int NEW_EVENT_WIDTH = 2;
-    private static int NEW_EVENT_MAX_LENGTH = 16;
-
-    private static int CURRENT_TIME_LINE_SIDE_BUFFER = 4;
-    private static int CURRENT_TIME_LINE_TOP_OFFSET = 2;
-
-    /* package */ static final int MINUTES_PER_HOUR = 60;
-    /* package */ static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
-    /* package */ static final int MILLIS_PER_MINUTE = 60 * 1000;
-    /* package */ static final int MILLIS_PER_HOUR = (3600 * 1000);
-    /* package */ static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
-
-    // More events text will transition between invisible and this alpha
-    private static final int MORE_EVENTS_MAX_ALPHA = 0x4C;
-    private static int DAY_HEADER_ONE_DAY_LEFT_MARGIN = 0;
-    private static int DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5;
-    private static int DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6;
-    private static int DAY_HEADER_RIGHT_MARGIN = 4;
-    private static int DAY_HEADER_BOTTOM_MARGIN = 3;
-    private static float DAY_HEADER_FONT_SIZE = 14;
-    private static float DATE_HEADER_FONT_SIZE = 32;
-    private static float NORMAL_FONT_SIZE = 12;
-    private static float EVENT_TEXT_FONT_SIZE = 12;
-    private static float HOURS_TEXT_SIZE = 12;
-    private static float AMPM_TEXT_SIZE = 9;
-    private static int MIN_HOURS_WIDTH = 96;
-    private static int MIN_CELL_WIDTH_FOR_TEXT = 20;
-    private static final int MAX_EVENT_TEXT_LEN = 500;
-    // smallest height to draw an event with
-    private static float MIN_EVENT_HEIGHT = 24.0F; // in pixels
-    private static int CALENDAR_COLOR_SQUARE_SIZE = 10;
-    private static int EVENT_RECT_TOP_MARGIN = 1;
-    private static int EVENT_RECT_BOTTOM_MARGIN = 0;
-    private static int EVENT_RECT_LEFT_MARGIN = 1;
-    private static int EVENT_RECT_RIGHT_MARGIN = 0;
-    private static int EVENT_RECT_STROKE_WIDTH = 2;
-    private static int EVENT_TEXT_TOP_MARGIN = 2;
-    private static int EVENT_TEXT_BOTTOM_MARGIN = 2;
-    private static int EVENT_TEXT_LEFT_MARGIN = 6;
-    private static int EVENT_TEXT_RIGHT_MARGIN = 6;
-    private static int ALL_DAY_EVENT_RECT_BOTTOM_MARGIN = 1;
-    private static int EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN;
-    private static int EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN;
-    private static int EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
-    private static int EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN;
-    // margins and sizing for the expand allday icon
-    private static int EXPAND_ALL_DAY_BOTTOM_MARGIN = 10;
-    // sizing for "box +n" in allDay events
-    private static int EVENT_SQUARE_WIDTH = 10;
-    private static int EVENT_LINE_PADDING = 4;
-    private static int NEW_EVENT_HINT_FONT_SIZE = 12;
-
-    private static int mEventTextColor;
-    private static int mMoreEventsTextColor;
-
-    private static int mWeek_saturdayColor;
-    private static int mWeek_sundayColor;
-    private static int mCalendarDateBannerTextColor;
-    private static int mCalendarAmPmLabel;
-    private static int mCalendarGridAreaSelected;
-    private static int mCalendarGridLineInnerHorizontalColor;
-    private static int mCalendarGridLineInnerVerticalColor;
-    private static int mFutureBgColor;
-    private static int mFutureBgColorRes;
-    private static int mBgColor;
-    private static int mNewEventHintColor;
-    private static int mCalendarHourLabelColor;
-    private static int mMoreAlldayEventsTextAlpha = MORE_EVENTS_MAX_ALPHA;
-
-    private float mAnimationDistance = 0;
-    private int mViewStartX;
-    private int mViewStartY;
-    private int mMaxViewStartY;
-    private int mViewHeight;
-    private int mViewWidth;
-    private int mGridAreaHeight = -1;
-    private static int mCellHeight = 0; // shared among all DayViews
-    private static int mMinCellHeight = 32;
-    private int mScrollStartY;
-    private int mPreviousDirection;
-    private static int mScaledPagingTouchSlop = 0;
-
-    /**
-     * Vertical distance or span between the two touch points at the start of a
-     * scaling gesture
-     */
-    private float mStartingSpanY = 0;
-    /** Height of 1 hour in pixels at the start of a scaling gesture */
-    private int mCellHeightBeforeScaleGesture;
-    /** The hour at the center two touch points */
-    private float mGestureCenterHour = 0;
-
-    private boolean mRecalCenterHour = false;
-
-    /**
-     * Flag to decide whether to handle the up event. Cases where up events
-     * should be ignored are 1) right after a scale gesture and 2) finger was
-     * down before app launch
-     */
-    private boolean mHandleActionUp = true;
-
-    private int mHoursTextHeight;
-    /**
-     * The height of the area used for allday events
-     */
-    private int mAlldayHeight;
-    /**
-     * The height of the allday event area used during animation
-     */
-    private int mAnimateDayHeight = 0;
-    /**
-     * The height of an individual allday event during animation
-     */
-    private int mAnimateDayEventHeight = (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-    /**
-     * Whether to use the expand or collapse icon.
-     */
-    private static boolean mUseExpandIcon = true;
-    /**
-     * The height of the day names/numbers
-     */
-    private static int DAY_HEADER_HEIGHT = 45;
-    /**
-     * The height of the day names/numbers for multi-day views
-     */
-    private static int MULTI_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT;
-    /**
-     * The height of the day names/numbers when viewing a single day
-     */
-    private static int ONE_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT;
-    /**
-     * Max of all day events in a given day in this view.
-     */
-    private int mMaxAlldayEvents;
-    /**
-     * A count of the number of allday events that were not drawn for each day
-     */
-    private int[] mSkippedAlldayEvents;
-    /**
-     * The number of allDay events at which point we start hiding allDay events.
-     */
-    private int mMaxUnexpandedAlldayEventCount = 4;
-    /**
-     * Whether or not to expand the allDay area to fill the screen
-     */
-    private static boolean mShowAllAllDayEvents = false;
-
-    protected int mNumDays = 7;
-    private int mNumHours = 10;
-
-    /** Width of the time line (list of hours) to the left. */
-    private int mHoursWidth;
-    private int mDateStrWidth;
-    /** Top of the scrollable region i.e. below date labels and all day events */
-    private int mFirstCell;
-    /** First fully visibile hour */
-    private int mFirstHour = -1;
-    /** Distance between the mFirstCell and the top of first fully visible hour. */
-    private int mFirstHourOffset;
-    private String[] mHourStrs;
-    private String[] mDayStrs;
-    private String[] mDayStrs2Letter;
-    private boolean mIs24HourFormat;
-
-    private final ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
-    private boolean mComputeSelectedEvents;
-    private boolean mUpdateToast;
-    private Event mSelectedEvent;
-    private Event mPrevSelectedEvent;
-    private final Rect mPrevBox = new Rect();
-    protected final Resources mResources;
-    protected final Drawable mCurrentTimeLine;
-    protected final Drawable mCurrentTimeAnimateLine;
-    protected final Drawable mTodayHeaderDrawable;
-    protected final Drawable mExpandAlldayDrawable;
-    protected final Drawable mCollapseAlldayDrawable;
-    protected Drawable mAcceptedOrTentativeEventBoxDrawable;
-    private String mAmString;
-    private String mPmString;
-    private static int sCounter = 0;
-
-    ScaleGestureDetector mScaleGestureDetector;
-
-    /**
-     * The initial state of the touch mode when we enter this view.
-     */
-    private static final int TOUCH_MODE_INITIAL_STATE = 0;
-
-    /**
-     * Indicates we just received the touch event and we are waiting to see if
-     * it is a tap or a scroll gesture.
-     */
-    private static final int TOUCH_MODE_DOWN = 1;
-
-    /**
-     * Indicates the touch gesture is a vertical scroll
-     */
-    private static final int TOUCH_MODE_VSCROLL = 0x20;
-
-    /**
-     * Indicates the touch gesture is a horizontal scroll
-     */
-    private static final int TOUCH_MODE_HSCROLL = 0x40;
-
-    private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
-    /**
-     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
-     */
-    private static final int SELECTION_HIDDEN = 0;
-    private static final int SELECTION_PRESSED = 1; // D-pad down but not up yet
-    private static final int SELECTION_SELECTED = 2;
-    private static final int SELECTION_LONGPRESS = 3;
-
-    private int mSelectionMode = SELECTION_HIDDEN;
-
-    private boolean mScrolling = false;
-
-    // Pixels scrolled
-    private float mInitialScrollX;
-    private float mInitialScrollY;
-
-    private boolean mAnimateToday = false;
-    private int mAnimateTodayAlpha = 0;
-
-    // Animates the height of the allday region
-    ObjectAnimator mAlldayAnimator;
-    // Animates the height of events in the allday region
-    ObjectAnimator mAlldayEventAnimator;
-    // Animates the transparency of the more events text
-    ObjectAnimator mMoreAlldayEventsAnimator;
-    // Animates the current time marker when Today is pressed
-    ObjectAnimator mTodayAnimator;
-    // whether or not an event is stopping because it was cancelled
-    private boolean mCancellingAnimations = false;
-    // tracks whether a touch originated in the allday area
-    private boolean mTouchStartedInAlldayArea = false;
-
-    private final CalendarController mController;
-    private final ViewSwitcher mViewSwitcher;
-    private final GestureDetector mGestureDetector;
-    private final OverScroller mScroller;
-    private final EdgeEffect mEdgeEffectTop;
-    private final EdgeEffect mEdgeEffectBottom;
-    private boolean mCallEdgeEffectOnAbsorb;
-    private final int OVERFLING_DISTANCE;
-    private float mLastVelocity;
-
-    private final ScrollInterpolator mHScrollInterpolator;
-    private AccessibilityManager mAccessibilityMgr = null;
-    private boolean mIsAccessibilityEnabled = false;
-    private boolean mTouchExplorationEnabled = false;
-    private final String mNewEventHintString;
-
-    public DayView(Context context, CalendarController controller,
-            ViewSwitcher viewSwitcher, EventLoader eventLoader, int numDays) {
-        super(context);
-        mContext = context;
-        initAccessibilityVariables();
-
-        mResources = context.getResources();
-        mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint);
-        mNumDays = numDays;
-
-        DATE_HEADER_FONT_SIZE = (int) mResources.getDimension(R.dimen.date_header_text_size);
-        DAY_HEADER_FONT_SIZE = (int) mResources.getDimension(R.dimen.day_label_text_size);
-        ONE_DAY_HEADER_HEIGHT = (int) mResources.getDimension(R.dimen.one_day_header_height);
-        DAY_HEADER_BOTTOM_MARGIN = (int) mResources.getDimension(R.dimen.day_header_bottom_margin);
-        EXPAND_ALL_DAY_BOTTOM_MARGIN = (int) mResources.getDimension(R.dimen.all_day_bottom_margin);
-        HOURS_TEXT_SIZE = (int) mResources.getDimension(R.dimen.hours_text_size);
-        AMPM_TEXT_SIZE = (int) mResources.getDimension(R.dimen.ampm_text_size);
-        MIN_HOURS_WIDTH = (int) mResources.getDimension(R.dimen.min_hours_width);
-        HOURS_LEFT_MARGIN = (int) mResources.getDimension(R.dimen.hours_left_margin);
-        HOURS_RIGHT_MARGIN = (int) mResources.getDimension(R.dimen.hours_right_margin);
-        MULTI_DAY_HEADER_HEIGHT = (int) mResources.getDimension(R.dimen.day_header_height);
-        int eventTextSizeId;
-        if (mNumDays == 1) {
-            eventTextSizeId = R.dimen.day_view_event_text_size;
-        } else {
-            eventTextSizeId = R.dimen.week_view_event_text_size;
-        }
-        EVENT_TEXT_FONT_SIZE = (int) mResources.getDimension(eventTextSizeId);
-        NEW_EVENT_HINT_FONT_SIZE = (int) mResources.getDimension(R.dimen.new_event_hint_text_size);
-        MIN_EVENT_HEIGHT = mResources.getDimension(R.dimen.event_min_height);
-        MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = MIN_EVENT_HEIGHT;
-        EVENT_TEXT_TOP_MARGIN = (int) mResources.getDimension(R.dimen.event_text_vertical_margin);
-        EVENT_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN;
-        EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN;
-        EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN;
-
-        EVENT_TEXT_LEFT_MARGIN = (int) mResources
-                .getDimension(R.dimen.event_text_horizontal_margin);
-        EVENT_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
-        EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
-        EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
-
-        if (mScale == 0) {
-
-            mScale = mResources.getDisplayMetrics().density;
-            if (mScale != 1) {
-                SINGLE_ALLDAY_HEIGHT *= mScale;
-                ALLDAY_TOP_MARGIN *= mScale;
-                MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale;
-
-                NORMAL_FONT_SIZE *= mScale;
-                GRID_LINE_LEFT_MARGIN *= mScale;
-                HOURS_TOP_MARGIN *= mScale;
-                MIN_CELL_WIDTH_FOR_TEXT *= mScale;
-                MAX_UNEXPANDED_ALLDAY_HEIGHT *= mScale;
-                mAnimateDayEventHeight = (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-
-                CURRENT_TIME_LINE_SIDE_BUFFER *= mScale;
-                CURRENT_TIME_LINE_TOP_OFFSET *= mScale;
-
-                MIN_Y_SPAN *= mScale;
-                MAX_CELL_HEIGHT *= mScale;
-                DEFAULT_CELL_HEIGHT *= mScale;
-                DAY_HEADER_HEIGHT *= mScale;
-                DAY_HEADER_RIGHT_MARGIN *= mScale;
-                DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale;
-                DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale;
-                DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale;
-                CALENDAR_COLOR_SQUARE_SIZE *= mScale;
-                EVENT_RECT_TOP_MARGIN *= mScale;
-                EVENT_RECT_BOTTOM_MARGIN *= mScale;
-                ALL_DAY_EVENT_RECT_BOTTOM_MARGIN *= mScale;
-                EVENT_RECT_LEFT_MARGIN *= mScale;
-                EVENT_RECT_RIGHT_MARGIN *= mScale;
-                EVENT_RECT_STROKE_WIDTH *= mScale;
-                EVENT_SQUARE_WIDTH *= mScale;
-                EVENT_LINE_PADDING *= mScale;
-                NEW_EVENT_MARGIN *= mScale;
-                NEW_EVENT_WIDTH *= mScale;
-                NEW_EVENT_MAX_LENGTH *= mScale;
-            }
-        }
-        HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
-        DAY_HEADER_HEIGHT = mNumDays == 1 ? ONE_DAY_HEADER_HEIGHT : MULTI_DAY_HEADER_HEIGHT;
-
-        mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_indicator_holo_light);
-        mCurrentTimeAnimateLine = mResources
-                .getDrawable(R.drawable.timeline_indicator_activated_holo_light);
-        mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light);
-        mExpandAlldayDrawable = mResources.getDrawable(R.drawable.ic_expand_holo_light);
-        mCollapseAlldayDrawable = mResources.getDrawable(R.drawable.ic_collapse_holo_light);
-        mNewEventHintColor =  mResources.getColor(R.color.new_event_hint_text_color);
-        mAcceptedOrTentativeEventBoxDrawable = mResources
-                .getDrawable(R.drawable.panel_month_event_holo_light);
-
-        mEventLoader = eventLoader;
-        mEventGeometry = new EventGeometry();
-        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
-        mEventGeometry.setHourGap(HOUR_GAP);
-        mEventGeometry.setCellMargin(DAY_GAP);
-        mLastPopupEventID = INVALID_EVENT_ID;
-        mController = controller;
-        mViewSwitcher = viewSwitcher;
-        mGestureDetector = new GestureDetector(context, new CalendarGestureListener());
-        mScaleGestureDetector = new ScaleGestureDetector(getContext(), this);
-        if (mCellHeight == 0) {
-            mCellHeight = Utils.getSharedPreference(mContext,
-                    GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT);
-        }
-        mScroller = new OverScroller(context);
-        mHScrollInterpolator = new ScrollInterpolator();
-        mEdgeEffectTop = new EdgeEffect(context);
-        mEdgeEffectBottom = new EdgeEffect(context);
-        ViewConfiguration vc = ViewConfiguration.get(context);
-        mScaledPagingTouchSlop = vc.getScaledPagingTouchSlop();
-        mOnDownDelay = ViewConfiguration.getTapTimeout();
-        OVERFLING_DISTANCE = vc.getScaledOverflingDistance();
-
-        init(context);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        if (mHandler == null) {
-            mHandler = getHandler();
-            mHandler.post(mUpdateCurrentTime);
-        }
-    }
-
-    private void init(Context context) {
-        setFocusable(true);
-
-        // Allow focus in touch mode so that we can do keyboard shortcuts
-        // even after we've entered touch mode.
-        setFocusableInTouchMode(true);
-        setClickable(true);
-        setOnCreateContextMenuListener(this);
-
-        mFirstDayOfWeek = Utils.getFirstDayOfWeek(context);
-
-        mCurrentTime = new Time(Utils.getTimeZone(context, mTZUpdater));
-        long currentTime = System.currentTimeMillis();
-        mCurrentTime.set(currentTime);
-        mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
-
-        mWeek_saturdayColor = mResources.getColor(R.color.week_saturday);
-        mWeek_sundayColor = mResources.getColor(R.color.week_sunday);
-        mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color);
-        mFutureBgColorRes = mResources.getColor(R.color.calendar_future_bg_color);
-        mBgColor = mResources.getColor(R.color.calendar_hour_background);
-        mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label);
-        mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected);
-        mCalendarGridLineInnerHorizontalColor = mResources
-                .getColor(R.color.calendar_grid_line_inner_horizontal_color);
-        mCalendarGridLineInnerVerticalColor = mResources
-                .getColor(R.color.calendar_grid_line_inner_vertical_color);
-        mCalendarHourLabelColor = mResources.getColor(R.color.calendar_hour_label);
-        mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
-        mMoreEventsTextColor = mResources.getColor(R.color.month_event_other_color);
-
-        mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
-        mEventTextPaint.setTextAlign(Paint.Align.LEFT);
-        mEventTextPaint.setAntiAlias(true);
-
-        int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
-        Paint p = mSelectionPaint;
-        p.setColor(gridLineColor);
-        p.setStyle(Style.FILL);
-        p.setAntiAlias(false);
-
-        p = mPaint;
-        p.setAntiAlias(true);
-
-        // Allocate space for 2 weeks worth of weekday names so that we can
-        // easily start the week display at any week day.
-        mDayStrs = new String[14];
-
-        // Also create an array of 2-letter abbreviations.
-        mDayStrs2Letter = new String[14];
-
-        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
-            int index = i - Calendar.SUNDAY;
-            // e.g. Tue for Tuesday
-            mDayStrs[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM)
-                    .toUpperCase();
-            mDayStrs[index + 7] = mDayStrs[index];
-            // e.g. Tu for Tuesday
-            mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT)
-                    .toUpperCase();
-
-            // If we don't have 2-letter day strings, fall back to 1-letter.
-            if (mDayStrs2Letter[index].equals(mDayStrs[index])) {
-                mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORTEST);
-            }
-
-            mDayStrs2Letter[index + 7] = mDayStrs2Letter[index];
-        }
-
-        // Figure out how much space we need for the 3-letter abbrev names
-        // in the worst case.
-        p.setTextSize(DATE_HEADER_FONT_SIZE);
-        p.setTypeface(mBold);
-        String[] dateStrs = {" 28", " 30"};
-        mDateStrWidth = computeMaxStringWidth(0, dateStrs, p);
-        p.setTextSize(DAY_HEADER_FONT_SIZE);
-        mDateStrWidth += computeMaxStringWidth(0, mDayStrs, p);
-
-        p.setTextSize(HOURS_TEXT_SIZE);
-        p.setTypeface(null);
-        handleOnResume();
-
-        mAmString = DateUtils.getAMPMString(Calendar.AM).toUpperCase();
-        mPmString = DateUtils.getAMPMString(Calendar.PM).toUpperCase();
-        String[] ampm = {mAmString, mPmString};
-        p.setTextSize(AMPM_TEXT_SIZE);
-        mHoursWidth = Math.max(HOURS_MARGIN, computeMaxStringWidth(mHoursWidth, ampm, p)
-                + HOURS_RIGHT_MARGIN);
-        mHoursWidth = Math.max(MIN_HOURS_WIDTH, mHoursWidth);
-
-        LayoutInflater inflater;
-        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mPopupView = inflater.inflate(R.layout.bubble_event, null);
-        mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT));
-        mPopup = new PopupWindow(context);
-        mPopup.setContentView(mPopupView);
-        Resources.Theme dialogTheme = getResources().newTheme();
-        dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
-        TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
-            android.R.attr.windowBackground });
-        mPopup.setBackgroundDrawable(ta.getDrawable(0));
-        ta.recycle();
-
-        // Enable touching the popup window
-        mPopupView.setOnClickListener(this);
-        // Catch long clicks for creating a new event
-        setOnLongClickListener(this);
-
-        mBaseDate = new Time(Utils.getTimeZone(context, mTZUpdater));
-        long millis = System.currentTimeMillis();
-        mBaseDate.set(millis);
-
-        mEarliestStartHour = new int[mNumDays];
-        mHasAllDayEvent = new boolean[mNumDays];
-
-        // mLines is the array of points used with Canvas.drawLines() in
-        // drawGridBackground() and drawAllDayEvents().  Its size depends
-        // on the max number of lines that can ever be drawn by any single
-        // drawLines() call in either of those methods.
-        final int maxGridLines = (24 + 1)  // max horizontal lines we might draw
-                + (mNumDays + 1); // max vertical lines we might draw
-        mLines = new float[maxGridLines * 4];
-    }
-
-    /**
-     * This is called when the popup window is pressed.
-     */
-    public void onClick(View v) {
-        if (v == mPopupView) {
-            // Pretend it was a trackball click because that will always
-            // jump to the "View event" screen.
-            switchViews(true /* trackball */);
-        }
-    }
-
-    public void handleOnResume() {
-        initAccessibilityVariables();
-        if(Utils.getSharedPreference(mContext, OtherPreferences.KEY_OTHER_1, false)) {
-            mFutureBgColor = 0;
-        } else {
-            mFutureBgColor = mFutureBgColorRes;
-        }
-        mIs24HourFormat = DateFormat.is24HourFormat(mContext);
-        mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
-        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
-        mLastSelectionDayForAccessibility = 0;
-        mLastSelectionHourForAccessibility = 0;
-        mLastSelectedEventForAccessibility = null;
-        mSelectionMode = SELECTION_HIDDEN;
-    }
-
-    private void initAccessibilityVariables() {
-        mAccessibilityMgr = (AccessibilityManager) mContext
-                .getSystemService(Service.ACCESSIBILITY_SERVICE);
-        mIsAccessibilityEnabled = mAccessibilityMgr != null && mAccessibilityMgr.isEnabled();
-        mTouchExplorationEnabled = isTouchExplorationEnabled();
-    }
-
-    /**
-     * Returns the start of the selected time in milliseconds since the epoch.
-     *
-     * @return selected time in UTC milliseconds since the epoch.
-     */
-    long getSelectedTimeInMillis() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        return time.normalize(true /* ignore isDst */);
-    }
-
-    Time getSelectedTime() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        time.normalize(true /* ignore isDst */);
-        return time;
-    }
-
-    Time getSelectedTimeForAccessibility() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDayForAccessibility);
-        time.hour = mSelectionHourForAccessibility;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        time.normalize(true /* ignore isDst */);
-        return time;
-    }
-
-    /**
-     * Returns the start of the selected time in minutes since midnight,
-     * local time.  The derived class must ensure that this is consistent
-     * with the return value from getSelectedTimeInMillis().
-     */
-    int getSelectedMinutesSinceMidnight() {
-        return mSelectionHour * MINUTES_PER_HOUR;
-    }
-
-    int getFirstVisibleHour() {
-        return mFirstHour;
-    }
-
-    void setFirstVisibleHour(int firstHour) {
-        mFirstHour = firstHour;
-        mFirstHourOffset = 0;
-    }
-
-    public void setSelected(Time time, boolean ignoreTime, boolean animateToday) {
-        mBaseDate.set(time);
-        setSelectedHour(mBaseDate.hour);
-        setSelectedEvent(null);
-        mPrevSelectedEvent = null;
-        long millis = mBaseDate.toMillis(false /* use isDst */);
-        setSelectedDay(Time.getJulianDay(millis, mBaseDate.gmtoff));
-        mSelectedEvents.clear();
-        mComputeSelectedEvents = true;
-
-        int gotoY = Integer.MIN_VALUE;
-
-        if (!ignoreTime && mGridAreaHeight != -1) {
-            int lastHour = 0;
-
-            if (mBaseDate.hour < mFirstHour) {
-                // Above visible region
-                gotoY = mBaseDate.hour * (mCellHeight + HOUR_GAP);
-            } else {
-                lastHour = (mGridAreaHeight - mFirstHourOffset) / (mCellHeight + HOUR_GAP)
-                        + mFirstHour;
-
-                if (mBaseDate.hour >= lastHour) {
-                    // Below visible region
-
-                    // target hour + 1 (to give it room to see the event) -
-                    // grid height (to get the y of the top of the visible
-                    // region)
-                    gotoY = (int) ((mBaseDate.hour + 1 + mBaseDate.minute / 60.0f)
-                            * (mCellHeight + HOUR_GAP) - mGridAreaHeight);
-                }
-            }
-
-            if (DEBUG) {
-                Log.e(TAG, "Go " + gotoY + " 1st " + mFirstHour + ":" + mFirstHourOffset + "CH "
-                        + (mCellHeight + HOUR_GAP) + " lh " + lastHour + " gh " + mGridAreaHeight
-                        + " ymax " + mMaxViewStartY);
-            }
-
-            if (gotoY > mMaxViewStartY) {
-                gotoY = mMaxViewStartY;
-            } else if (gotoY < 0 && gotoY != Integer.MIN_VALUE) {
-                gotoY = 0;
-            }
-        }
-
-        recalc();
-
-        mRemeasure = true;
-        invalidate();
-
-        boolean delayAnimateToday = false;
-        if (gotoY != Integer.MIN_VALUE) {
-            ValueAnimator scrollAnim = ObjectAnimator.ofInt(this, "viewStartY", mViewStartY, gotoY);
-            scrollAnim.setDuration(GOTO_SCROLL_DURATION);
-            scrollAnim.setInterpolator(new AccelerateDecelerateInterpolator());
-            scrollAnim.addListener(mAnimatorListener);
-            scrollAnim.start();
-            delayAnimateToday = true;
-        }
-        if (animateToday) {
-            synchronized (mTodayAnimatorListener) {
-                if (mTodayAnimator != null) {
-                    mTodayAnimator.removeAllListeners();
-                    mTodayAnimator.cancel();
-                }
-                mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
-                        mAnimateTodayAlpha, 255);
-                mAnimateToday = true;
-                mTodayAnimatorListener.setFadingIn(true);
-                mTodayAnimatorListener.setAnimator(mTodayAnimator);
-                mTodayAnimator.addListener(mTodayAnimatorListener);
-                mTodayAnimator.setDuration(150);
-                if (delayAnimateToday) {
-                    mTodayAnimator.setStartDelay(GOTO_SCROLL_DURATION);
-                }
-                mTodayAnimator.start();
-            }
-        }
-        sendAccessibilityEventAsNeeded(false);
-    }
-
-    // Called from animation framework via reflection. Do not remove
-    public void setViewStartY(int viewStartY) {
-        if (viewStartY > mMaxViewStartY) {
-            viewStartY = mMaxViewStartY;
-        }
-
-        mViewStartY = viewStartY;
-
-        computeFirstHour();
-        invalidate();
-    }
-
-    public void setAnimateTodayAlpha(int todayAlpha) {
-        mAnimateTodayAlpha = todayAlpha;
-        invalidate();
-    }
-
-    public Time getSelectedDay() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        time.normalize(true /* ignore isDst */);
-        return time;
-    }
-
-    public void updateTitle() {
-        Time start = new Time(mBaseDate);
-        start.normalize(true);
-        Time end = new Time(start);
-        end.monthDay += mNumDays - 1;
-        // Move it forward one minute so the formatter doesn't lose a day
-        end.minute += 1;
-        end.normalize(true);
-
-        long formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
-        if (mNumDays != 1) {
-            // Don't show day of the month if for multi-day view
-            formatFlags |= DateUtils.FORMAT_NO_MONTH_DAY;
-
-            // Abbreviate the month if showing multiple months
-            if (start.month != end.month) {
-                formatFlags |= DateUtils.FORMAT_ABBREV_MONTH;
-            }
-        }
-
-        mController.sendEvent(this, EventType.UPDATE_TITLE, start, end, null, -1, ViewType.CURRENT,
-                formatFlags, null, null);
-    }
-
-    /**
-     * return a negative number if "time" is comes before the visible time
-     * range, a positive number if "time" is after the visible time range, and 0
-     * if it is in the visible time range.
-     */
-    public int compareToVisibleTimeRange(Time time) {
-
-        int savedHour = mBaseDate.hour;
-        int savedMinute = mBaseDate.minute;
-        int savedSec = mBaseDate.second;
-
-        mBaseDate.hour = 0;
-        mBaseDate.minute = 0;
-        mBaseDate.second = 0;
-
-        if (DEBUG) {
-            Log.d(TAG, "Begin " + mBaseDate.toString());
-            Log.d(TAG, "Diff  " + time.toString());
-        }
-
-        // Compare beginning of range
-        int diff = Time.compare(time, mBaseDate);
-        if (diff > 0) {
-            // Compare end of range
-            mBaseDate.monthDay += mNumDays;
-            mBaseDate.normalize(true);
-            diff = Time.compare(time, mBaseDate);
-
-            if (DEBUG) Log.d(TAG, "End   " + mBaseDate.toString());
-
-            mBaseDate.monthDay -= mNumDays;
-            mBaseDate.normalize(true);
-            if (diff < 0) {
-                // in visible time
-                diff = 0;
-            } else if (diff == 0) {
-                // Midnight of following day
-                diff = 1;
-            }
-        }
-
-        if (DEBUG) Log.d(TAG, "Diff: " + diff);
-
-        mBaseDate.hour = savedHour;
-        mBaseDate.minute = savedMinute;
-        mBaseDate.second = savedSec;
-        return diff;
-    }
-
-    private void recalc() {
-        // Set the base date to the beginning of the week if we are displaying
-        // 7 days at a time.
-        if (mNumDays == 7) {
-            adjustToBeginningOfWeek(mBaseDate);
-        }
-
-        final long start = mBaseDate.toMillis(false /* use isDst */);
-        mFirstJulianDay = Time.getJulianDay(start, mBaseDate.gmtoff);
-        mLastJulianDay = mFirstJulianDay + mNumDays - 1;
-
-        mMonthLength = mBaseDate.getActualMaximum(Time.MONTH_DAY);
-        mFirstVisibleDate = mBaseDate.monthDay;
-        mFirstVisibleDayOfWeek = mBaseDate.weekDay;
-    }
-
-    private void adjustToBeginningOfWeek(Time time) {
-        int dayOfWeek = time.weekDay;
-        int diff = dayOfWeek - mFirstDayOfWeek;
-        if (diff != 0) {
-            if (diff < 0) {
-                diff += 7;
-            }
-            time.monthDay -= diff;
-            time.normalize(true /* ignore isDst */);
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
-        mViewWidth = width;
-        mViewHeight = height;
-        mEdgeEffectTop.setSize(mViewWidth, mViewHeight);
-        mEdgeEffectBottom.setSize(mViewWidth, mViewHeight);
-        int gridAreaWidth = width - mHoursWidth;
-        mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
-
-        // This would be about 1 day worth in a 7 day view
-        mHorizontalSnapBackThreshold = width / 7;
-
-        Paint p = new Paint();
-        p.setTextSize(HOURS_TEXT_SIZE);
-        mHoursTextHeight = (int) Math.abs(p.ascent());
-        remeasure(width, height);
-    }
-
-    /**
-     * Measures the space needed for various parts of the view after
-     * loading new events.  This can change if there are all-day events.
-     */
-    private void remeasure(int width, int height) {
-        // Shrink to fit available space but make sure we can display at least two events
-        MAX_UNEXPANDED_ALLDAY_HEIGHT = (int) (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4);
-        MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.min(MAX_UNEXPANDED_ALLDAY_HEIGHT, height / 6);
-        MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.max(MAX_UNEXPANDED_ALLDAY_HEIGHT,
-                (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 2);
-        mMaxUnexpandedAlldayEventCount =
-                (int) (MAX_UNEXPANDED_ALLDAY_HEIGHT / MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
-
-        // First, clear the array of earliest start times, and the array
-        // indicating presence of an all-day event.
-        for (int day = 0; day < mNumDays; day++) {
-            mEarliestStartHour[day] = 25;  // some big number
-            mHasAllDayEvent[day] = false;
-        }
-
-        int maxAllDayEvents = mMaxAlldayEvents;
-
-        // The min is where 24 hours cover the entire visible area
-        mMinCellHeight = Math.max((height - DAY_HEADER_HEIGHT) / 24, (int) MIN_EVENT_HEIGHT);
-        if (mCellHeight < mMinCellHeight) {
-            mCellHeight = mMinCellHeight;
-        }
-
-        // Calculate mAllDayHeight
-        mFirstCell = DAY_HEADER_HEIGHT;
-        int allDayHeight = 0;
-        if (maxAllDayEvents > 0) {
-            int maxAllAllDayHeight = height - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
-            // If there is at most one all-day event per day, then use less
-            // space (but more than the space for a single event).
-            if (maxAllDayEvents == 1) {
-                allDayHeight = SINGLE_ALLDAY_HEIGHT;
-            } else if (maxAllDayEvents <= mMaxUnexpandedAlldayEventCount){
-                // Allow the all-day area to grow in height depending on the
-                // number of all-day events we need to show, up to a limit.
-                allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
-                if (allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
-                    allDayHeight = MAX_UNEXPANDED_ALLDAY_HEIGHT;
-                }
-            } else {
-                // if we have more than the magic number, check if we're animating
-                // and if not adjust the sizes appropriately
-                if (mAnimateDayHeight != 0) {
-                    // Don't shrink the space past the final allDay space. The animation
-                    // continues to hide the last event so the more events text can
-                    // fade in.
-                    allDayHeight = Math.max(mAnimateDayHeight, MAX_UNEXPANDED_ALLDAY_HEIGHT);
-                } else {
-                    // Try to fit all the events in
-                    allDayHeight = (int) (maxAllDayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
-                    // But clip the area depending on which mode we're in
-                    if (!mShowAllAllDayEvents && allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
-                        allDayHeight = (int) (mMaxUnexpandedAlldayEventCount *
-                                MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT);
-                    } else if (allDayHeight > maxAllAllDayHeight) {
-                        allDayHeight = maxAllAllDayHeight;
-                    }
-                }
-            }
-            mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN;
-        } else {
-            mSelectionAllday = false;
-        }
-        mAlldayHeight = allDayHeight;
-
-        mGridAreaHeight = height - mFirstCell;
-
-        // Set up the expand icon position
-        int allDayIconWidth = mExpandAlldayDrawable.getIntrinsicWidth();
-        mExpandAllDayRect.left = Math.max((mHoursWidth - allDayIconWidth) / 2,
-                EVENT_ALL_DAY_TEXT_LEFT_MARGIN);
-        mExpandAllDayRect.right = Math.min(mExpandAllDayRect.left + allDayIconWidth, mHoursWidth
-                - EVENT_ALL_DAY_TEXT_RIGHT_MARGIN);
-        mExpandAllDayRect.bottom = mFirstCell - EXPAND_ALL_DAY_BOTTOM_MARGIN;
-        mExpandAllDayRect.top = mExpandAllDayRect.bottom
-                - mExpandAlldayDrawable.getIntrinsicHeight();
-
-        mNumHours = mGridAreaHeight / (mCellHeight + HOUR_GAP);
-        mEventGeometry.setHourHeight(mCellHeight);
-
-        final long minimumDurationMillis = (long)
-                (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f));
-        Event.computePositions(mEvents, minimumDurationMillis);
-
-        // Compute the top of our reachable view
-        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
-        if (DEBUG) {
-            Log.e(TAG, "mViewStartY: " + mViewStartY);
-            Log.e(TAG, "mMaxViewStartY: " + mMaxViewStartY);
-        }
-        if (mViewStartY > mMaxViewStartY) {
-            mViewStartY = mMaxViewStartY;
-            computeFirstHour();
-        }
-
-        if (mFirstHour == -1) {
-            initFirstHour();
-            mFirstHourOffset = 0;
-        }
-
-        // When we change the base date, the number of all-day events may
-        // change and that changes the cell height.  When we switch dates,
-        // we use the mFirstHourOffset from the previous view, but that may
-        // be too large for the new view if the cell height is smaller.
-        if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
-            mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
-        }
-        mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
-
-        final int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
-        //When we get new events we don't want to dismiss the popup unless the event changes
-        if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent.id) {
-            mPopup.dismiss();
-        }
-        mPopup.setWidth(eventAreaWidth - 20);
-        mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
-    }
-
-    /**
-     * Initialize the state for another view.  The given view is one that has
-     * its own bitmap and will use an animation to replace the current view.
-     * The current view and new view are either both Week views or both Day
-     * views.  They differ in their base date.
-     *
-     * @param view the view to initialize.
-     */
-    private void initView(DayView view) {
-        view.setSelectedHour(mSelectionHour);
-        view.mSelectedEvents.clear();
-        view.mComputeSelectedEvents = true;
-        view.mFirstHour = mFirstHour;
-        view.mFirstHourOffset = mFirstHourOffset;
-        view.remeasure(getWidth(), getHeight());
-        view.initAllDayHeights();
-
-        view.setSelectedEvent(null);
-        view.mPrevSelectedEvent = null;
-        view.mFirstDayOfWeek = mFirstDayOfWeek;
-        if (view.mEvents.size() > 0) {
-            view.mSelectionAllday = mSelectionAllday;
-        } else {
-            view.mSelectionAllday = false;
-        }
-
-        // Redraw the screen so that the selection box will be redrawn.  We may
-        // have scrolled to a different part of the day in some other view
-        // so the selection box in this view may no longer be visible.
-        view.recalc();
-    }
-
-    /**
-     * Switch to another view based on what was selected (an event or a free
-     * slot) and how it was selected (by touch or by trackball).
-     *
-     * @param trackBallSelection true if the selection was made using the
-     * trackball.
-     */
-    private void switchViews(boolean trackBallSelection) {
-        Event selectedEvent = mSelectedEvent;
-
-        mPopup.dismiss();
-        mLastPopupEventID = INVALID_EVENT_ID;
-        if (mNumDays > 1) {
-            // This is the Week view.
-            // With touch, we always switch to Day/Agenda View
-            // With track ball, if we selected a free slot, then create an event.
-            // If we selected a specific event, switch to EventInfo view.
-            if (trackBallSelection) {
-                if (selectedEvent != null) {
-                    if (mIsAccessibilityEnabled) {
-                        mAccessibilityMgr.interrupt();
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        mScrolling = false;
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        return super.onKeyDown(keyCode, event);
-    }
-
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        return true;
-    }
-
-    private boolean isTouchExplorationEnabled() {
-        return mIsAccessibilityEnabled && mAccessibilityMgr.isTouchExplorationEnabled();
-    }
-
-    private void sendAccessibilityEventAsNeeded(boolean speakEvents) {
-        if (!mIsAccessibilityEnabled) {
-            return;
-        }
-        boolean dayChanged = mLastSelectionDayForAccessibility != mSelectionDayForAccessibility;
-        boolean hourChanged = mLastSelectionHourForAccessibility != mSelectionHourForAccessibility;
-        if (dayChanged || hourChanged ||
-                mLastSelectedEventForAccessibility != mSelectedEventForAccessibility) {
-            mLastSelectionDayForAccessibility = mSelectionDayForAccessibility;
-            mLastSelectionHourForAccessibility = mSelectionHourForAccessibility;
-            mLastSelectedEventForAccessibility = mSelectedEventForAccessibility;
-
-            StringBuilder b = new StringBuilder();
-
-            // Announce only the changes i.e. day or hour or both
-            if (dayChanged) {
-                b.append(getSelectedTimeForAccessibility().format("%A "));
-            }
-            if (hourChanged) {
-                b.append(getSelectedTimeForAccessibility().format(mIs24HourFormat ? "%k" : "%l%p"));
-            }
-            if (dayChanged || hourChanged) {
-                b.append(PERIOD_SPACE);
-            }
-
-            if (speakEvents) {
-                if (mEventCountTemplate == null) {
-                    mEventCountTemplate = mContext.getString(R.string.template_announce_item_index);
-                }
-
-                // Read out the relevant event(s)
-                int numEvents = mSelectedEvents.size();
-                if (numEvents > 0) {
-                    if (mSelectedEventForAccessibility == null) {
-                        // Read out all the events
-                        int i = 1;
-                        for (Event calEvent : mSelectedEvents) {
-                            if (numEvents > 1) {
-                                // Read out x of numEvents if there are more than one event
-                                mStringBuilder.setLength(0);
-                                b.append(mFormatter.format(mEventCountTemplate, i++, numEvents));
-                                b.append(" ");
-                            }
-                            appendEventAccessibilityString(b, calEvent);
-                        }
-                    } else {
-                        if (numEvents > 1) {
-                            // Read out x of numEvents if there are more than one event
-                            mStringBuilder.setLength(0);
-                            b.append(mFormatter.format(mEventCountTemplate, mSelectedEvents
-                                    .indexOf(mSelectedEventForAccessibility) + 1, numEvents));
-                            b.append(" ");
-                        }
-                        appendEventAccessibilityString(b, mSelectedEventForAccessibility);
-                    }
-                }
-            }
-
-            if (dayChanged || hourChanged || speakEvents) {
-                AccessibilityEvent event = AccessibilityEvent
-                        .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-                CharSequence msg = b.toString();
-                event.getText().add(msg);
-                event.setAddedCount(msg.length());
-                sendAccessibilityEventUnchecked(event);
-            }
-        }
-    }
-
-    /**
-     * @param b
-     * @param calEvent
-     */
-    private void appendEventAccessibilityString(StringBuilder b, Event calEvent) {
-        b.append(calEvent.getTitleAndLocation());
-        b.append(PERIOD_SPACE);
-        String when;
-        int flags = DateUtils.FORMAT_SHOW_DATE;
-        if (calEvent.allDay) {
-            flags |= DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY;
-        } else {
-            flags |= DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(mContext)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-        }
-        when = Utils.formatDateRange(mContext, calEvent.startMillis, calEvent.endMillis, flags);
-        b.append(when);
-        b.append(PERIOD_SPACE);
-    }
-
-    private class GotoBroadcaster implements Animation.AnimationListener {
-        private final int mCounter;
-        private final Time mStart;
-        private final Time mEnd;
-
-        public GotoBroadcaster(Time start, Time end) {
-            mCounter = ++sCounter;
-            mStart = start;
-            mEnd = end;
-        }
-
-        @Override
-        public void onAnimationEnd(Animation animation) {
-            DayView view = (DayView) mViewSwitcher.getCurrentView();
-            view.mViewStartX = 0;
-            view = (DayView) mViewSwitcher.getNextView();
-            view.mViewStartX = 0;
-
-            if (mCounter == sCounter) {
-                mController.sendEvent(this, EventType.GO_TO, mStart, mEnd, null, -1,
-                        ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
-            }
-        }
-
-        @Override
-        public void onAnimationRepeat(Animation animation) {
-        }
-
-        @Override
-        public void onAnimationStart(Animation animation) {
-        }
-    }
-
-    private View switchViews(boolean forward, float xOffSet, float width, float velocity) {
-        mAnimationDistance = width - xOffSet;
-        if (DEBUG) {
-            Log.d(TAG, "switchViews(" + forward + ") O:" + xOffSet + " Dist:" + mAnimationDistance);
-        }
-
-        float progress = Math.abs(xOffSet) / width;
-        if (progress > 1.0f) {
-            progress = 1.0f;
-        }
-
-        float inFromXValue, inToXValue;
-        float outFromXValue, outToXValue;
-        if (forward) {
-            inFromXValue = 1.0f - progress;
-            inToXValue = 0.0f;
-            outFromXValue = -progress;
-            outToXValue = -1.0f;
-        } else {
-            inFromXValue = progress - 1.0f;
-            inToXValue = 0.0f;
-            outFromXValue = progress;
-            outToXValue = 1.0f;
-        }
-
-        final Time start = new Time(mBaseDate.timezone);
-        start.set(mController.getTime());
-        if (forward) {
-            start.monthDay += mNumDays;
-        } else {
-            start.monthDay -= mNumDays;
-        }
-        mController.setTime(start.normalize(true));
-
-        Time newSelected = start;
-
-        if (mNumDays == 7) {
-            newSelected = new Time(start);
-            adjustToBeginningOfWeek(start);
-        }
-
-        final Time end = new Time(start);
-        end.monthDay += mNumDays - 1;
-
-        // We have to allocate these animation objects each time we switch views
-        // because that is the only way to set the animation parameters.
-        TranslateAnimation inAnimation = new TranslateAnimation(
-                Animation.RELATIVE_TO_SELF, inFromXValue,
-                Animation.RELATIVE_TO_SELF, inToXValue,
-                Animation.ABSOLUTE, 0.0f,
-                Animation.ABSOLUTE, 0.0f);
-
-        TranslateAnimation outAnimation = new TranslateAnimation(
-                Animation.RELATIVE_TO_SELF, outFromXValue,
-                Animation.RELATIVE_TO_SELF, outToXValue,
-                Animation.ABSOLUTE, 0.0f,
-                Animation.ABSOLUTE, 0.0f);
-
-        long duration = calculateDuration(width - Math.abs(xOffSet), width, velocity);
-        inAnimation.setDuration(duration);
-        inAnimation.setInterpolator(mHScrollInterpolator);
-        outAnimation.setInterpolator(mHScrollInterpolator);
-        outAnimation.setDuration(duration);
-        outAnimation.setAnimationListener(new GotoBroadcaster(start, end));
-        mViewSwitcher.setInAnimation(inAnimation);
-        mViewSwitcher.setOutAnimation(outAnimation);
-
-        DayView view = (DayView) mViewSwitcher.getCurrentView();
-        view.cleanup();
-        mViewSwitcher.showNext();
-        view = (DayView) mViewSwitcher.getCurrentView();
-        view.setSelected(newSelected, true, false);
-        view.requestFocus();
-        view.reloadEvents();
-        view.updateTitle();
-        view.restartCurrentTimeUpdates();
-
-        return view;
-    }
-
-    // This is called after scrolling stops to move the selected hour
-    // to the visible part of the screen.
-    private void resetSelectedHour() {
-        if (mSelectionHour < mFirstHour + 1) {
-            setSelectedHour(mFirstHour + 1);
-            setSelectedEvent(null);
-            mSelectedEvents.clear();
-            mComputeSelectedEvents = true;
-        } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
-            setSelectedHour(mFirstHour + mNumHours - 3);
-            setSelectedEvent(null);
-            mSelectedEvents.clear();
-            mComputeSelectedEvents = true;
-        }
-    }
-
-    private void initFirstHour() {
-        mFirstHour = mSelectionHour - mNumHours / 5;
-        if (mFirstHour < 0) {
-            mFirstHour = 0;
-        } else if (mFirstHour + mNumHours > 24) {
-            mFirstHour = 24 - mNumHours;
-        }
-    }
-
-    /**
-     * Recomputes the first full hour that is visible on screen after the
-     * screen is scrolled.
-     */
-    private void computeFirstHour() {
-        // Compute the first full hour that is visible on screen
-        mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP);
-        mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY;
-    }
-
-    private void adjustHourSelection() {
-        if (mSelectionHour < 0) {
-            setSelectedHour(0);
-            if (mMaxAlldayEvents > 0) {
-                mPrevSelectedEvent = null;
-                mSelectionAllday = true;
-            }
-        }
-
-        if (mSelectionHour > 23) {
-            setSelectedHour(23);
-        }
-
-        // If the selected hour is at least 2 time slots from the top and
-        // bottom of the screen, then don't scroll the view.
-        if (mSelectionHour < mFirstHour + 1) {
-            // If there are all-days events for the selected day but there
-            // are no more normal events earlier in the day, then jump to
-            // the all-day event area.
-            // Exception 1: allow the user to scroll to 8am with the trackball
-            // before jumping to the all-day event area.
-            // Exception 2: if 12am is on screen, then allow the user to select
-            // 12am before going up to the all-day event area.
-            int daynum = mSelectionDay - mFirstJulianDay;
-            if (daynum < mEarliestStartHour.length && daynum >= 0
-                    && mMaxAlldayEvents > 0
-                    && mEarliestStartHour[daynum] > mSelectionHour
-                    && mFirstHour > 0 && mFirstHour < 8) {
-                mPrevSelectedEvent = null;
-                mSelectionAllday = true;
-                setSelectedHour(mFirstHour + 1);
-                return;
-            }
-
-            if (mFirstHour > 0) {
-                mFirstHour -= 1;
-                mViewStartY -= (mCellHeight + HOUR_GAP);
-                if (mViewStartY < 0) {
-                    mViewStartY = 0;
-                }
-                return;
-            }
-        }
-
-        if (mSelectionHour > mFirstHour + mNumHours - 3) {
-            if (mFirstHour < 24 - mNumHours) {
-                mFirstHour += 1;
-                mViewStartY += (mCellHeight + HOUR_GAP);
-                if (mViewStartY > mMaxViewStartY) {
-                    mViewStartY = mMaxViewStartY;
-                }
-                return;
-            } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
-                mViewStartY = mMaxViewStartY;
-            }
-        }
-    }
-
-    void clearCachedEvents() {
-        mLastReloadMillis = 0;
-    }
-
-    private final Runnable mCancelCallback = new Runnable() {
-        public void run() {
-            clearCachedEvents();
-        }
-    };
-
-    /* package */ void reloadEvents() {
-        // Protect against this being called before this view has been
-        // initialized.
-//        if (mContext == null) {
-//            return;
-//        }
-
-        // Make sure our time zones are up to date
-        mTZUpdater.run();
-
-        setSelectedEvent(null);
-        mPrevSelectedEvent = null;
-        mSelectedEvents.clear();
-
-        // The start date is the beginning of the week at 12am
-        Time weekStart = new Time(Utils.getTimeZone(mContext, mTZUpdater));
-        weekStart.set(mBaseDate);
-        weekStart.hour = 0;
-        weekStart.minute = 0;
-        weekStart.second = 0;
-        long millis = weekStart.normalize(true /* ignore isDst */);
-
-        // Avoid reloading events unnecessarily.
-        if (millis == mLastReloadMillis) {
-            return;
-        }
-        mLastReloadMillis = millis;
-
-        // load events in the background
-//        mContext.startProgressSpinner();
-        final ArrayList<Event> events = new ArrayList<Event>();
-        mEventLoader.loadEventsInBackground(mNumDays, events, mFirstJulianDay, new Runnable() {
-
-            public void run() {
-                boolean fadeinEvents = mFirstJulianDay != mLoadedFirstJulianDay;
-                mEvents = events;
-                mLoadedFirstJulianDay = mFirstJulianDay;
-                if (mAllDayEvents == null) {
-                    mAllDayEvents = new ArrayList<Event>();
-                } else {
-                    mAllDayEvents.clear();
-                }
-
-                // Create a shorter array for all day events
-                for (Event e : events) {
-                    if (e.drawAsAllday()) {
-                        mAllDayEvents.add(e);
-                    }
-                }
-
-                // New events, new layouts
-                if (mLayouts == null || mLayouts.length < events.size()) {
-                    mLayouts = new StaticLayout[events.size()];
-                } else {
-                    Arrays.fill(mLayouts, null);
-                }
-
-                if (mAllDayLayouts == null || mAllDayLayouts.length < mAllDayEvents.size()) {
-                    mAllDayLayouts = new StaticLayout[events.size()];
-                } else {
-                    Arrays.fill(mAllDayLayouts, null);
-                }
-
-                computeEventRelations();
-
-                mRemeasure = true;
-                mComputeSelectedEvents = true;
-                recalc();
-
-                // Start animation to cross fade the events
-                if (fadeinEvents) {
-                    if (mEventsCrossFadeAnimation == null) {
-                        mEventsCrossFadeAnimation =
-                                ObjectAnimator.ofInt(DayView.this, "EventsAlpha", 0, 255);
-                        mEventsCrossFadeAnimation.setDuration(EVENTS_CROSS_FADE_DURATION);
-                    }
-                    mEventsCrossFadeAnimation.start();
-                } else{
-                    invalidate();
-                }
-            }
-        }, mCancelCallback);
-    }
-
-    public void setEventsAlpha(int alpha) {
-        mEventsAlpha = alpha;
-        invalidate();
-    }
-
-    public int getEventsAlpha() {
-        return mEventsAlpha;
-    }
-
-    public void stopEventsAnimation() {
-        if (mEventsCrossFadeAnimation != null) {
-            mEventsCrossFadeAnimation.cancel();
-        }
-        mEventsAlpha = 255;
-    }
-
-    private void computeEventRelations() {
-        // Compute the layout relation between each event before measuring cell
-        // width, as the cell width should be adjusted along with the relation.
-        //
-        // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
-        // We should mark them as "overwapped". Though they are not overwapped logically, but
-        // minimum cell height implicitly expands the cell height of A and it should look like
-        // (1:00pm - 1:15pm) after the cell height adjustment.
-
-        // Compute the space needed for the all-day events, if any.
-        // Make a pass over all the events, and keep track of the maximum
-        // number of all-day events in any one day.  Also, keep track of
-        // the earliest event in each day.
-        int maxAllDayEvents = 0;
-        final ArrayList<Event> events = mEvents;
-        final int len = events.size();
-        // Num of all-day-events on each day.
-        final int eventsCount[] = new int[mLastJulianDay - mFirstJulianDay + 1];
-        Arrays.fill(eventsCount, 0);
-        for (int ii = 0; ii < len; ii++) {
-            Event event = events.get(ii);
-            if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
-                continue;
-            }
-            if (event.drawAsAllday()) {
-                // Count all the events being drawn as allDay events
-                final int firstDay = Math.max(event.startDay, mFirstJulianDay);
-                final int lastDay = Math.min(event.endDay, mLastJulianDay);
-                for (int day = firstDay; day <= lastDay; day++) {
-                    final int count = ++eventsCount[day - mFirstJulianDay];
-                    if (maxAllDayEvents < count) {
-                        maxAllDayEvents = count;
-                    }
-                }
-
-                int daynum = event.startDay - mFirstJulianDay;
-                int durationDays = event.endDay - event.startDay + 1;
-                if (daynum < 0) {
-                    durationDays += daynum;
-                    daynum = 0;
-                }
-                if (daynum + durationDays > mNumDays) {
-                    durationDays = mNumDays - daynum;
-                }
-                for (int day = daynum; durationDays > 0; day++, durationDays--) {
-                    mHasAllDayEvent[day] = true;
-                }
-            } else {
-                int daynum = event.startDay - mFirstJulianDay;
-                int hour = event.startTime / 60;
-                if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
-                    mEarliestStartHour[daynum] = hour;
-                }
-
-                // Also check the end hour in case the event spans more than
-                // one day.
-                daynum = event.endDay - mFirstJulianDay;
-                hour = event.endTime / 60;
-                if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
-                    mEarliestStartHour[daynum] = hour;
-                }
-            }
-        }
-        mMaxAlldayEvents = maxAllDayEvents;
-        initAllDayHeights();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mRemeasure) {
-            remeasure(getWidth(), getHeight());
-            mRemeasure = false;
-        }
-        canvas.save();
-
-        float yTranslate = -mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight;
-        // offset canvas by the current drag and header position
-        canvas.translate(-mViewStartX, yTranslate);
-        // clip to everything below the allDay area
-        Rect dest = mDestRect;
-        dest.top = (int) (mFirstCell - yTranslate);
-        dest.bottom = (int) (mViewHeight - yTranslate);
-        dest.left = 0;
-        dest.right = mViewWidth;
-        canvas.save();
-        canvas.clipRect(dest);
-        // Draw the movable part of the view
-        doDraw(canvas);
-        // restore to having no clip
-        canvas.restore();
-
-        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            float xTranslate;
-            if (mViewStartX > 0) {
-                xTranslate = mViewWidth;
-            } else {
-                xTranslate = -mViewWidth;
-            }
-            // Move the canvas around to prep it for the next view
-            // specifically, shift it by a screen and undo the
-            // yTranslation which will be redone in the nextView's onDraw().
-            canvas.translate(xTranslate, -yTranslate);
-            DayView nextView = (DayView) mViewSwitcher.getNextView();
-
-            // Prevent infinite recursive calls to onDraw().
-            nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
-            nextView.onDraw(canvas);
-            // Move it back for this view
-            canvas.translate(-xTranslate, 0);
-        } else {
-            // If we drew another view we already translated it back
-            // If we didn't draw another view we should be at the edge of the
-            // screen
-            canvas.translate(mViewStartX, -yTranslate);
-        }
-
-        // Draw the fixed areas (that don't scroll) directly to the canvas.
-        drawAfterScroll(canvas);
-        if (mComputeSelectedEvents && mUpdateToast) {
-            mUpdateToast = false;
-        }
-        mComputeSelectedEvents = false;
-
-        // Draw overscroll glow
-        if (!mEdgeEffectTop.isFinished()) {
-            if (DAY_HEADER_HEIGHT != 0) {
-                canvas.translate(0, DAY_HEADER_HEIGHT);
-            }
-            if (mEdgeEffectTop.draw(canvas)) {
-                invalidate();
-            }
-            if (DAY_HEADER_HEIGHT != 0) {
-                canvas.translate(0, -DAY_HEADER_HEIGHT);
-            }
-        }
-        if (!mEdgeEffectBottom.isFinished()) {
-            canvas.rotate(180, mViewWidth/2, mViewHeight/2);
-            if (mEdgeEffectBottom.draw(canvas)) {
-                invalidate();
-            }
-        }
-        canvas.restore();
-    }
-
-    private void drawAfterScroll(Canvas canvas) {
-        Paint p = mPaint;
-        Rect r = mRect;
-
-        drawAllDayHighlights(r, canvas, p);
-        if (mMaxAlldayEvents != 0) {
-            drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p);
-            drawUpperLeftCorner(r, canvas, p);
-        }
-
-        drawScrollLine(r, canvas, p);
-        drawDayHeaderLoop(r, canvas, p);
-
-        // Draw the AM and PM indicators if we're in 12 hour mode
-        if (!mIs24HourFormat) {
-            drawAmPm(canvas, p);
-        }
-    }
-
-    // This isn't really the upper-left corner. It's the square area just
-    // below the upper-left corner, above the hours and to the left of the
-    // all-day area.
-    private void drawUpperLeftCorner(Rect r, Canvas canvas, Paint p) {
-        setupHourTextPaint(p);
-        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
-            // Draw the allDay expand/collapse icon
-            if (mUseExpandIcon) {
-                mExpandAlldayDrawable.setBounds(mExpandAllDayRect);
-                mExpandAlldayDrawable.draw(canvas);
-            } else {
-                mCollapseAlldayDrawable.setBounds(mExpandAllDayRect);
-                mCollapseAlldayDrawable.draw(canvas);
-            }
-        }
-    }
-
-    private void drawScrollLine(Rect r, Canvas canvas, Paint p) {
-        final int right = computeDayLeftPosition(mNumDays);
-        final int y = mFirstCell - 1;
-
-        p.setAntiAlias(false);
-        p.setStyle(Style.FILL);
-
-        p.setColor(mCalendarGridLineInnerHorizontalColor);
-        p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
-        canvas.drawLine(GRID_LINE_LEFT_MARGIN, y, right, y, p);
-        p.setAntiAlias(true);
-    }
-
-    // Computes the x position for the left side of the given day (base 0)
-    private int computeDayLeftPosition(int day) {
-        int effectiveWidth = mViewWidth - mHoursWidth;
-        return day * effectiveWidth / mNumDays + mHoursWidth;
-    }
-
-    private void drawAllDayHighlights(Rect r, Canvas canvas, Paint p) {
-        if (mFutureBgColor != 0) {
-            // First, color the labels area light gray
-            r.top = 0;
-            r.bottom = DAY_HEADER_HEIGHT;
-            r.left = 0;
-            r.right = mViewWidth;
-            p.setColor(mBgColor);
-            p.setStyle(Style.FILL);
-            canvas.drawRect(r, p);
-            // and the area that says All day
-            r.top = DAY_HEADER_HEIGHT;
-            r.bottom = mFirstCell - 1;
-            r.left = 0;
-            r.right = mHoursWidth;
-            canvas.drawRect(r, p);
-
-            int startIndex = -1;
-
-            int todayIndex = mTodayJulianDay - mFirstJulianDay;
-            if (todayIndex < 0) {
-                // Future
-                startIndex = 0;
-            } else if (todayIndex >= 1 && todayIndex + 1 < mNumDays) {
-                // Multiday - tomorrow is visible.
-                startIndex = todayIndex + 1;
-            }
-
-            if (startIndex >= 0) {
-                // Draw the future highlight
-                r.top = 0;
-                r.bottom = mFirstCell - 1;
-                r.left = computeDayLeftPosition(startIndex) + 1;
-                r.right = computeDayLeftPosition(mNumDays);
-                p.setColor(mFutureBgColor);
-                p.setStyle(Style.FILL);
-                canvas.drawRect(r, p);
-            }
-        }
-    }
-
-    private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) {
-        // Draw the horizontal day background banner
-        // p.setColor(mCalendarDateBannerBackground);
-        // r.top = 0;
-        // r.bottom = DAY_HEADER_HEIGHT;
-        // r.left = 0;
-        // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
-        // canvas.drawRect(r, p);
-        //
-        // Fill the extra space on the right side with the default background
-        // r.left = r.right;
-        // r.right = mViewWidth;
-        // p.setColor(mCalendarGridAreaBackground);
-        // canvas.drawRect(r, p);
-        if (mNumDays == 1 && ONE_DAY_HEADER_HEIGHT == 0) {
-            return;
-        }
-
-        p.setTypeface(mBold);
-        p.setTextAlign(Paint.Align.RIGHT);
-        int cell = mFirstJulianDay;
-
-        String[] dayNames;
-        if (mDateStrWidth < mCellWidth) {
-            dayNames = mDayStrs;
-        } else {
-            dayNames = mDayStrs2Letter;
-        }
-
-        p.setAntiAlias(true);
-        for (int day = 0; day < mNumDays; day++, cell++) {
-            int dayOfWeek = day + mFirstVisibleDayOfWeek;
-            if (dayOfWeek >= 14) {
-                dayOfWeek -= 14;
-            }
-
-            int color = mCalendarDateBannerTextColor;
-            if (mNumDays == 1) {
-                if (dayOfWeek == Time.SATURDAY) {
-                    color = mWeek_saturdayColor;
-                } else if (dayOfWeek == Time.SUNDAY) {
-                    color = mWeek_sundayColor;
-                }
-            } else {
-                final int column = day % 7;
-                if (Utils.isSaturday(column, mFirstDayOfWeek)) {
-                    color = mWeek_saturdayColor;
-                } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
-                    color = mWeek_sundayColor;
-                }
-            }
-
-            p.setColor(color);
-            drawDayHeader(dayNames[dayOfWeek], day, cell, canvas, p);
-        }
-        p.setTypeface(null);
-    }
-
-    private void drawAmPm(Canvas canvas, Paint p) {
-        p.setColor(mCalendarAmPmLabel);
-        p.setTextSize(AMPM_TEXT_SIZE);
-        p.setTypeface(mBold);
-        p.setAntiAlias(true);
-        p.setTextAlign(Paint.Align.RIGHT);
-        String text = mAmString;
-        if (mFirstHour >= 12) {
-            text = mPmString;
-        }
-        int y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP;
-        canvas.drawText(text, HOURS_LEFT_MARGIN, y, p);
-
-        if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
-            // Also draw the "PM"
-            text = mPmString;
-            y = mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP)
-                    + 2 * mHoursTextHeight + HOUR_GAP;
-            canvas.drawText(text, HOURS_LEFT_MARGIN, y, p);
-        }
-    }
-
-    private void drawCurrentTimeLine(Rect r, final int day, final int top, Canvas canvas,
-            Paint p) {
-        r.left = computeDayLeftPosition(day) - CURRENT_TIME_LINE_SIDE_BUFFER + 1;
-        r.right = computeDayLeftPosition(day + 1) + CURRENT_TIME_LINE_SIDE_BUFFER + 1;
-
-        r.top = top - CURRENT_TIME_LINE_TOP_OFFSET;
-        r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight();
-
-        mCurrentTimeLine.setBounds(r);
-        mCurrentTimeLine.draw(canvas);
-        if (mAnimateToday) {
-            mCurrentTimeAnimateLine.setBounds(r);
-            mCurrentTimeAnimateLine.setAlpha(mAnimateTodayAlpha);
-            mCurrentTimeAnimateLine.draw(canvas);
-        }
-    }
-
-    private void doDraw(Canvas canvas) {
-        Paint p = mPaint;
-        Rect r = mRect;
-
-        if (mFutureBgColor != 0) {
-            drawBgColors(r, canvas, p);
-        }
-        drawGridBackground(r, canvas, p);
-        drawHours(r, canvas, p);
-
-        // Draw each day
-        int cell = mFirstJulianDay;
-        p.setAntiAlias(false);
-        int alpha = p.getAlpha();
-        p.setAlpha(mEventsAlpha);
-        for (int day = 0; day < mNumDays; day++, cell++) {
-            // TODO Wow, this needs cleanup. drawEvents loop through all the
-            // events on every call.
-            drawEvents(cell, day, HOUR_GAP, canvas, p);
-            // If this is today
-            if (cell == mTodayJulianDay) {
-                int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
-                        + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
-
-                // And the current time shows up somewhere on the screen
-                if (lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
-                    drawCurrentTimeLine(r, day, lineY, canvas, p);
-                }
-            }
-        }
-        p.setAntiAlias(true);
-        p.setAlpha(alpha);
-    }
-
-    private void drawHours(Rect r, Canvas canvas, Paint p) {
-        setupHourTextPaint(p);
-
-        int y = HOUR_GAP + mHoursTextHeight + HOURS_TOP_MARGIN;
-
-        for (int i = 0; i < 24; i++) {
-            String time = mHourStrs[i];
-            canvas.drawText(time, HOURS_LEFT_MARGIN, y, p);
-            y += mCellHeight + HOUR_GAP;
-        }
-    }
-
-    private void setupHourTextPaint(Paint p) {
-        p.setColor(mCalendarHourLabelColor);
-        p.setTextSize(HOURS_TEXT_SIZE);
-        p.setTypeface(Typeface.DEFAULT);
-        p.setTextAlign(Paint.Align.RIGHT);
-        p.setAntiAlias(true);
-    }
-
-    private void drawDayHeader(String dayStr, int day, int cell, Canvas canvas, Paint p) {
-        int dateNum = mFirstVisibleDate + day;
-        int x;
-        if (dateNum > mMonthLength) {
-            dateNum -= mMonthLength;
-        }
-        p.setAntiAlias(true);
-
-        int todayIndex = mTodayJulianDay - mFirstJulianDay;
-        // Draw day of the month
-        String dateNumStr = String.valueOf(dateNum);
-        if (mNumDays > 1) {
-            float y = DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN;
-
-            // Draw day of the month
-            x = computeDayLeftPosition(day + 1) - DAY_HEADER_RIGHT_MARGIN;
-            p.setTextAlign(Align.RIGHT);
-            p.setTextSize(DATE_HEADER_FONT_SIZE);
-
-            p.setTypeface(todayIndex == day ? mBold : Typeface.DEFAULT);
-            canvas.drawText(dateNumStr, x, y, p);
-
-            // Draw day of the week
-            x -= p.measureText(" " + dateNumStr);
-            p.setTextSize(DAY_HEADER_FONT_SIZE);
-            p.setTypeface(Typeface.DEFAULT);
-            canvas.drawText(dayStr, x, y, p);
-        } else {
-            float y = ONE_DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN;
-            p.setTextAlign(Align.LEFT);
-
-
-            // Draw day of the week
-            x = computeDayLeftPosition(day) + DAY_HEADER_ONE_DAY_LEFT_MARGIN;
-            p.setTextSize(DAY_HEADER_FONT_SIZE);
-            p.setTypeface(Typeface.DEFAULT);
-            canvas.drawText(dayStr, x, y, p);
-
-            // Draw day of the month
-            x += p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN;
-            p.setTextSize(DATE_HEADER_FONT_SIZE);
-            p.setTypeface(todayIndex == day ? mBold : Typeface.DEFAULT);
-            canvas.drawText(dateNumStr, x, y, p);
-        }
-    }
-
-    private void drawGridBackground(Rect r, Canvas canvas, Paint p) {
-        Paint.Style savedStyle = p.getStyle();
-
-        final float stopX = computeDayLeftPosition(mNumDays);
-        float y = 0;
-        final float deltaY = mCellHeight + HOUR_GAP;
-        int linesIndex = 0;
-        final float startY = 0;
-        final float stopY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
-        float x = mHoursWidth;
-
-        // Draw the inner horizontal grid lines
-        p.setColor(mCalendarGridLineInnerHorizontalColor);
-        p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
-        p.setAntiAlias(false);
-        y = 0;
-        linesIndex = 0;
-        for (int hour = 0; hour <= 24; hour++) {
-            mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN;
-            mLines[linesIndex++] = y;
-            mLines[linesIndex++] = stopX;
-            mLines[linesIndex++] = y;
-            y += deltaY;
-        }
-        if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
-            canvas.drawLines(mLines, 0, linesIndex, p);
-            linesIndex = 0;
-            p.setColor(mCalendarGridLineInnerVerticalColor);
-        }
-
-        // Draw the inner vertical grid lines
-        for (int day = 0; day <= mNumDays; day++) {
-            x = computeDayLeftPosition(day);
-            mLines[linesIndex++] = x;
-            mLines[linesIndex++] = startY;
-            mLines[linesIndex++] = x;
-            mLines[linesIndex++] = stopY;
-        }
-        canvas.drawLines(mLines, 0, linesIndex, p);
-
-        // Restore the saved style.
-        p.setStyle(savedStyle);
-        p.setAntiAlias(true);
-    }
-
-    /**
-     * @param r
-     * @param canvas
-     * @param p
-     */
-    private void drawBgColors(Rect r, Canvas canvas, Paint p) {
-        int todayIndex = mTodayJulianDay - mFirstJulianDay;
-        // Draw the hours background color
-        r.top = mDestRect.top;
-        r.bottom = mDestRect.bottom;
-        r.left = 0;
-        r.right = mHoursWidth;
-        p.setColor(mBgColor);
-        p.setStyle(Style.FILL);
-        p.setAntiAlias(false);
-        canvas.drawRect(r, p);
-
-        // Draw background for grid area
-        if (mNumDays == 1 && todayIndex == 0) {
-            // Draw a white background for the time later than current time
-            int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
-                    + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
-            if (lineY < mViewStartY + mViewHeight) {
-                lineY = Math.max(lineY, mViewStartY);
-                r.left = mHoursWidth;
-                r.right = mViewWidth;
-                r.top = lineY;
-                r.bottom = mViewStartY + mViewHeight;
-                p.setColor(mFutureBgColor);
-                canvas.drawRect(r, p);
-            }
-        } else if (todayIndex >= 0 && todayIndex < mNumDays) {
-            // Draw today with a white background for the time later than current time
-            int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
-                    + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
-            if (lineY < mViewStartY + mViewHeight) {
-                lineY = Math.max(lineY, mViewStartY);
-                r.left = computeDayLeftPosition(todayIndex) + 1;
-                r.right = computeDayLeftPosition(todayIndex + 1);
-                r.top = lineY;
-                r.bottom = mViewStartY + mViewHeight;
-                p.setColor(mFutureBgColor);
-                canvas.drawRect(r, p);
-            }
-
-            // Paint Tomorrow and later days with future color
-            if (todayIndex + 1 < mNumDays) {
-                r.left = computeDayLeftPosition(todayIndex + 1) + 1;
-                r.right = computeDayLeftPosition(mNumDays);
-                r.top = mDestRect.top;
-                r.bottom = mDestRect.bottom;
-                p.setColor(mFutureBgColor);
-                canvas.drawRect(r, p);
-            }
-        } else if (todayIndex < 0) {
-            // Future
-            r.left = computeDayLeftPosition(0) + 1;
-            r.right = computeDayLeftPosition(mNumDays);
-            r.top = mDestRect.top;
-            r.bottom = mDestRect.bottom;
-            p.setColor(mFutureBgColor);
-            canvas.drawRect(r, p);
-        }
-        p.setAntiAlias(true);
-    }
-
-    private int computeMaxStringWidth(int currentMax, String[] strings, Paint p) {
-        float maxWidthF = 0.0f;
-
-        int len = strings.length;
-        for (int i = 0; i < len; i++) {
-            float width = p.measureText(strings[i]);
-            maxWidthF = Math.max(width, maxWidthF);
-        }
-        int maxWidth = (int) (maxWidthF + 0.5);
-        if (maxWidth < currentMax) {
-            maxWidth = currentMax;
-        }
-        return maxWidth;
-    }
-
-    private void saveSelectionPosition(float left, float top, float right, float bottom) {
-        mPrevBox.left = (int) left;
-        mPrevBox.right = (int) right;
-        mPrevBox.top = (int) top;
-        mPrevBox.bottom = (int) bottom;
-    }
-
-    private void setupTextRect(Rect r) {
-        if (r.bottom <= r.top || r.right <= r.left) {
-            r.bottom = r.top;
-            r.right = r.left;
-            return;
-        }
-
-        if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
-            r.top += EVENT_TEXT_TOP_MARGIN;
-            r.bottom -= EVENT_TEXT_BOTTOM_MARGIN;
-        }
-        if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
-            r.left += EVENT_TEXT_LEFT_MARGIN;
-            r.right -= EVENT_TEXT_RIGHT_MARGIN;
-        }
-    }
-
-    private void setupAllDayTextRect(Rect r) {
-        if (r.bottom <= r.top || r.right <= r.left) {
-            r.bottom = r.top;
-            r.right = r.left;
-            return;
-        }
-
-        if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
-            r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN;
-            r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN;
-        }
-        if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
-            r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN;
-            r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN;
-        }
-    }
-
-    /**
-     * Return the layout for a numbered event. Create it if not already existing
-     */
-    private StaticLayout getEventLayout(StaticLayout[] layouts, int i, Event event, Paint paint,
-            Rect r) {
-        if (i < 0 || i >= layouts.length) {
-            return null;
-        }
-
-        StaticLayout layout = layouts[i];
-        // Check if we have already initialized the StaticLayout and that
-        // the width hasn't changed (due to vertical resizing which causes
-        // re-layout of events at min height)
-        if (layout == null || r.width() != layout.getWidth()) {
-            SpannableStringBuilder bob = new SpannableStringBuilder();
-            if (event.title != null) {
-                // MAX - 1 since we add a space
-                bob.append(drawTextSanitizer(event.title.toString(), MAX_EVENT_TEXT_LEN - 1));
-                bob.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length(), 0);
-                bob.append(' ');
-            }
-            if (event.location != null) {
-                bob.append(drawTextSanitizer(event.location.toString(),
-                        MAX_EVENT_TEXT_LEN - bob.length()));
-            }
-
-            switch (event.selfAttendeeStatus) {
-                case Attendees.ATTENDEE_STATUS_INVITED:
-                    paint.setColor(event.color);
-                    break;
-                case Attendees.ATTENDEE_STATUS_DECLINED:
-                    paint.setColor(mEventTextColor);
-                    paint.setAlpha(Utils.DECLINED_EVENT_TEXT_ALPHA);
-                    break;
-                case Attendees.ATTENDEE_STATUS_NONE: // Your own events
-                case Attendees.ATTENDEE_STATUS_ACCEPTED:
-                case Attendees.ATTENDEE_STATUS_TENTATIVE:
-                default:
-                    paint.setColor(mEventTextColor);
-                    break;
-            }
-
-            // Leave a one pixel boundary on the left and right of the rectangle for the event
-            layout = new StaticLayout(bob, 0, bob.length(), new TextPaint(paint), r.width(),
-                    Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width());
-
-            layouts[i] = layout;
-        }
-        layout.getPaint().setAlpha(mEventsAlpha);
-        return layout;
-    }
-
-    private void drawAllDayEvents(int firstDay, int numDays, Canvas canvas, Paint p) {
-
-        p.setTextSize(NORMAL_FONT_SIZE);
-        p.setTextAlign(Paint.Align.LEFT);
-        Paint eventTextPaint = mEventTextPaint;
-
-        final float startY = DAY_HEADER_HEIGHT;
-        final float stopY = startY + mAlldayHeight + ALLDAY_TOP_MARGIN;
-        float x = 0;
-        int linesIndex = 0;
-
-        // Draw the inner vertical grid lines
-        p.setColor(mCalendarGridLineInnerVerticalColor);
-        x = mHoursWidth;
-        p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
-        // Line bounding the top of the all day area
-        mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN;
-        mLines[linesIndex++] = startY;
-        mLines[linesIndex++] = computeDayLeftPosition(mNumDays);
-        mLines[linesIndex++] = startY;
-
-        for (int day = 0; day <= mNumDays; day++) {
-            x = computeDayLeftPosition(day);
-            mLines[linesIndex++] = x;
-            mLines[linesIndex++] = startY;
-            mLines[linesIndex++] = x;
-            mLines[linesIndex++] = stopY;
-        }
-        p.setAntiAlias(false);
-        canvas.drawLines(mLines, 0, linesIndex, p);
-        p.setStyle(Style.FILL);
-
-        int y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
-        int lastDay = firstDay + numDays - 1;
-        final ArrayList<Event> events = mAllDayEvents;
-        int numEvents = events.size();
-        // Whether or not we should draw the more events text
-        boolean hasMoreEvents = false;
-        // size of the allDay area
-        float drawHeight = mAlldayHeight;
-        // max number of events being drawn in one day of the allday area
-        float numRectangles = mMaxAlldayEvents;
-        // Where to cut off drawn allday events
-        int allDayEventClip = DAY_HEADER_HEIGHT + mAlldayHeight + ALLDAY_TOP_MARGIN;
-        // The number of events that weren't drawn in each day
-        mSkippedAlldayEvents = new int[numDays];
-        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount && !mShowAllAllDayEvents &&
-                mAnimateDayHeight == 0) {
-            // We draw one fewer event than will fit so that more events text
-            // can be drawn
-            numRectangles = mMaxUnexpandedAlldayEventCount - 1;
-            // We also clip the events above the more events text
-            allDayEventClip -= MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-            hasMoreEvents = true;
-        } else if (mAnimateDayHeight != 0) {
-            // clip at the end of the animating space
-            allDayEventClip = DAY_HEADER_HEIGHT + mAnimateDayHeight + ALLDAY_TOP_MARGIN;
-        }
-
-        int alpha = eventTextPaint.getAlpha();
-        eventTextPaint.setAlpha(mEventsAlpha);
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            int startDay = event.startDay;
-            int endDay = event.endDay;
-            if (startDay > lastDay || endDay < firstDay) {
-                continue;
-            }
-            if (startDay < firstDay) {
-                startDay = firstDay;
-            }
-            if (endDay > lastDay) {
-                endDay = lastDay;
-            }
-            int startIndex = startDay - firstDay;
-            int endIndex = endDay - firstDay;
-            float height = mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount ? mAnimateDayEventHeight :
-                    drawHeight / numRectangles;
-
-            // Prevent a single event from getting too big
-            if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
-                height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
-            }
-
-            // Leave a one-pixel space between the vertical day lines and the
-            // event rectangle.
-            event.left = computeDayLeftPosition(startIndex);
-            event.right = computeDayLeftPosition(endIndex + 1) - DAY_GAP;
-            event.top = y + height * event.getColumn();
-            event.bottom = event.top + height - ALL_DAY_EVENT_RECT_BOTTOM_MARGIN;
-            if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
-                // check if we should skip this event. We skip if it starts
-                // after the clip bound or ends after the skip bound and we're
-                // not animating.
-                if (event.top >= allDayEventClip) {
-                    incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex);
-                    continue;
-                } else if (event.bottom > allDayEventClip) {
-                    if (hasMoreEvents) {
-                        incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex);
-                        continue;
-                    }
-                    event.bottom = allDayEventClip;
-                }
-            }
-            Rect r = drawEventRect(event, canvas, p, eventTextPaint, (int) event.top,
-                    (int) event.bottom);
-            setupAllDayTextRect(r);
-            StaticLayout layout = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r);
-            drawEventText(layout, r, canvas, r.top, r.bottom, true);
-
-            // Check if this all-day event intersects the selected day
-            if (mSelectionAllday && mComputeSelectedEvents) {
-                if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
-                    mSelectedEvents.add(event);
-                }
-            }
-        }
-        eventTextPaint.setAlpha(alpha);
-
-        if (mMoreAlldayEventsTextAlpha != 0 && mSkippedAlldayEvents != null) {
-            // If the more allday text should be visible, draw it.
-            alpha = p.getAlpha();
-            p.setAlpha(mEventsAlpha);
-            p.setColor(mMoreAlldayEventsTextAlpha << 24 & mMoreEventsTextColor);
-            for (int i = 0; i < mSkippedAlldayEvents.length; i++) {
-                if (mSkippedAlldayEvents[i] > 0) {
-                    drawMoreAlldayEvents(canvas, mSkippedAlldayEvents[i], i, p);
-                }
-            }
-            p.setAlpha(alpha);
-        }
-
-        if (mSelectionAllday) {
-            // Compute the neighbors for the list of all-day events that
-            // intersect the selected day.
-            computeAllDayNeighbors();
-
-            // Set the selection position to zero so that when we move down
-            // to the normal event area, we will highlight the topmost event.
-            saveSelectionPosition(0f, 0f, 0f, 0f);
-        }
-    }
-
-    // Helper method for counting the number of allday events skipped on each day
-    private void incrementSkipCount(int[] counts, int startIndex, int endIndex) {
-        if (counts == null || startIndex < 0 || endIndex > counts.length) {
-            return;
-        }
-        for (int i = startIndex; i <= endIndex; i++) {
-            counts[i]++;
-        }
-    }
-
-    // Draws the "box +n" text for hidden allday events
-    protected void drawMoreAlldayEvents(Canvas canvas, int remainingEvents, int day, Paint p) {
-        int x = computeDayLeftPosition(day) + EVENT_ALL_DAY_TEXT_LEFT_MARGIN;
-        int y = (int) (mAlldayHeight - .5f * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - .5f
-                * EVENT_SQUARE_WIDTH + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN);
-        Rect r = mRect;
-        r.top = y;
-        r.left = x;
-        r.bottom = y + EVENT_SQUARE_WIDTH;
-        r.right = x + EVENT_SQUARE_WIDTH;
-        p.setColor(mMoreEventsTextColor);
-        p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH);
-        p.setStyle(Style.STROKE);
-        p.setAntiAlias(false);
-        canvas.drawRect(r, p);
-        p.setAntiAlias(true);
-        p.setStyle(Style.FILL);
-        p.setTextSize(EVENT_TEXT_FONT_SIZE);
-        String text = mResources.getQuantityString(R.plurals.month_more_events, remainingEvents);
-        y += EVENT_SQUARE_WIDTH;
-        x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING;
-        canvas.drawText(String.format(text, remainingEvents), x, y, p);
-    }
-
-    private void computeAllDayNeighbors() {
-        int len = mSelectedEvents.size();
-        if (len == 0 || mSelectedEvent != null) {
-            return;
-        }
-
-        // First, clear all the links
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-            ev.nextUp = null;
-            ev.nextDown = null;
-            ev.nextLeft = null;
-            ev.nextRight = null;
-        }
-
-        // For each event in the selected event list "mSelectedEvents", find
-        // its neighbors in the up and down directions. This could be done
-        // more efficiently by sorting on the Event.getColumn() field, but
-        // the list is expected to be very small.
-
-        // Find the event in the same row as the previously selected all-day
-        // event, if any.
-        int startPosition = -1;
-        if (mPrevSelectedEvent != null && mPrevSelectedEvent.drawAsAllday()) {
-            startPosition = mPrevSelectedEvent.getColumn();
-        }
-        int maxPosition = -1;
-        Event startEvent = null;
-        Event maxPositionEvent = null;
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-            int position = ev.getColumn();
-            if (position == startPosition) {
-                startEvent = ev;
-            } else if (position > maxPosition) {
-                maxPositionEvent = ev;
-                maxPosition = position;
-            }
-            for (int jj = 0; jj < len; jj++) {
-                if (jj == ii) {
-                    continue;
-                }
-                Event neighbor = mSelectedEvents.get(jj);
-                int neighborPosition = neighbor.getColumn();
-                if (neighborPosition == position - 1) {
-                    ev.nextUp = neighbor;
-                } else if (neighborPosition == position + 1) {
-                    ev.nextDown = neighbor;
-                }
-            }
-        }
-        if (startEvent != null) {
-            setSelectedEvent(startEvent);
-        } else {
-            setSelectedEvent(maxPositionEvent);
-        }
-    }
-
-    private void drawEvents(int date, int dayIndex, int top, Canvas canvas, Paint p) {
-        Paint eventTextPaint = mEventTextPaint;
-        int left = computeDayLeftPosition(dayIndex) + 1;
-        int cellWidth = computeDayLeftPosition(dayIndex + 1) - left + 1;
-        int cellHeight = mCellHeight;
-
-        // Use the selected hour as the selection region
-        Rect selectionArea = mSelectionRect;
-        selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
-        selectionArea.bottom = selectionArea.top + cellHeight;
-        selectionArea.left = left;
-        selectionArea.right = selectionArea.left + cellWidth;
-
-        final ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        EventGeometry geometry = mEventGeometry;
-
-        final int viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight;
-
-        int alpha = eventTextPaint.getAlpha();
-        eventTextPaint.setAlpha(mEventsAlpha);
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
-                continue;
-            }
-
-            // Don't draw it if it is not visible
-            if (event.bottom < mViewStartY || event.top > viewEndY) {
-                continue;
-            }
-
-            if (date == mSelectionDay && !mSelectionAllday && mComputeSelectedEvents
-                    && geometry.eventIntersectsSelection(event, selectionArea)) {
-                mSelectedEvents.add(event);
-            }
-
-            Rect r = drawEventRect(event, canvas, p, eventTextPaint, mViewStartY, viewEndY);
-            setupTextRect(r);
-
-            // Don't draw text if it is not visible
-            if (r.top > viewEndY || r.bottom < mViewStartY) {
-                continue;
-            }
-            StaticLayout layout = getEventLayout(mLayouts, i, event, eventTextPaint, r);
-            // TODO: not sure why we are 4 pixels off
-            drawEventText(layout, r, canvas, mViewStartY + 4, mViewStartY + mViewHeight
-                    - DAY_HEADER_HEIGHT - mAlldayHeight, false);
-        }
-        eventTextPaint.setAlpha(alpha);
-    }
-
-    private Rect drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint,
-            int visibleTop, int visibleBot) {
-        // Draw the Event Rect
-        Rect r = mRect;
-        r.top = Math.max((int) event.top + EVENT_RECT_TOP_MARGIN, visibleTop);
-        r.bottom = Math.min((int) event.bottom - EVENT_RECT_BOTTOM_MARGIN, visibleBot);
-        r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
-        r.right = (int) event.right;
-
-        int color = event.color;
-        switch (event.selfAttendeeStatus) {
-            case Attendees.ATTENDEE_STATUS_INVITED:
-                if (event != mClickedEvent) {
-                    p.setStyle(Style.STROKE);
-                }
-                break;
-            case Attendees.ATTENDEE_STATUS_DECLINED:
-                if (event != mClickedEvent) {
-                    color = Utils.getDeclinedColorFromColor(color);
-                }
-            case Attendees.ATTENDEE_STATUS_NONE: // Your own events
-            case Attendees.ATTENDEE_STATUS_ACCEPTED:
-            case Attendees.ATTENDEE_STATUS_TENTATIVE:
-            default:
-                p.setStyle(Style.FILL_AND_STROKE);
-                break;
-        }
-
-        p.setAntiAlias(false);
-
-        int floorHalfStroke = (int) Math.floor(EVENT_RECT_STROKE_WIDTH / 2.0f);
-        int ceilHalfStroke = (int) Math.ceil(EVENT_RECT_STROKE_WIDTH / 2.0f);
-        r.top = Math.max((int) event.top + EVENT_RECT_TOP_MARGIN + floorHalfStroke, visibleTop);
-        r.bottom = Math.min((int) event.bottom - EVENT_RECT_BOTTOM_MARGIN - ceilHalfStroke,
-                visibleBot);
-        r.left += floorHalfStroke;
-        r.right -= ceilHalfStroke;
-        p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH);
-        p.setColor(color);
-        int alpha = p.getAlpha();
-        p.setAlpha(mEventsAlpha);
-        canvas.drawRect(r, p);
-        p.setAlpha(alpha);
-        p.setStyle(Style.FILL);
-
-        // Setup rect for drawEventText which follows
-        r.top = (int) event.top + EVENT_RECT_TOP_MARGIN;
-        r.bottom = (int) event.bottom - EVENT_RECT_BOTTOM_MARGIN;
-        r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
-        r.right = (int) event.right - EVENT_RECT_RIGHT_MARGIN;
-        return r;
-    }
-
-    private final Pattern drawTextSanitizerFilter = Pattern.compile("[\t\n],");
-
-    // Sanitize a string before passing it to drawText or else we get little
-    // squares. For newlines and tabs before a comma, delete the character.
-    // Otherwise, just replace them with a space.
-    private String drawTextSanitizer(String string, int maxEventTextLen) {
-        Matcher m = drawTextSanitizerFilter.matcher(string);
-        string = m.replaceAll(",");
-
-        int len = string.length();
-        if (maxEventTextLen <= 0) {
-            string = "";
-            len = 0;
-        } else if (len > maxEventTextLen) {
-            string = string.substring(0, maxEventTextLen);
-            len = maxEventTextLen;
-        }
-
-        return string.replace('\n', ' ');
-    }
-
-    private void drawEventText(StaticLayout eventLayout, Rect rect, Canvas canvas, int top,
-            int bottom, boolean center) {
-        // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
-
-        int width = rect.right - rect.left;
-        int height = rect.bottom - rect.top;
-
-        // If the rectangle is too small for text, then return
-        if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
-            return;
-        }
-
-        int totalLineHeight = 0;
-        int lineCount = eventLayout.getLineCount();
-        for (int i = 0; i < lineCount; i++) {
-            int lineBottom = eventLayout.getLineBottom(i);
-            if (lineBottom <= height) {
-                totalLineHeight = lineBottom;
-            } else {
-                break;
-            }
-        }
-
-        // + 2 is small workaround when the font is slightly bigger then the rect. This will
-        // still allow the text to be shown without overflowing into the other all day rects.
-        if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight + 2 < top) {
-            return;
-        }
-
-        // Use a StaticLayout to format the string.
-        canvas.save();
-      //  canvas.translate(rect.left, rect.top + (rect.bottom - rect.top / 2));
-        int padding = center? (rect.bottom - rect.top - totalLineHeight) / 2 : 0;
-        canvas.translate(rect.left, rect.top + padding);
-        rect.left = 0;
-        rect.right = width;
-        rect.top = 0;
-        rect.bottom = totalLineHeight;
-
-        // There's a bug somewhere. If this rect is outside of a previous
-        // cliprect, this becomes a no-op. What happens is that the text draw
-        // past the event rect. The current fix is to not draw the staticLayout
-        // at all if it is completely out of bound.
-        canvas.clipRect(rect);
-        eventLayout.draw(canvas);
-        canvas.restore();
-    }
-
-    // The following routines are called from the parent activity when certain
-    // touch events occur.
-    private void doDown(MotionEvent ev) {
-        mTouchMode = TOUCH_MODE_DOWN;
-        mViewStartX = 0;
-        mOnFlingCalled = false;
-        mHandler.removeCallbacks(mContinueScroll);
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        // Save selection information: we use setSelectionFromPosition to find the selected event
-        // in order to show the "clicked" color. But since it is also setting the selected info
-        // for new events, we need to restore the old info after calling the function.
-        Event oldSelectedEvent = mSelectedEvent;
-        int oldSelectionDay = mSelectionDay;
-        int oldSelectionHour = mSelectionHour;
-        if (setSelectionFromPosition(x, y, false)) {
-            // If a time was selected (a blue selection box is visible) and the click location
-            // is in the selected time, do not show a click on an event to prevent a situation
-            // of both a selection and an event are clicked when they overlap.
-            boolean pressedSelected = (mSelectionMode != SELECTION_HIDDEN)
-                    && oldSelectionDay == mSelectionDay && oldSelectionHour == mSelectionHour;
-            if (!pressedSelected && mSelectedEvent != null) {
-                mSavedClickedEvent = mSelectedEvent;
-                mDownTouchTime = System.currentTimeMillis();
-                postDelayed (mSetClick,mOnDownDelay);
-            } else {
-                eventClickCleanup();
-            }
-        }
-        mSelectedEvent = oldSelectedEvent;
-        mSelectionDay = oldSelectionDay;
-        mSelectionHour = oldSelectionHour;
-        invalidate();
-    }
-
-    // Kicks off all the animations when the expand allday area is tapped
-    private void doExpandAllDayClick() {
-        mShowAllAllDayEvents = !mShowAllAllDayEvents;
-
-        ObjectAnimator.setFrameDelay(0);
-
-        // Determine the starting height
-        if (mAnimateDayHeight == 0) {
-            mAnimateDayHeight = mShowAllAllDayEvents ?
-                    mAlldayHeight - (int) MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT : mAlldayHeight;
-        }
-        // Cancel current animations
-        mCancellingAnimations = true;
-        if (mAlldayAnimator != null) {
-            mAlldayAnimator.cancel();
-        }
-        if (mAlldayEventAnimator != null) {
-            mAlldayEventAnimator.cancel();
-        }
-        if (mMoreAlldayEventsAnimator != null) {
-            mMoreAlldayEventsAnimator.cancel();
-        }
-        mCancellingAnimations = false;
-        // get new animators
-        mAlldayAnimator = getAllDayAnimator();
-        mAlldayEventAnimator = getAllDayEventAnimator();
-        mMoreAlldayEventsAnimator = ObjectAnimator.ofInt(this,
-                    "moreAllDayEventsTextAlpha",
-                    mShowAllAllDayEvents ? MORE_EVENTS_MAX_ALPHA : 0,
-                    mShowAllAllDayEvents ? 0 : MORE_EVENTS_MAX_ALPHA);
-
-        // Set up delays and start the animators
-        mAlldayAnimator.setStartDelay(mShowAllAllDayEvents ? ANIMATION_SECONDARY_DURATION : 0);
-        mAlldayAnimator.start();
-        mMoreAlldayEventsAnimator.setStartDelay(mShowAllAllDayEvents ? 0 : ANIMATION_DURATION);
-        mMoreAlldayEventsAnimator.setDuration(ANIMATION_SECONDARY_DURATION);
-        mMoreAlldayEventsAnimator.start();
-        if (mAlldayEventAnimator != null) {
-            // This is the only animator that can return null, so check it
-            mAlldayEventAnimator
-                    .setStartDelay(mShowAllAllDayEvents ? ANIMATION_SECONDARY_DURATION : 0);
-            mAlldayEventAnimator.start();
-        }
-    }
-
-    /**
-     * Figures out the initial heights for allDay events and space when
-     * a view is being set up.
-     */
-    public void initAllDayHeights() {
-        if (mMaxAlldayEvents <= mMaxUnexpandedAlldayEventCount) {
-            return;
-        }
-        if (mShowAllAllDayEvents) {
-            int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
-            maxADHeight = Math.min(maxADHeight,
-                    (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
-            mAnimateDayEventHeight = maxADHeight / mMaxAlldayEvents;
-        } else {
-            mAnimateDayEventHeight = (int)MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-        }
-    }
-
-    // Sets up an animator for changing the height of allday events
-    private ObjectAnimator getAllDayEventAnimator() {
-        // First calculate the absolute max height
-        int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
-        // Now expand to fit but not beyond the absolute max
-        maxADHeight =
-                Math.min(maxADHeight, (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
-        // calculate the height of individual events in order to fit
-        int fitHeight = maxADHeight / mMaxAlldayEvents;
-        int currentHeight = mAnimateDayEventHeight;
-        int desiredHeight =
-                mShowAllAllDayEvents ? fitHeight : (int)MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT;
-        // if there's nothing to animate just return
-        if (currentHeight == desiredHeight) {
-            return null;
-        }
-
-        // Set up the animator with the calculated values
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, "animateDayEventHeight",
-                currentHeight, desiredHeight);
-        animator.setDuration(ANIMATION_DURATION);
-        return animator;
-    }
-
-    // Sets up an animator for changing the height of the allday area
-    private ObjectAnimator getAllDayAnimator() {
-        // Calculate the absolute max height
-        int maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT;
-        // Find the desired height but don't exceed abs max
-        maxADHeight =
-                Math.min(maxADHeight, (int)(mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT));
-        // calculate the current and desired heights
-        int currentHeight = mAnimateDayHeight != 0 ? mAnimateDayHeight : mAlldayHeight;
-        int desiredHeight = mShowAllAllDayEvents ? maxADHeight :
-                (int) (MAX_UNEXPANDED_ALLDAY_HEIGHT - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - 1);
-
-        // Set up the animator with the calculated values
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, "animateDayHeight",
-                currentHeight, desiredHeight);
-        animator.setDuration(ANIMATION_DURATION);
-
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!mCancellingAnimations) {
-                    // when finished, set this to 0 to signify not animating
-                    mAnimateDayHeight = 0;
-                    mUseExpandIcon = !mShowAllAllDayEvents;
-                }
-                mRemeasure = true;
-                invalidate();
-            }
-        });
-        return animator;
-    }
-
-    // setter for the 'box +n' alpha text used by the animator
-    public void setMoreAllDayEventsTextAlpha(int alpha) {
-        mMoreAlldayEventsTextAlpha = alpha;
-        invalidate();
-    }
-
-    // setter for the height of the allday area used by the animator
-    public void setAnimateDayHeight(int height) {
-        mAnimateDayHeight = height;
-        mRemeasure = true;
-        invalidate();
-    }
-
-    // setter for the height of allday events used by the animator
-    public void setAnimateDayEventHeight(int height) {
-        mAnimateDayEventHeight = height;
-        mRemeasure = true;
-        invalidate();
-    }
-
-    private void doSingleTapUp(MotionEvent ev) {
-        if (!mHandleActionUp || mScrolling) {
-            return;
-        }
-
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-        int selectedDay = mSelectionDay;
-        int selectedHour = mSelectionHour;
-
-        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
-            // check if the tap was in the allday expansion area
-            int bottom = mFirstCell;
-            if((x < mHoursWidth && y > DAY_HEADER_HEIGHT && y < DAY_HEADER_HEIGHT + mAlldayHeight)
-                    || (!mShowAllAllDayEvents && mAnimateDayHeight == 0 && y < bottom &&
-                            y >= bottom - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT)) {
-                doExpandAllDayClick();
-                return;
-            }
-        }
-
-        boolean validPosition = setSelectionFromPosition(x, y, false);
-        if (!validPosition) {
-            if (y < DAY_HEADER_HEIGHT) {
-                Time selectedTime = new Time(mBaseDate);
-                selectedTime.setJulianDay(mSelectionDay);
-                selectedTime.hour = mSelectionHour;
-                selectedTime.normalize(true /* ignore isDst */);
-                mController.sendEvent(this, EventType.GO_TO, null, null, selectedTime, -1,
-                        ViewType.DAY, CalendarController.EXTRA_GOTO_DATE, null, null);
-            }
-            return;
-        }
-
-        boolean hasSelection = mSelectionMode != SELECTION_HIDDEN;
-        boolean pressedSelected = (hasSelection || mTouchExplorationEnabled)
-                && selectedDay == mSelectionDay && selectedHour == mSelectionHour;
-
-        if (mSelectedEvent != null) {
-            // If the tap is on an event, launch the "View event" view
-            if (mIsAccessibilityEnabled) {
-                mAccessibilityMgr.interrupt();
-            }
-
-            mSelectionMode = SELECTION_HIDDEN;
-
-            int yLocation =
-                (int)((mSelectedEvent.top + mSelectedEvent.bottom)/2);
-            // Y location is affected by the position of the event in the scrolling
-            // view (mViewStartY) and the presence of all day events (mFirstCell)
-            if (!mSelectedEvent.allDay) {
-                yLocation += (mFirstCell - mViewStartY);
-            }
-            mClickedYLocation = yLocation;
-            long clearDelay = (CLICK_DISPLAY_DURATION + mOnDownDelay) -
-                    (System.currentTimeMillis() - mDownTouchTime);
-            if (clearDelay > 0) {
-                this.postDelayed(mClearClick, clearDelay);
-            } else {
-                this.post(mClearClick);
-            }
-        }
-        invalidate();
-    }
-
-    private void doLongPress(MotionEvent ev) {
-        eventClickCleanup();
-        if (mScrolling) {
-            return;
-        }
-
-        // Scale gesture in progress
-        if (mStartingSpanY != 0) {
-            return;
-        }
-
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        boolean validPosition = setSelectionFromPosition(x, y, false);
-        if (!validPosition) {
-            // return if the touch wasn't on an area of concern
-            return;
-        }
-
-        invalidate();
-        performLongClick();
-    }
-
-    private void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
-        cancelAnimation();
-        if (mStartingScroll) {
-            mInitialScrollX = 0;
-            mInitialScrollY = 0;
-            mStartingScroll = false;
-        }
-
-        mInitialScrollX += deltaX;
-        mInitialScrollY += deltaY;
-        int distanceX = (int) mInitialScrollX;
-        int distanceY = (int) mInitialScrollY;
-
-        final float focusY = getAverageY(e2);
-        if (mRecalCenterHour) {
-            // Calculate the hour that correspond to the average of the Y touch points
-            mGestureCenterHour = (mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight)
-                    / (mCellHeight + DAY_GAP);
-            mRecalCenterHour = false;
-        }
-
-        // If we haven't figured out the predominant scroll direction yet,
-        // then do it now.
-        if (mTouchMode == TOUCH_MODE_DOWN) {
-            int absDistanceX = Math.abs(distanceX);
-            int absDistanceY = Math.abs(distanceY);
-            mScrollStartY = mViewStartY;
-            mPreviousDirection = 0;
-
-            if (absDistanceX > absDistanceY) {
-                int slopFactor = mScaleGestureDetector.isInProgress() ? 20 : 2;
-                if (absDistanceX > mScaledPagingTouchSlop * slopFactor) {
-                    mTouchMode = TOUCH_MODE_HSCROLL;
-                    mViewStartX = distanceX;
-                    initNextView(-mViewStartX);
-                }
-            } else {
-                mTouchMode = TOUCH_MODE_VSCROLL;
-            }
-        } else if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            // We are already scrolling horizontally, so check if we
-            // changed the direction of scrolling so that the other week
-            // is now visible.
-            mViewStartX = distanceX;
-            if (distanceX != 0) {
-                int direction = (distanceX > 0) ? 1 : -1;
-                if (direction != mPreviousDirection) {
-                    // The user has switched the direction of scrolling
-                    // so re-init the next view
-                    initNextView(-mViewStartX);
-                    mPreviousDirection = direction;
-                }
-            }
-        }
-
-        if ((mTouchMode & TOUCH_MODE_VSCROLL) != 0) {
-            // Calculate the top of the visible region in the calendar grid.
-            // Increasing/decrease this will scroll the calendar grid up/down.
-            mViewStartY = (int) ((mGestureCenterHour * (mCellHeight + DAY_GAP))
-                    - focusY + DAY_HEADER_HEIGHT + mAlldayHeight);
-
-            // If dragging while already at the end, do a glow
-            final int pulledToY = (int) (mScrollStartY + deltaY);
-            if (pulledToY < 0) {
-                mEdgeEffectTop.onPull(deltaY / mViewHeight);
-                if (!mEdgeEffectBottom.isFinished()) {
-                    mEdgeEffectBottom.onRelease();
-                }
-            } else if (pulledToY > mMaxViewStartY) {
-                mEdgeEffectBottom.onPull(deltaY / mViewHeight);
-                if (!mEdgeEffectTop.isFinished()) {
-                    mEdgeEffectTop.onRelease();
-                }
-            }
-
-            if (mViewStartY < 0) {
-                mViewStartY = 0;
-                mRecalCenterHour = true;
-            } else if (mViewStartY > mMaxViewStartY) {
-                mViewStartY = mMaxViewStartY;
-                mRecalCenterHour = true;
-            }
-            if (mRecalCenterHour) {
-                // Calculate the hour that correspond to the average of the Y touch points
-                mGestureCenterHour = (mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight)
-                        / (mCellHeight + DAY_GAP);
-                mRecalCenterHour = false;
-            }
-            computeFirstHour();
-        }
-
-        mScrolling = true;
-
-        mSelectionMode = SELECTION_HIDDEN;
-        invalidate();
-    }
-
-    private float getAverageY(MotionEvent me) {
-        int count = me.getPointerCount();
-        float focusY = 0;
-        for (int i = 0; i < count; i++) {
-            focusY += me.getY(i);
-        }
-        focusY /= count;
-        return focusY;
-    }
-
-    private void cancelAnimation() {
-        Animation in = mViewSwitcher.getInAnimation();
-        if (in != null) {
-            // cancel() doesn't terminate cleanly.
-            in.scaleCurrentDuration(0);
-        }
-        Animation out = mViewSwitcher.getOutAnimation();
-        if (out != null) {
-            // cancel() doesn't terminate cleanly.
-            out.scaleCurrentDuration(0);
-        }
-    }
-
-    private void doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        cancelAnimation();
-
-        mSelectionMode = SELECTION_HIDDEN;
-        eventClickCleanup();
-
-        mOnFlingCalled = true;
-
-        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            // Horizontal fling.
-            // initNextView(deltaX);
-            mTouchMode = TOUCH_MODE_INITIAL_STATE;
-            if (DEBUG) Log.d(TAG, "doFling: velocityX " + velocityX);
-            int deltaX = (int) e2.getX() - (int) e1.getX();
-            switchViews(deltaX < 0, mViewStartX, mViewWidth, velocityX);
-            mViewStartX = 0;
-            return;
-        }
-
-        if ((mTouchMode & TOUCH_MODE_VSCROLL) == 0) {
-            if (DEBUG) Log.d(TAG, "doFling: no fling");
-            return;
-        }
-
-        // Vertical fling.
-        mTouchMode = TOUCH_MODE_INITIAL_STATE;
-        mViewStartX = 0;
-
-        if (DEBUG) {
-            Log.d(TAG, "doFling: mViewStartY" + mViewStartY + " velocityY " + velocityY);
-        }
-
-        // Continue scrolling vertically
-        mScrolling = true;
-        mScroller.fling(0 /* startX */, mViewStartY /* startY */, 0 /* velocityX */,
-                (int) -velocityY, 0 /* minX */, 0 /* maxX */, 0 /* minY */,
-                mMaxViewStartY /* maxY */, OVERFLING_DISTANCE, OVERFLING_DISTANCE);
-
-        // When flinging down, show a glow when it hits the end only if it
-        // wasn't started at the top
-        if (velocityY > 0 && mViewStartY != 0) {
-            mCallEdgeEffectOnAbsorb = true;
-        }
-        // When flinging up, show a glow when it hits the end only if it wasn't
-        // started at the bottom
-        else if (velocityY < 0 && mViewStartY != mMaxViewStartY) {
-            mCallEdgeEffectOnAbsorb = true;
-        }
-        mHandler.post(mContinueScroll);
-    }
-
-    private boolean initNextView(int deltaX) {
-        // Change the view to the previous day or week
-        DayView view = (DayView) mViewSwitcher.getNextView();
-        Time date = view.mBaseDate;
-        date.set(mBaseDate);
-        boolean switchForward;
-        if (deltaX > 0) {
-            date.monthDay -= mNumDays;
-            view.setSelectedDay(mSelectionDay - mNumDays);
-            switchForward = false;
-        } else {
-            date.monthDay += mNumDays;
-            view.setSelectedDay(mSelectionDay + mNumDays);
-            switchForward = true;
-        }
-        date.normalize(true /* ignore isDst */);
-        initView(view);
-        view.layout(getLeft(), getTop(), getRight(), getBottom());
-        view.reloadEvents();
-        return switchForward;
-    }
-
-    // ScaleGestureDetector.OnScaleGestureListener
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        mHandleActionUp = false;
-        float gestureCenterInPixels = detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight;
-        mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP);
-
-        mStartingSpanY = Math.max(MIN_Y_SPAN, Math.abs(detector.getCurrentSpanY()));
-        mCellHeightBeforeScaleGesture = mCellHeight;
-
-        if (DEBUG_SCALING) {
-            float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
-            Log.d(TAG, "onScaleBegin: mGestureCenterHour:" + mGestureCenterHour
-                    + "\tViewStartHour: " + ViewStartHour + "\tmViewStartY:" + mViewStartY
-                    + "\tmCellHeight:" + mCellHeight + " SpanY:" + detector.getCurrentSpanY());
-        }
-
-        return true;
-    }
-
-    // ScaleGestureDetector.OnScaleGestureListener
-    public boolean onScale(ScaleGestureDetector detector) {
-        float spanY = Math.max(MIN_Y_SPAN, Math.abs(detector.getCurrentSpanY()));
-
-        mCellHeight = (int) (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY);
-
-        if (mCellHeight < mMinCellHeight) {
-            // If mStartingSpanY is too small, even a small increase in the
-            // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
-            mStartingSpanY = spanY;
-            mCellHeight = mMinCellHeight;
-            mCellHeightBeforeScaleGesture = mMinCellHeight;
-        } else if (mCellHeight > MAX_CELL_HEIGHT) {
-            mStartingSpanY = spanY;
-            mCellHeight = MAX_CELL_HEIGHT;
-            mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT;
-        }
-
-        int gestureCenterInPixels = (int) detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight;
-        mViewStartY = (int) (mGestureCenterHour * (mCellHeight + DAY_GAP)) - gestureCenterInPixels;
-        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
-
-        if (DEBUG_SCALING) {
-            float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
-            Log.d(TAG, "onScale: mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: "
-                    + ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:"
-                    + mCellHeight + " SpanY:" + detector.getCurrentSpanY());
-        }
-
-        if (mViewStartY < 0) {
-            mViewStartY = 0;
-            mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
-                    / (float) (mCellHeight + DAY_GAP);
-        } else if (mViewStartY > mMaxViewStartY) {
-            mViewStartY = mMaxViewStartY;
-            mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
-                    / (float) (mCellHeight + DAY_GAP);
-        }
-        computeFirstHour();
-
-        mRemeasure = true;
-        invalidate();
-        return true;
-    }
-
-    // ScaleGestureDetector.OnScaleGestureListener
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        mScrollStartY = mViewStartY;
-        mInitialScrollY = 0;
-        mInitialScrollX = 0;
-        mStartingSpanY = 0;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        int action = ev.getAction();
-        if (DEBUG) Log.e(TAG, "" + action + " ev.getPointerCount() = " + ev.getPointerCount());
-
-        if ((ev.getActionMasked() == MotionEvent.ACTION_DOWN) ||
-                (ev.getActionMasked() == MotionEvent.ACTION_UP) ||
-                (ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) ||
-                (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN)) {
-            mRecalCenterHour = true;
-        }
-
-        if ((mTouchMode & TOUCH_MODE_HSCROLL) == 0) {
-            mScaleGestureDetector.onTouchEvent(ev);
-        }
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                mStartingScroll = true;
-                if (DEBUG) {
-                    Log.e(TAG, "ACTION_DOWN ev.getDownTime = " + ev.getDownTime() + " Cnt="
-                            + ev.getPointerCount());
-                }
-
-                int bottom = mAlldayHeight + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
-                if (ev.getY() < bottom) {
-                    mTouchStartedInAlldayArea = true;
-                } else {
-                    mTouchStartedInAlldayArea = false;
-                }
-                mHandleActionUp = true;
-                mGestureDetector.onTouchEvent(ev);
-                return true;
-
-            case MotionEvent.ACTION_MOVE:
-                if (DEBUG) Log.e(TAG, "ACTION_MOVE Cnt=" + ev.getPointerCount() + DayView.this);
-                mGestureDetector.onTouchEvent(ev);
-                return true;
-
-            case MotionEvent.ACTION_UP:
-                if (DEBUG) Log.e(TAG, "ACTION_UP Cnt=" + ev.getPointerCount() + mHandleActionUp);
-                mEdgeEffectTop.onRelease();
-                mEdgeEffectBottom.onRelease();
-                mStartingScroll = false;
-                mGestureDetector.onTouchEvent(ev);
-                if (!mHandleActionUp) {
-                    mHandleActionUp = true;
-                    mViewStartX = 0;
-                    invalidate();
-                    return true;
-                }
-
-                if (mOnFlingCalled) {
-                    return true;
-                }
-
-                // If we were scrolling, then reset the selected hour so that it
-                // is visible.
-                if (mScrolling) {
-                    mScrolling = false;
-                    resetSelectedHour();
-                    invalidate();
-                }
-
-                if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-                    mTouchMode = TOUCH_MODE_INITIAL_STATE;
-                    if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
-                        // The user has gone beyond the threshold so switch views
-                        if (DEBUG) Log.d(TAG, "- horizontal scroll: switch views");
-                        switchViews(mViewStartX > 0, mViewStartX, mViewWidth, 0);
-                        mViewStartX = 0;
-                        return true;
-                    } else {
-                        // Not beyond the threshold so invalidate which will cause
-                        // the view to snap back. Also call recalc() to ensure
-                        // that we have the correct starting date and title.
-                        if (DEBUG) Log.d(TAG, "- horizontal scroll: snap back");
-                        recalc();
-                        invalidate();
-                        mViewStartX = 0;
-                    }
-                }
-
-                return true;
-
-                // This case isn't expected to happen.
-            case MotionEvent.ACTION_CANCEL:
-                if (DEBUG) Log.e(TAG, "ACTION_CANCEL");
-                mGestureDetector.onTouchEvent(ev);
-                mScrolling = false;
-                resetSelectedHour();
-                return true;
-
-            default:
-                if (DEBUG) Log.e(TAG, "Not MotionEvent " + ev.toString());
-                if (mGestureDetector.onTouchEvent(ev)) {
-                    return true;
-                }
-                return super.onTouchEvent(ev);
-        }
-    }
-
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        MenuItem item;
-
-        // If the trackball is held down, then the context menu pops up and
-        // we never get onKeyUp() for the long-press. So check for it here
-        // and change the selection to the long-press state.
-        if (mSelectionMode != SELECTION_LONGPRESS) {
-            invalidate();
-        }
-
-        final long startMillis = getSelectedTimeInMillis();
-        int flags = DateUtils.FORMAT_SHOW_TIME
-                | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
-                | DateUtils.FORMAT_SHOW_WEEKDAY;
-        final String title = Utils.formatDateRange(mContext, startMillis, startMillis, flags);
-        menu.setHeaderTitle(title);
-
-        mPopup.dismiss();
-    }
-
-    /**
-     * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
-     * If the touch position is not within the displayed grid, then this
-     * method returns false.
-     *
-     * @param x the x position of the touch
-     * @param y the y position of the touch
-     * @param keepOldSelection - do not change the selection info (used for invoking accessibility
-     *                           messages)
-     * @return true if the touch position is valid
-     */
-    private boolean setSelectionFromPosition(int x, final int y, boolean keepOldSelection) {
-
-        Event savedEvent = null;
-        int savedDay = 0;
-        int savedHour = 0;
-        boolean savedAllDay = false;
-        if (keepOldSelection) {
-            // Store selection info and restore it at the end. This way, we can invoke the
-            // right accessibility message without affecting the selection.
-            savedEvent = mSelectedEvent;
-            savedDay = mSelectionDay;
-            savedHour = mSelectionHour;
-            savedAllDay = mSelectionAllday;
-        }
-        if (x < mHoursWidth) {
-            x = mHoursWidth;
-        }
-
-        int day = (x - mHoursWidth) / (mCellWidth + DAY_GAP);
-        if (day >= mNumDays) {
-            day = mNumDays - 1;
-        }
-        day += mFirstJulianDay;
-        setSelectedDay(day);
-
-        if (y < DAY_HEADER_HEIGHT) {
-            sendAccessibilityEventAsNeeded(false);
-            return false;
-        }
-
-        setSelectedHour(mFirstHour); /* First fully visible hour */
-
-        if (y < mFirstCell) {
-            mSelectionAllday = true;
-        } else {
-            // y is now offset from top of the scrollable region
-            int adjustedY = y - mFirstCell;
-
-            if (adjustedY < mFirstHourOffset) {
-                setSelectedHour(mSelectionHour - 1); /* In the partially visible hour */
-            } else {
-                setSelectedHour(mSelectionHour +
-                        (adjustedY - mFirstHourOffset) / (mCellHeight + HOUR_GAP));
-            }
-
-            mSelectionAllday = false;
-        }
-
-        findSelectedEvent(x, y);
-
-        sendAccessibilityEventAsNeeded(true);
-
-        // Restore old values
-        if (keepOldSelection) {
-            mSelectedEvent = savedEvent;
-            mSelectionDay = savedDay;
-            mSelectionHour = savedHour;
-            mSelectionAllday = savedAllDay;
-        }
-        return true;
-    }
-
-    private void findSelectedEvent(int x, int y) {
-        int date = mSelectionDay;
-        int cellWidth = mCellWidth;
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        int left = computeDayLeftPosition(mSelectionDay - mFirstJulianDay);
-        int top = 0;
-        setSelectedEvent(null);
-
-        mSelectedEvents.clear();
-        if (mSelectionAllday) {
-            float yDistance;
-            float minYdistance = 10000.0f; // any large number
-            Event closestEvent = null;
-            float drawHeight = mAlldayHeight;
-            int yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
-            int maxUnexpandedColumn = mMaxUnexpandedAlldayEventCount;
-            if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
-                // Leave a gap for the 'box +n' text
-                maxUnexpandedColumn--;
-            }
-            events = mAllDayEvents;
-            numEvents = events.size();
-            for (int i = 0; i < numEvents; i++) {
-                Event event = events.get(i);
-                if (!event.drawAsAllday() ||
-                        (!mShowAllAllDayEvents && event.getColumn() >= maxUnexpandedColumn)) {
-                    // Don't check non-allday events or events that aren't shown
-                    continue;
-                }
-
-                if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
-                    float numRectangles = mShowAllAllDayEvents ? mMaxAlldayEvents
-                            : mMaxUnexpandedAlldayEventCount;
-                    float height = drawHeight / numRectangles;
-                    if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
-                        height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
-                    }
-                    float eventTop = yOffset + height * event.getColumn();
-                    float eventBottom = eventTop + height;
-                    if (eventTop < y && eventBottom > y) {
-                        // If the touch is inside the event rectangle, then
-                        // add the event.
-                        mSelectedEvents.add(event);
-                        closestEvent = event;
-                        break;
-                    } else {
-                        // Find the closest event
-                        if (eventTop >= y) {
-                            yDistance = eventTop - y;
-                        } else {
-                            yDistance = y - eventBottom;
-                        }
-                        if (yDistance < minYdistance) {
-                            minYdistance = yDistance;
-                            closestEvent = event;
-                        }
-                    }
-                }
-            }
-            setSelectedEvent(closestEvent);
-            return;
-        }
-
-        // Adjust y for the scrollable bitmap
-        y += mViewStartY - mFirstCell;
-
-        // Use a region around (x,y) for the selection region
-        Rect region = mRect;
-        region.left = x - 10;
-        region.right = x + 10;
-        region.top = y - 10;
-        region.bottom = y + 10;
-
-        EventGeometry geometry = mEventGeometry;
-
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            // Compute the event rectangle.
-            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
-                continue;
-            }
-
-            // If the event intersects the selection region, then add it to
-            // mSelectedEvents.
-            if (geometry.eventIntersectsSelection(event, region)) {
-                mSelectedEvents.add(event);
-            }
-        }
-
-        // If there are any events in the selected region, then assign the
-        // closest one to mSelectedEvent.
-        if (mSelectedEvents.size() > 0) {
-            int len = mSelectedEvents.size();
-            Event closestEvent = null;
-            float minDist = mViewWidth + mViewHeight; // some large distance
-            for (int index = 0; index < len; index++) {
-                Event ev = mSelectedEvents.get(index);
-                float dist = geometry.pointToEvent(x, y, ev);
-                if (dist < minDist) {
-                    minDist = dist;
-                    closestEvent = ev;
-                }
-            }
-            setSelectedEvent(closestEvent);
-
-            // Keep the selected hour and day consistent with the selected
-            // event. They could be different if we touched on an empty hour
-            // slot very close to an event in the previous hour slot. In
-            // that case we will select the nearby event.
-            int startDay = mSelectedEvent.startDay;
-            int endDay = mSelectedEvent.endDay;
-            if (mSelectionDay < startDay) {
-                setSelectedDay(startDay);
-            } else if (mSelectionDay > endDay) {
-                setSelectedDay(endDay);
-            }
-
-            int startHour = mSelectedEvent.startTime / 60;
-            int endHour;
-            if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
-                endHour = (mSelectedEvent.endTime - 1) / 60;
-            } else {
-                endHour = mSelectedEvent.endTime / 60;
-            }
-
-            if (mSelectionHour < startHour && mSelectionDay == startDay) {
-                setSelectedHour(startHour);
-            } else if (mSelectionHour > endHour && mSelectionDay == endDay) {
-                setSelectedHour(endHour);
-            }
-        }
-    }
-
-    // Encapsulates the code to continue the scrolling after the
-    // finger is lifted. Instead of stopping the scroll immediately,
-    // the scroll continues to "free spin" and gradually slows down.
-    private class ContinueScroll implements Runnable {
-
-        public void run() {
-            mScrolling = mScrolling && mScroller.computeScrollOffset();
-            if (!mScrolling || mPaused) {
-                resetSelectedHour();
-                invalidate();
-                return;
-            }
-
-            mViewStartY = mScroller.getCurrY();
-
-            if (mCallEdgeEffectOnAbsorb) {
-                if (mViewStartY < 0) {
-                    mEdgeEffectTop.onAbsorb((int) mLastVelocity);
-                    mCallEdgeEffectOnAbsorb = false;
-                } else if (mViewStartY > mMaxViewStartY) {
-                    mEdgeEffectBottom.onAbsorb((int) mLastVelocity);
-                    mCallEdgeEffectOnAbsorb = false;
-                }
-                mLastVelocity = mScroller.getCurrVelocity();
-            }
-
-            if (mScrollStartY == 0 || mScrollStartY == mMaxViewStartY) {
-                // Allow overscroll/springback only on a fling,
-                // not a pull/fling from the end
-                if (mViewStartY < 0) {
-                    mViewStartY = 0;
-                } else if (mViewStartY > mMaxViewStartY) {
-                    mViewStartY = mMaxViewStartY;
-                }
-            }
-
-            computeFirstHour();
-            mHandler.post(this);
-            invalidate();
-        }
-    }
-
-    /**
-     * Cleanup the pop-up and timers.
-     */
-    public void cleanup() {
-        // Protect against null-pointer exceptions
-        if (mPopup != null) {
-            mPopup.dismiss();
-        }
-        mPaused = true;
-        mLastPopupEventID = INVALID_EVENT_ID;
-        if (mHandler != null) {
-            mHandler.removeCallbacks(mDismissPopup);
-            mHandler.removeCallbacks(mUpdateCurrentTime);
-        }
-
-        Utils.setSharedPreference(mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
-            mCellHeight);
-        // Clear all click animations
-        eventClickCleanup();
-        // Turn off redraw
-        mRemeasure = false;
-        // Turn off scrolling to make sure the view is in the correct state if we fling back to it
-        mScrolling = false;
-    }
-
-    private void eventClickCleanup() {
-        this.removeCallbacks(mClearClick);
-        this.removeCallbacks(mSetClick);
-        mClickedEvent = null;
-        mSavedClickedEvent = null;
-    }
-
-    private void setSelectedEvent(Event e) {
-        mSelectedEvent = e;
-        mSelectedEventForAccessibility = e;
-    }
-
-    private void setSelectedHour(int h) {
-        mSelectionHour = h;
-        mSelectionHourForAccessibility = h;
-    }
-    private void setSelectedDay(int d) {
-        mSelectionDay = d;
-        mSelectionDayForAccessibility = d;
-    }
-
-    /**
-     * Restart the update timer
-     */
-    public void restartCurrentTimeUpdates() {
-        mPaused = false;
-        if (mHandler != null) {
-            mHandler.removeCallbacks(mUpdateCurrentTime);
-            mHandler.post(mUpdateCurrentTime);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        cleanup();
-        super.onDetachedFromWindow();
-    }
-
-    class DismissPopup implements Runnable {
-
-        public void run() {
-            // Protect against null-pointer exceptions
-            if (mPopup != null) {
-                mPopup.dismiss();
-            }
-        }
-    }
-
-    class UpdateCurrentTime implements Runnable {
-
-        public void run() {
-            long currentTime = System.currentTimeMillis();
-            mCurrentTime.set(currentTime);
-            //% causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
-            if (!DayView.this.mPaused) {
-                mHandler.postDelayed(mUpdateCurrentTime, UPDATE_CURRENT_TIME_DELAY
-                        - (currentTime % UPDATE_CURRENT_TIME_DELAY));
-            }
-            mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
-            invalidate();
-        }
-    }
-
-    class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
-        @Override
-        public boolean onSingleTapUp(MotionEvent ev) {
-            if (DEBUG) Log.e(TAG, "GestureDetector.onSingleTapUp");
-            DayView.this.doSingleTapUp(ev);
-            return true;
-        }
-
-        @Override
-        public void onLongPress(MotionEvent ev) {
-            if (DEBUG) Log.e(TAG, "GestureDetector.onLongPress");
-            DayView.this.doLongPress(ev);
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-            if (DEBUG) Log.e(TAG, "GestureDetector.onScroll");
-            eventClickCleanup();
-            if (mTouchStartedInAlldayArea) {
-                if (Math.abs(distanceX) < Math.abs(distanceY)) {
-                    // Make sure that click feedback is gone when you scroll from the
-                    // all day area
-                    invalidate();
-                    return false;
-                }
-                // don't scroll vertically if this started in the allday area
-                distanceY = 0;
-            }
-            DayView.this.doScroll(e1, e2, distanceX, distanceY);
-            return true;
-        }
-
-        @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            if (DEBUG) Log.e(TAG, "GestureDetector.onFling");
-
-            if (mTouchStartedInAlldayArea) {
-                if (Math.abs(velocityX) < Math.abs(velocityY)) {
-                    return false;
-                }
-                // don't fling vertically if this started in the allday area
-                velocityY = 0;
-            }
-            DayView.this.doFling(e1, e2, velocityX, velocityY);
-            return true;
-        }
-
-        @Override
-        public boolean onDown(MotionEvent ev) {
-            if (DEBUG) Log.e(TAG, "GestureDetector.onDown");
-            DayView.this.doDown(ev);
-            return true;
-        }
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        return true;
-    }
-
-    // The rest of this file was borrowed from Launcher2 - PagedView.java
-    private static final int MINIMUM_SNAP_VELOCITY = 2200;
-
-    private class ScrollInterpolator implements Interpolator {
-        public ScrollInterpolator() {
-        }
-
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            t = t * t * t * t * t + 1;
-
-            if ((1 - t) * mAnimationDistance < 1) {
-                cancelAnimation();
-            }
-
-            return t;
-        }
-    }
-
-    private long calculateDuration(float delta, float width, float velocity) {
-        /*
-         * Here we compute a "distance" that will be used in the computation of
-         * the overall snap duration. This is a function of the actual distance
-         * that needs to be traveled; we keep this value close to half screen
-         * size in order to reduce the variance in snap duration as a function
-         * of the distance the page needs to travel.
-         */
-        final float halfScreenSize = width / 2;
-        float distanceRatio = delta / width;
-        float distanceInfluenceForSnapDuration = distanceInfluenceForSnapDuration(distanceRatio);
-        float distance = halfScreenSize + halfScreenSize * distanceInfluenceForSnapDuration;
-
-        velocity = Math.abs(velocity);
-        velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
-
-        /*
-         * we want the page's snap velocity to approximately match the velocity
-         * at which the user flings, so we scale the duration by a value near to
-         * the derivative of the scroll interpolator at zero, ie. 5. We use 6 to
-         * make it a little slower.
-         */
-        long duration = 6 * Math.round(1000 * Math.abs(distance / velocity));
-        if (DEBUG) {
-            Log.e(TAG, "halfScreenSize:" + halfScreenSize + " delta:" + delta + " distanceRatio:"
-                    + distanceRatio + " distance:" + distance + " velocity:" + velocity
-                    + " duration:" + duration + " distanceInfluenceForSnapDuration:"
-                    + distanceInfluenceForSnapDuration);
-        }
-        return duration;
-    }
-
-    /*
-     * We want the duration of the page snap animation to be influenced by the
-     * distance that the screen has to travel, however, we don't want this
-     * duration to be effected in a purely linear fashion. Instead, we use this
-     * method to moderate the effect that the distance of travel has on the
-     * overall snap duration.
-     */
-    private float distanceInfluenceForSnapDuration(float f) {
-        f -= 0.5f; // center the values about 0.
-        f *= 0.3f * Math.PI / 2.0f;
-        return (float) Math.sin(f);
-    }
-}
diff --git a/src/com/android/calendar/DayView.kt b/src/com/android/calendar/DayView.kt
new file mode 100644
index 0000000..58126f2
--- /dev/null
+++ b/src/com/android/calendar/DayView.kt
@@ -0,0 +1,3990 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.app.Service
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.text.Layout.Alignment
+import android.text.SpannableStringBuilder
+import android.text.StaticLayout
+import android.text.TextPaint
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.text.style.StyleSpan
+import android.util.Log
+import android.view.ContextMenu
+import android.view.ContextMenu.ContextMenuInfo
+import android.view.GestureDetector
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.view.animation.Animation
+import android.view.animation.Interpolator
+import android.view.animation.TranslateAnimation
+import android.widget.EdgeEffect
+import android.widget.OverScroller
+import android.widget.PopupWindow
+import android.widget.ViewSwitcher
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Calendar
+import java.util.Formatter
+import java.util.Locale
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+/**
+ * View for multi-day view. So far only 1 and 7 day have been tested.
+ */
+class DayView(
+    context: Context?,
+    controller: CalendarController?,
+    viewSwitcher: ViewSwitcher?,
+    eventLoader: EventLoader?,
+    numDays: Int
+) : View(context), View.OnCreateContextMenuListener, ScaleGestureDetector.OnScaleGestureListener,
+    View.OnClickListener, View.OnLongClickListener {
+    private var mOnFlingCalled = false
+    private var mStartingScroll = false
+    protected var mPaused = true
+    private var mHandler: Handler? = null
+
+    /**
+     * ID of the last event which was displayed with the toast popup.
+     *
+     * This is used to prevent popping up multiple quick views for the same event, especially
+     * during calendar syncs. This becomes valid when an event is selected, either by default
+     * on starting calendar or by scrolling to an event. It becomes invalid when the user
+     * explicitly scrolls to an empty time slot, changes views, or deletes the event.
+     */
+    private var mLastPopupEventID: Long
+    protected var mContext: Context? = null
+    private val mContinueScroll: ContinueScroll = ContinueScroll()
+
+    // Make this visible within the package for more informative debugging
+    var mBaseDate: Time? = null
+    private var mCurrentTime: Time? = null
+    private val mUpdateCurrentTime: UpdateCurrentTime = UpdateCurrentTime()
+    private var mTodayJulianDay = 0
+    private val mBold: Typeface = Typeface.DEFAULT_BOLD
+    private var mFirstJulianDay = 0
+    private var mLoadedFirstJulianDay = -1
+    private var mLastJulianDay = 0
+    private var mMonthLength = 0
+    private var mFirstVisibleDate = 0
+    private var mFirstVisibleDayOfWeek = 0
+    private var mEarliestStartHour: IntArray? = null // indexed by the week day offset
+    private var mHasAllDayEvent: BooleanArray? = null // indexed by the week day offset
+    private var mEventCountTemplate: String? = null
+    private var mClickedEvent: Event? = null // The event the user clicked on
+    private var mSavedClickedEvent: Event? = null
+    private var mClickedYLocation = 0
+    private var mDownTouchTime: Long = 0
+    private var mEventsAlpha = 255
+    private var mEventsCrossFadeAnimation: ObjectAnimator? = null
+    private val mTZUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            val tz: String? = Utils.getTimeZone(mContext, this)
+            mBaseDate!!.timezone = tz
+            mBaseDate?.normalize(true)
+            mCurrentTime?.switchTimezone(tz)
+            invalidate()
+        }
+    }
+
+    // Sets the "clicked" color from the clicked event
+    private val mSetClick: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            mClickedEvent = mSavedClickedEvent
+            mSavedClickedEvent = null
+            this@DayView.invalidate()
+        }
+    }
+
+    // Clears the "clicked" color from the clicked event and launch the event
+    private val mClearClick: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            if (mClickedEvent != null) {
+                mController?.sendEventRelatedEvent(
+                    this as Object?, EventType.VIEW_EVENT, mClickedEvent!!.id,
+                    mClickedEvent!!.startMillis, mClickedEvent!!.endMillis,
+                    this@DayView.getWidth() / 2, mClickedYLocation,
+                    selectedTimeInMillis
+                )
+            }
+            mClickedEvent = null
+            this@DayView.invalidate()
+        }
+    }
+    private val mTodayAnimatorListener: TodayAnimatorListener = TodayAnimatorListener()
+
+    internal inner class TodayAnimatorListener : AnimatorListenerAdapter() {
+        @Volatile
+        private var mAnimator: Animator? = null
+
+        @Volatile
+        private var mFadingIn = false
+        @Override
+        override fun onAnimationEnd(animation: Animator) {
+            synchronized(this) {
+                if (mAnimator !== animation) {
+                    animation.removeAllListeners()
+                    animation.cancel()
+                    return
+                }
+                if (mFadingIn) {
+                    if (mTodayAnimator != null) {
+                        mTodayAnimator?.removeAllListeners()
+                        mTodayAnimator?.cancel()
+                    }
+                    mTodayAnimator = ObjectAnimator
+                        .ofInt(this@DayView, "animateTodayAlpha", 255, 0)
+                    mAnimator = mTodayAnimator
+                    mFadingIn = false
+                    mTodayAnimator?.addListener(this)
+                    mTodayAnimator?.setDuration(600)
+                    mTodayAnimator?.start()
+                } else {
+                    mAnimateToday = false
+                    mAnimateTodayAlpha = 0
+                    mAnimator?.removeAllListeners()
+                    mAnimator = null
+                    mTodayAnimator = null
+                    invalidate()
+                }
+            }
+        }
+
+        fun setAnimator(animation: Animator?) {
+            mAnimator = animation
+        }
+
+        fun setFadingIn(fadingIn: Boolean) {
+            mFadingIn = fadingIn
+        }
+    }
+
+    var mAnimatorListener: AnimatorListenerAdapter = object : AnimatorListenerAdapter() {
+        @Override
+        override fun onAnimationStart(animation: Animator?) {
+            mScrolling = true
+        }
+
+        @Override
+        override fun onAnimationCancel(animation: Animator?) {
+            mScrolling = false
+        }
+
+        @Override
+        override fun onAnimationEnd(animation: Animator?) {
+            mScrolling = false
+            resetSelectedHour()
+            invalidate()
+        }
+    }
+
+    /**
+     * This variable helps to avoid unnecessarily reloading events by keeping
+     * track of the start millis parameter used for the most recent loading
+     * of events.  If the next reload matches this, then the events are not
+     * reloaded.  To force a reload, set this to zero (this is set to zero
+     * in the method clearCachedEvents()).
+     */
+    private var mLastReloadMillis: Long = 0
+    private var mEvents: ArrayList<Event> = ArrayList<Event>()
+    private var mAllDayEvents: ArrayList<Event>? = ArrayList<Event>()
+    private var mLayouts: Array<StaticLayout?>? = null
+    private var mAllDayLayouts: Array<StaticLayout?>? = null
+    private var mSelectionDay = 0 // Julian day
+    private var mSelectionHour = 0
+    var mSelectionAllday = false
+
+    // Current selection info for accessibility
+    private var mSelectionDayForAccessibility = 0 // Julian day
+    private var mSelectionHourForAccessibility = 0
+    private var mSelectedEventForAccessibility: Event? = null
+
+    // Last selection info for accessibility
+    private var mLastSelectionDayForAccessibility = 0
+    private var mLastSelectionHourForAccessibility = 0
+    private var mLastSelectedEventForAccessibility: Event? = null
+
+    /** Width of a day or non-conflicting event  */
+    private var mCellWidth = 0
+
+    // Pre-allocate these objects and re-use them
+    private val mRect: Rect = Rect()
+    private val mDestRect: Rect = Rect()
+    private val mSelectionRect: Rect = Rect()
+
+    // This encloses the more allDay events icon
+    private val mExpandAllDayRect: Rect = Rect()
+
+    // TODO Clean up paint usage
+    private val mPaint: Paint = Paint()
+    private val mEventTextPaint: Paint = Paint()
+    private val mSelectionPaint: Paint = Paint()
+    private var mLines: FloatArray = emptyArray<Float>().toFloatArray()
+    private var mFirstDayOfWeek = 0 // First day of the week
+    private var mPopup: PopupWindow? = null
+    private var mPopupView: View? = null
+    private val mDismissPopup: DismissPopup = DismissPopup()
+    private var mRemeasure = true
+    private val mEventLoader: EventLoader
+    protected val mEventGeometry: EventGeometry
+    private var mAnimationDistance = 0f
+    private var mViewStartX = 0
+    private var mViewStartY = 0
+    private var mMaxViewStartY = 0
+    private var mViewHeight = 0
+    private var mViewWidth = 0
+    private var mGridAreaHeight = -1
+    private var mScrollStartY = 0
+    private var mPreviousDirection = 0
+
+    /**
+     * Vertical distance or span between the two touch points at the start of a
+     * scaling gesture
+     */
+    private var mStartingSpanY = 0f
+
+    /** Height of 1 hour in pixels at the start of a scaling gesture  */
+    private var mCellHeightBeforeScaleGesture = 0
+
+    /** The hour at the center two touch points  */
+    private var mGestureCenterHour = 0f
+    private var mRecalCenterHour = false
+
+    /**
+     * Flag to decide whether to handle the up event. Cases where up events
+     * should be ignored are 1) right after a scale gesture and 2) finger was
+     * down before app launch
+     */
+    private var mHandleActionUp = true
+    private var mHoursTextHeight = 0
+
+    /**
+     * The height of the area used for allday events
+     */
+    private var mAlldayHeight = 0
+
+    /**
+     * The height of the allday event area used during animation
+     */
+    private var mAnimateDayHeight = 0
+
+    /**
+     * The height of an individual allday event during animation
+     */
+    private var mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+
+    /**
+     * Max of all day events in a given day in this view.
+     */
+    private var mMaxAlldayEvents = 0
+
+    /**
+     * A count of the number of allday events that were not drawn for each day
+     */
+    private var mSkippedAlldayEvents: IntArray? = null
+
+    /**
+     * The number of allDay events at which point we start hiding allDay events.
+     */
+    private var mMaxUnexpandedAlldayEventCount = 4
+    protected var mNumDays = 7
+    private var mNumHours = 10
+
+    /** Width of the time line (list of hours) to the left.  */
+    private var mHoursWidth = 0
+    private var mDateStrWidth = 0
+
+    /** Top of the scrollable region i.e. below date labels and all day events  */
+    private var mFirstCell = 0
+
+    /** First fully visible hour  */
+    private var mFirstHour = -1
+
+    /** Distance between the mFirstCell and the top of first fully visible hour.  */
+    private var mFirstHourOffset = 0
+    private var mHourStrs: Array<String>? = null
+    private var mDayStrs: Array<String?>? = null
+    private var mDayStrs2Letter: Array<String?>? = null
+    private var mIs24HourFormat = false
+    private val mSelectedEvents: ArrayList<Event> = ArrayList<Event>()
+    private var mComputeSelectedEvents = false
+    private var mUpdateToast = false
+    private var mSelectedEvent: Event? = null
+    private var mPrevSelectedEvent: Event? = null
+    private val mPrevBox: Rect = Rect()
+    protected val mResources: Resources
+    protected val mCurrentTimeLine: Drawable
+    protected val mCurrentTimeAnimateLine: Drawable
+    protected val mTodayHeaderDrawable: Drawable
+    protected val mExpandAlldayDrawable: Drawable
+    protected val mCollapseAlldayDrawable: Drawable
+    protected var mAcceptedOrTentativeEventBoxDrawable: Drawable
+    private var mAmString: String? = null
+    private var mPmString: String? = null
+    var mScaleGestureDetector: ScaleGestureDetector
+    private var mTouchMode = TOUCH_MODE_INITIAL_STATE
+    private var mSelectionMode = SELECTION_HIDDEN
+    private var mScrolling = false
+
+    // Pixels scrolled
+    private var mInitialScrollX = 0f
+    private var mInitialScrollY = 0f
+    private var mAnimateToday = false
+    private var mAnimateTodayAlpha = 0
+
+    // Animates the height of the allday region
+    var mAlldayAnimator: ObjectAnimator? = null
+
+    // Animates the height of events in the allday region
+    var mAlldayEventAnimator: ObjectAnimator? = null
+
+    // Animates the transparency of the more events text
+    var mMoreAlldayEventsAnimator: ObjectAnimator? = null
+
+    // Animates the current time marker when Today is pressed
+    var mTodayAnimator: ObjectAnimator? = null
+
+    // whether or not an event is stopping because it was cancelled
+    private var mCancellingAnimations = false
+
+    // tracks whether a touch originated in the allday area
+    private var mTouchStartedInAlldayArea = false
+    private val mController: CalendarController
+    private val mViewSwitcher: ViewSwitcher
+    private val mGestureDetector: GestureDetector
+    private val mScroller: OverScroller
+    private val mEdgeEffectTop: EdgeEffect
+    private val mEdgeEffectBottom: EdgeEffect
+    private var mCallEdgeEffectOnAbsorb = false
+    private val OVERFLING_DISTANCE: Int
+    private var mLastVelocity = 0f
+    private val mHScrollInterpolator: ScrollInterpolator
+    private var mAccessibilityMgr: AccessibilityManager? = null
+    private var mIsAccessibilityEnabled = false
+    private var mTouchExplorationEnabled = false
+    private val mNewEventHintString: String
+    @Override
+    protected override fun onAttachedToWindow() {
+        if (mHandler == null) {
+            mHandler = getHandler()
+            mHandler?.post(mUpdateCurrentTime)
+        }
+    }
+
+    private fun init(context: Context) {
+        setFocusable(true)
+
+        // Allow focus in touch mode so that we can do keyboard shortcuts
+        // even after we've entered touch mode.
+        setFocusableInTouchMode(true)
+        setClickable(true)
+        setOnCreateContextMenuListener(this)
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(context)
+        mCurrentTime = Time(Utils.getTimeZone(context, mTZUpdater))
+        val currentTime: Long = System.currentTimeMillis()
+        mCurrentTime?.set(currentTime)
+        mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
+        mWeek_saturdayColor = mResources.getColor(R.color.week_saturday)
+        mWeek_sundayColor = mResources.getColor(R.color.week_sunday)
+        mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color)
+        mFutureBgColorRes = mResources.getColor(R.color.calendar_future_bg_color)
+        mBgColor = mResources.getColor(R.color.calendar_hour_background)
+        mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label)
+        mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected)
+        mCalendarGridLineInnerHorizontalColor = mResources
+            .getColor(R.color.calendar_grid_line_inner_horizontal_color)
+        mCalendarGridLineInnerVerticalColor = mResources
+            .getColor(R.color.calendar_grid_line_inner_vertical_color)
+        mCalendarHourLabelColor = mResources.getColor(R.color.calendar_hour_label)
+        mEventTextColor = mResources.getColor(R.color.calendar_event_text_color)
+        mMoreEventsTextColor = mResources.getColor(R.color.month_event_other_color)
+        mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE)
+        mEventTextPaint.setTextAlign(Paint.Align.LEFT)
+        mEventTextPaint.setAntiAlias(true)
+        val gridLineColor: Int = mResources.getColor(R.color.calendar_grid_line_highlight_color)
+        var p: Paint = mSelectionPaint
+        p.setColor(gridLineColor)
+        p.setStyle(Style.FILL)
+        p.setAntiAlias(false)
+        p = mPaint
+        p.setAntiAlias(true)
+
+        // Allocate space for 2 weeks worth of weekday names so that we can
+        // easily start the week display at any week day.
+        mDayStrs = arrayOfNulls(14)
+
+        // Also create an array of 2-letter abbreviations.
+        mDayStrs2Letter = arrayOfNulls(14)
+        for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+            val index: Int = i - Calendar.SUNDAY
+            // e.g. Tue for Tuesday
+            mDayStrs!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM)
+                .toUpperCase()
+            mDayStrs!![index + 7] = mDayStrs!![index]
+            // e.g. Tu for Tuesday
+            mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT)
+                .toUpperCase()
+
+            // If we don't have 2-letter day strings, fall back to 1-letter.
+            if (mDayStrs2Letter!![index]!!.equals(mDayStrs!![index])) {
+                mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i,
+                DateUtils.LENGTH_SHORTEST)
+            }
+            mDayStrs2Letter!![index + 7] = mDayStrs2Letter!![index]
+        }
+
+        // Figure out how much space we need for the 3-letter abbrev names
+        // in the worst case.
+        p.setTextSize(DATE_HEADER_FONT_SIZE)
+        p.setTypeface(mBold)
+        val dateStrs = arrayOf<String?>(" 28", " 30")
+        mDateStrWidth = computeMaxStringWidth(0, dateStrs, p)
+        p.setTextSize(DAY_HEADER_FONT_SIZE)
+        mDateStrWidth += computeMaxStringWidth(0, mDayStrs as Array<String?>, p)
+        p.setTextSize(HOURS_TEXT_SIZE)
+        p.setTypeface(null)
+        handleOnResume()
+        mAmString = DateUtils.getAMPMString(Calendar.AM).toUpperCase()
+        mPmString = DateUtils.getAMPMString(Calendar.PM).toUpperCase()
+        val ampm = arrayOf(mAmString, mPmString)
+        p.setTextSize(AMPM_TEXT_SIZE)
+        mHoursWidth = Math.max(
+            HOURS_MARGIN, computeMaxStringWidth(mHoursWidth, ampm, p) +
+                HOURS_RIGHT_MARGIN
+        )
+        mHoursWidth = Math.max(MIN_HOURS_WIDTH, mHoursWidth)
+        val inflater: LayoutInflater
+        inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        mPopupView = inflater.inflate(R.layout.bubble_event, null)
+        mPopupView?.setLayoutParams(
+            LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        )
+        mPopup = PopupWindow(context)
+        mPopup?.setContentView(mPopupView)
+        val dialogTheme: Resources.Theme = getResources().newTheme()
+        dialogTheme.applyStyle(android.R.style.Theme_Dialog, true)
+        val ta: TypedArray = dialogTheme.obtainStyledAttributes(
+            intArrayOf(
+                android.R.attr.windowBackground
+            )
+        )
+        mPopup?.setBackgroundDrawable(ta.getDrawable(0))
+        ta.recycle()
+
+        // Enable touching the popup window
+        mPopupView?.setOnClickListener(this)
+        // Catch long clicks for creating a new event
+        setOnLongClickListener(this)
+        mBaseDate = Time(Utils.getTimeZone(context, mTZUpdater))
+        val millis: Long = System.currentTimeMillis()
+        mBaseDate?.set(millis)
+        mEarliestStartHour = IntArray(mNumDays)
+        mHasAllDayEvent = BooleanArray(mNumDays)
+
+        // mLines is the array of points used with Canvas.drawLines() in
+        // drawGridBackground() and drawAllDayEvents().  Its size depends
+        // on the max number of lines that can ever be drawn by any single
+        // drawLines() call in either of those methods.
+        val maxGridLines = (24 + 1 + // max horizontal lines we might draw
+            (mNumDays + 1)) // max vertical lines we might draw
+        mLines = FloatArray(maxGridLines * 4)
+    }
+
+    /**
+     * This is called when the popup window is pressed.
+     */
+    override fun onClick(v: View) {
+        if (v === mPopupView) {
+            // Pretend it was a trackball click because that will always
+            // jump to the "View event" screen.
+            switchViews(true /* trackball */)
+        }
+    }
+
+    fun handleOnResume() {
+        initAccessibilityVariables()
+        if (Utils.getSharedPreference(mContext, OtherPreferences.KEY_OTHER_1, false)) {
+            mFutureBgColor = 0
+        } else {
+            mFutureBgColor = mFutureBgColorRes
+        }
+        mIs24HourFormat = DateFormat.is24HourFormat(mContext)
+        mHourStrs = if (mIs24HourFormat) CalendarData.s24Hours else CalendarData.s12HoursNoAmPm
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+        mLastSelectionDayForAccessibility = 0
+        mLastSelectionHourForAccessibility = 0
+        mLastSelectedEventForAccessibility = null
+        mSelectionMode = SELECTION_HIDDEN
+    }
+
+    private fun initAccessibilityVariables() {
+        mAccessibilityMgr = mContext
+            ?.getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        mIsAccessibilityEnabled = mAccessibilityMgr != null && mAccessibilityMgr!!.isEnabled()
+        mTouchExplorationEnabled = isTouchExplorationEnabled
+    } /* ignore isDst */ // We ignore the "isDst" field because we want normalize() to figure
+    // out the correct DST value and not adjust the selected time based
+    // on the current setting of DST.
+    /**
+     * Returns the start of the selected time in milliseconds since the epoch.
+     *
+     * @return selected time in UTC milliseconds since the epoch.
+     */
+    val selectedTimeInMillis: Long
+        get() {
+            val time = Time(mBaseDate)
+            time.setJulianDay(mSelectionDay)
+            time.hour = mSelectionHour
+
+            // We ignore the "isDst" field because we want normalize() to figure
+            // out the correct DST value and not adjust the selected time based
+            // on the current setting of DST.
+            return time.normalize(true /* ignore isDst */)
+        } /* ignore isDst */
+
+    // We ignore the "isDst" field because we want normalize() to figure
+    // out the correct DST value and not adjust the selected time based
+    // on the current setting of DST.
+    val selectedTime: Time
+        get() {
+            val time = Time(mBaseDate)
+            time.setJulianDay(mSelectionDay)
+            time.hour = mSelectionHour
+
+            // We ignore the "isDst" field because we want normalize() to figure
+            // out the correct DST value and not adjust the selected time based
+            // on the current setting of DST.
+            time.normalize(true /* ignore isDst */)
+            return time
+        } /* ignore isDst */
+
+    // We ignore the "isDst" field because we want normalize() to figure
+    // out the correct DST value and not adjust the selected time based
+    // on the current setting of DST.
+    val selectedTimeForAccessibility: Time
+        get() {
+            val time = Time(mBaseDate)
+            time.setJulianDay(mSelectionDayForAccessibility)
+            time.hour = mSelectionHourForAccessibility
+
+            // We ignore the "isDst" field because we want normalize() to figure
+            // out the correct DST value and not adjust the selected time based
+            // on the current setting of DST.
+            time.normalize(true /* ignore isDst */)
+            return time
+        }
+
+    /**
+     * Returns the start of the selected time in minutes since midnight,
+     * local time.  The derived class must ensure that this is consistent
+     * with the return value from getSelectedTimeInMillis().
+     */
+    val selectedMinutesSinceMidnight: Int
+        get() = mSelectionHour * MINUTES_PER_HOUR
+    var firstVisibleHour: Int
+        get() = mFirstHour
+        set(firstHour) {
+            mFirstHour = firstHour
+            mFirstHourOffset = 0
+        }
+
+    fun setSelected(time: Time?, ignoreTime: Boolean, animateToday: Boolean) {
+        mBaseDate?.set(time)
+        setSelectedHour(mBaseDate!!.hour)
+        setSelectedEvent(null)
+        mPrevSelectedEvent = null
+        val millis: Long = mBaseDate!!.toMillis(false /* use isDst */)
+        setSelectedDay(Time.getJulianDay(millis, mBaseDate!!.gmtoff))
+        mSelectedEvents.clear()
+        mComputeSelectedEvents = true
+        var gotoY: Int = Integer.MIN_VALUE
+        if (!ignoreTime && mGridAreaHeight != -1) {
+            var lastHour = 0
+            if (mBaseDate!!.hour < mFirstHour) {
+                // Above visible region
+                gotoY = mBaseDate!!.hour * (mCellHeight + HOUR_GAP)
+            } else {
+                lastHour = ((mGridAreaHeight - mFirstHourOffset) / (mCellHeight + HOUR_GAP) +
+                    mFirstHour)
+                if (mBaseDate!!.hour >= lastHour) {
+                    // Below visible region
+
+                    // target hour + 1 (to give it room to see the event) -
+                    // grid height (to get the y of the top of the visible
+                    // region)
+                    gotoY = ((mBaseDate!!.hour + 1 + mBaseDate!!.minute / 60.0f) *
+                        (mCellHeight + HOUR_GAP) - mGridAreaHeight).toInt()
+                }
+            }
+            if (DEBUG) {
+                Log.e(
+                    TAG, "Go " + gotoY + " 1st " + mFirstHour + ":" + mFirstHourOffset + "CH " +
+                        (mCellHeight + HOUR_GAP) + " lh " + lastHour + " gh " + mGridAreaHeight +
+                        " ymax " + mMaxViewStartY
+                )
+            }
+            if (gotoY > mMaxViewStartY) {
+                gotoY = mMaxViewStartY
+            } else if (gotoY < 0 && gotoY != Integer.MIN_VALUE) {
+                gotoY = 0
+            }
+        }
+        recalc()
+        mRemeasure = true
+        invalidate()
+        var delayAnimateToday = false
+        if (gotoY != Integer.MIN_VALUE) {
+            val scrollAnim: ValueAnimator =
+                ObjectAnimator.ofInt(this, "viewStartY", mViewStartY, gotoY)
+            scrollAnim.setDuration(GOTO_SCROLL_DURATION.toLong())
+            scrollAnim.setInterpolator(AccelerateDecelerateInterpolator())
+            scrollAnim.addListener(mAnimatorListener)
+            scrollAnim.start()
+            delayAnimateToday = true
+        }
+        if (animateToday) {
+            synchronized(mTodayAnimatorListener) {
+                if (mTodayAnimator != null) {
+                    mTodayAnimator?.removeAllListeners()
+                    mTodayAnimator?.cancel()
+                }
+                mTodayAnimator = ObjectAnimator.ofInt(
+                    this, "animateTodayAlpha",
+                    mAnimateTodayAlpha, 255
+                )
+                mAnimateToday = true
+                mTodayAnimatorListener.setFadingIn(true)
+                mTodayAnimatorListener.setAnimator(mTodayAnimator)
+                mTodayAnimator?.addListener(mTodayAnimatorListener)
+                mTodayAnimator?.setDuration(150)
+                if (delayAnimateToday) {
+                    mTodayAnimator?.setStartDelay(GOTO_SCROLL_DURATION.toLong())
+                }
+                mTodayAnimator?.start()
+            }
+        }
+        sendAccessibilityEventAsNeeded(false)
+    }
+
+    // Called from animation framework via reflection. Do not remove
+    fun setViewStartY(viewStartY: Int) {
+        var viewStartY = viewStartY
+        if (viewStartY > mMaxViewStartY) {
+            viewStartY = mMaxViewStartY
+        }
+        mViewStartY = viewStartY
+        computeFirstHour()
+        invalidate()
+    }
+
+    fun setAnimateTodayAlpha(todayAlpha: Int) {
+        mAnimateTodayAlpha = todayAlpha
+        invalidate()
+    } /* ignore isDst */
+
+    fun getSelectedDay(): Time {
+        val time = Time(mBaseDate)
+        time.setJulianDay(mSelectionDay)
+        time.hour = mSelectionHour
+
+        // We ignore the "isDst" field because we want normalize() to figure
+        // out the correct DST value and not adjust the selected time based
+        // on the current setting of DST.
+        time.normalize(true /* ignore isDst */)
+        return time
+    }
+
+    fun updateTitle() {
+        val start = Time(mBaseDate)
+        start.normalize(true)
+        val end = Time(start)
+        end.monthDay += mNumDays - 1
+        // Move it forward one minute so the formatter doesn't lose a day
+        end.minute += 1
+        end.normalize(true)
+        var formatFlags: Long = DateUtils.FORMAT_SHOW_DATE.toLong() or
+            DateUtils.FORMAT_SHOW_YEAR.toLong()
+        if (mNumDays != 1) {
+            // Don't show day of the month if for multi-day view
+            formatFlags = formatFlags or DateUtils.FORMAT_NO_MONTH_DAY.toLong()
+
+            // Abbreviate the month if showing multiple months
+            if (start.month !== end.month) {
+                formatFlags = formatFlags or DateUtils.FORMAT_ABBREV_MONTH.toLong()
+            }
+        }
+        mController.sendEvent(
+            this as Object?, EventType.UPDATE_TITLE, start, end, null, -1, ViewType.CURRENT,
+            formatFlags, null, null
+        )
+    }
+
+    /**
+     * return a negative number if "time" is comes before the visible time
+     * range, a positive number if "time" is after the visible time range, and 0
+     * if it is in the visible time range.
+     */
+    fun compareToVisibleTimeRange(time: Time): Int {
+        val savedHour: Int = mBaseDate!!.hour
+        val savedMinute: Int = mBaseDate!!.minute
+        val savedSec: Int = mBaseDate!!.second
+        mBaseDate!!.hour = 0
+        mBaseDate!!.minute = 0
+        mBaseDate!!.second = 0
+        if (DEBUG) {
+            Log.d(TAG, "Begin " + mBaseDate.toString())
+            Log.d(TAG, "Diff  " + time.toString())
+        }
+
+        // Compare beginning of range
+        var diff: Int = Time.compare(time, mBaseDate)
+        if (diff > 0) {
+            // Compare end of range
+            mBaseDate!!.monthDay += mNumDays
+            mBaseDate?.normalize(true)
+            diff = Time.compare(time, mBaseDate)
+            if (DEBUG) Log.d(TAG, "End   " + mBaseDate.toString())
+            mBaseDate!!.monthDay -= mNumDays
+            mBaseDate?.normalize(true)
+            if (diff < 0) {
+                // in visible time
+                diff = 0
+            } else if (diff == 0) {
+                // Midnight of following day
+                diff = 1
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Diff: $diff")
+        mBaseDate!!.hour = savedHour
+        mBaseDate!!.minute = savedMinute
+        mBaseDate!!.second = savedSec
+        return diff
+    }
+
+    private fun recalc() {
+        // Set the base date to the beginning of the week if we are displaying
+        // 7 days at a time.
+        if (mNumDays == 7) {
+            adjustToBeginningOfWeek(mBaseDate)
+        }
+        val start: Long = mBaseDate!!.toMillis(false /* use isDst */)
+        mFirstJulianDay = Time.getJulianDay(start, mBaseDate!!.gmtoff)
+        mLastJulianDay = mFirstJulianDay + mNumDays - 1
+        mMonthLength = mBaseDate!!.getActualMaximum(Time.MONTH_DAY)
+        mFirstVisibleDate = mBaseDate!!.monthDay
+        mFirstVisibleDayOfWeek = mBaseDate!!.weekDay
+    }
+
+    private fun adjustToBeginningOfWeek(time: Time?) {
+        val dayOfWeek: Int = time!!.weekDay
+        var diff = dayOfWeek - mFirstDayOfWeek
+        if (diff != 0) {
+            if (diff < 0) {
+                diff += 7
+            }
+            time!!.monthDay -= diff
+            time?.normalize(true /* ignore isDst */)
+        }
+    }
+
+    @Override
+    protected override fun onSizeChanged(width: Int, height: Int, oldw: Int, oldh: Int) {
+        mViewWidth = width
+        mViewHeight = height
+        mEdgeEffectTop.setSize(mViewWidth, mViewHeight)
+        mEdgeEffectBottom.setSize(mViewWidth, mViewHeight)
+        val gridAreaWidth = width - mHoursWidth
+        mCellWidth = (gridAreaWidth - mNumDays * DAY_GAP) / mNumDays
+
+        // This would be about 1 day worth in a 7 day view
+        mHorizontalSnapBackThreshold = width / 7
+        val p = Paint()
+        p.setTextSize(HOURS_TEXT_SIZE)
+        mHoursTextHeight = Math.abs(p.ascent()).toInt()
+        remeasure(width, height)
+    }
+
+    /**
+     * Measures the space needed for various parts of the view after
+     * loading new events.  This can change if there are all-day events.
+     */
+    private fun remeasure(width: Int, height: Int) {
+        // Shrink to fit available space but make sure we can display at least two events
+        MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
+        MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.min(MAX_UNEXPANDED_ALLDAY_HEIGHT, height / 6)
+        MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.max(
+            MAX_UNEXPANDED_ALLDAY_HEIGHT,
+            MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt() * 2
+        )
+        mMaxUnexpandedAlldayEventCount =
+            (MAX_UNEXPANDED_ALLDAY_HEIGHT / MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+
+        // First, clear the array of earliest start times, and the array
+        // indicating presence of an all-day event.
+        for (day in 0 until mNumDays) {
+            mEarliestStartHour!![day] = 25 // some big number
+            mHasAllDayEvent!![day] = false
+        }
+        val maxAllDayEvents = mMaxAlldayEvents
+
+        // The min is where 24 hours cover the entire visible area
+        mMinCellHeight = Math.max((height - DAY_HEADER_HEIGHT) / 24, MIN_EVENT_HEIGHT.toInt())
+        if (mCellHeight < mMinCellHeight) {
+            mCellHeight = mMinCellHeight
+        }
+
+        // Calculate mAllDayHeight
+        mFirstCell = DAY_HEADER_HEIGHT
+        var allDayHeight = 0
+        if (maxAllDayEvents > 0) {
+            val maxAllAllDayHeight = height - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+            // If there is at most one all-day event per day, then use less
+            // space (but more than the space for a single event).
+            if (maxAllDayEvents == 1) {
+                allDayHeight = SINGLE_ALLDAY_HEIGHT
+            } else if (maxAllDayEvents <= mMaxUnexpandedAlldayEventCount) {
+                // Allow the all-day area to grow in height depending on the
+                // number of all-day events we need to show, up to a limit.
+                allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT
+                if (allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
+                    allDayHeight = MAX_UNEXPANDED_ALLDAY_HEIGHT
+                }
+            } else {
+                // if we have more than the magic number, check if we're animating
+                // and if not adjust the sizes appropriately
+                if (mAnimateDayHeight != 0) {
+                    // Don't shrink the space past the final allDay space. The animation
+                    // continues to hide the last event so the more events text can
+                    // fade in.
+                    allDayHeight = Math.max(mAnimateDayHeight, MAX_UNEXPANDED_ALLDAY_HEIGHT)
+                } else {
+                    // Try to fit all the events in
+                    allDayHeight = (maxAllDayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+                    // But clip the area depending on which mode we're in
+                    if (!mShowAllAllDayEvents && allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
+                        allDayHeight = (mMaxUnexpandedAlldayEventCount *
+                            MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+                    } else if (allDayHeight > maxAllAllDayHeight) {
+                        allDayHeight = maxAllAllDayHeight
+                    }
+                }
+            }
+            mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN
+        } else {
+            mSelectionAllday = false
+        }
+        mAlldayHeight = allDayHeight
+        mGridAreaHeight = height - mFirstCell
+
+        // Set up the expand icon position
+        val allDayIconWidth: Int = mExpandAlldayDrawable.getIntrinsicWidth()
+        mExpandAllDayRect.left = Math.max(
+            (mHoursWidth - allDayIconWidth) / 2,
+            EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+        )
+        mExpandAllDayRect.right = Math.min(
+            mExpandAllDayRect.left + allDayIconWidth, mHoursWidth -
+                EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
+        )
+        mExpandAllDayRect.bottom = mFirstCell - EXPAND_ALL_DAY_BOTTOM_MARGIN
+        mExpandAllDayRect.top = (mExpandAllDayRect.bottom -
+            mExpandAlldayDrawable.getIntrinsicHeight())
+        mNumHours = mGridAreaHeight / (mCellHeight + HOUR_GAP)
+        mEventGeometry.setHourHeight(mCellHeight.toFloat())
+        val minimumDurationMillis =
+            (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f)).toLong()
+        Event.computePositions(mEvents, minimumDurationMillis)
+
+        // Compute the top of our reachable view
+        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
+        if (DEBUG) {
+            Log.e(TAG, "mViewStartY: $mViewStartY")
+            Log.e(TAG, "mMaxViewStartY: $mMaxViewStartY")
+        }
+        if (mViewStartY > mMaxViewStartY) {
+            mViewStartY = mMaxViewStartY
+            computeFirstHour()
+        }
+        if (mFirstHour == -1) {
+            initFirstHour()
+            mFirstHourOffset = 0
+        }
+
+        // When we change the base date, the number of all-day events may
+        // change and that changes the cell height.  When we switch dates,
+        // we use the mFirstHourOffset from the previous view, but that may
+        // be too large for the new view if the cell height is smaller.
+        if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
+            mFirstHourOffset = mCellHeight + HOUR_GAP - 1
+        }
+        mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset
+        val eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP)
+        // When we get new events we don't want to dismiss the popup unless the event changes
+        if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent!!.id) {
+            mPopup?.dismiss()
+        }
+        mPopup?.setWidth(eventAreaWidth - 20)
+        mPopup?.setHeight(WindowManager.LayoutParams.WRAP_CONTENT)
+    }
+
+    /**
+     * Initialize the state for another view.  The given view is one that has
+     * its own bitmap and will use an animation to replace the current view.
+     * The current view and new view are either both Week views or both Day
+     * views.  They differ in their base date.
+     *
+     * @param view the view to initialize.
+     */
+    private fun initView(view: DayView) {
+        view.setSelectedHour(mSelectionHour)
+        view.mSelectedEvents.clear()
+        view.mComputeSelectedEvents = true
+        view.mFirstHour = mFirstHour
+        view.mFirstHourOffset = mFirstHourOffset
+        view.remeasure(getWidth(), getHeight())
+        view.initAllDayHeights()
+        view.setSelectedEvent(null)
+        view.mPrevSelectedEvent = null
+        view.mFirstDayOfWeek = mFirstDayOfWeek
+        if (view.mEvents.size > 0) {
+            view.mSelectionAllday = mSelectionAllday
+        } else {
+            view.mSelectionAllday = false
+        }
+
+        // Redraw the screen so that the selection box will be redrawn.  We may
+        // have scrolled to a different part of the day in some other view
+        // so the selection box in this view may no longer be visible.
+        view.recalc()
+    }
+
+    /**
+     * Switch to another view based on what was selected (an event or a free
+     * slot) and how it was selected (by touch or by trackball).
+     *
+     * @param trackBallSelection true if the selection was made using the
+     * trackball.
+     */
+    private fun switchViews(trackBallSelection: Boolean) {
+        val selectedEvent: Event? = mSelectedEvent
+        mPopup?.dismiss()
+        mLastPopupEventID = INVALID_EVENT_ID
+        if (mNumDays > 1) {
+            // This is the Week view.
+            // With touch, we always switch to Day/Agenda View
+            // With track ball, if we selected a free slot, then create an event.
+            // If we selected a specific event, switch to EventInfo view.
+            if (trackBallSelection) {
+                if (selectedEvent != null) {
+                    if (mIsAccessibilityEnabled) {
+                        mAccessibilityMgr?.interrupt()
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
+        mScrolling = false
+        return super.onKeyUp(keyCode, event)
+    }
+
+    @Override
+    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+        return super.onKeyDown(keyCode, event)
+    }
+
+    @Override
+    override fun onHoverEvent(event: MotionEvent?): Boolean {
+        return true
+    }
+
+    private val isTouchExplorationEnabled: Boolean
+        private get() = mIsAccessibilityEnabled && mAccessibilityMgr!!.isTouchExplorationEnabled()
+
+    private fun sendAccessibilityEventAsNeeded(speakEvents: Boolean) {
+        if (!mIsAccessibilityEnabled) {
+            return
+        }
+        val dayChanged = mLastSelectionDayForAccessibility != mSelectionDayForAccessibility
+        val hourChanged = mLastSelectionHourForAccessibility != mSelectionHourForAccessibility
+        if (dayChanged || hourChanged || mLastSelectedEventForAccessibility !==
+            mSelectedEventForAccessibility) {
+            mLastSelectionDayForAccessibility = mSelectionDayForAccessibility
+            mLastSelectionHourForAccessibility = mSelectionHourForAccessibility
+            mLastSelectedEventForAccessibility = mSelectedEventForAccessibility
+            val b = StringBuilder()
+
+            // Announce only the changes i.e. day or hour or both
+            if (dayChanged) {
+                b.append(selectedTimeForAccessibility.format("%A "))
+            }
+            if (hourChanged) {
+                b.append(selectedTimeForAccessibility.format(if (mIs24HourFormat) "%k" else "%l%p"))
+            }
+            if (dayChanged || hourChanged) {
+                b.append(PERIOD_SPACE)
+            }
+            if (speakEvents) {
+                if (mEventCountTemplate == null) {
+                    mEventCountTemplate = mContext?.getString(R.string.template_announce_item_index)
+                }
+
+                // Read out the relevant event(s)
+                val numEvents: Int = mSelectedEvents.size
+                if (numEvents > 0) {
+                    if (mSelectedEventForAccessibility == null) {
+                        // Read out all the events
+                        var i = 1
+                        for (calEvent in mSelectedEvents) {
+                            if (numEvents > 1) {
+                                // Read out x of numEvents if there are more than one event
+                                mStringBuilder.setLength(0)
+                                b.append(mFormatter.format(mEventCountTemplate, i++, numEvents))
+                                b.append(" ")
+                            }
+                            appendEventAccessibilityString(b, calEvent)
+                        }
+                    } else {
+                        if (numEvents > 1) {
+                            // Read out x of numEvents if there are more than one event
+                            mStringBuilder.setLength(0)
+                            b.append(
+                                mFormatter.format(
+                                    mEventCountTemplate, mSelectedEvents
+                                        .indexOf(mSelectedEventForAccessibility) + 1, numEvents
+                                )
+                            )
+                            b.append(" ")
+                        }
+                        appendEventAccessibilityString(b, mSelectedEventForAccessibility)
+                    }
+                }
+            }
+            if (dayChanged || hourChanged || speakEvents) {
+                val event: AccessibilityEvent = AccessibilityEvent
+                    .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+                val msg: CharSequence = b.toString()
+                event.getText().add(msg)
+                event.setAddedCount(msg.length)
+                sendAccessibilityEventUnchecked(event)
+            }
+        }
+    }
+
+    /**
+     * @param b
+     * @param calEvent
+     */
+    private fun appendEventAccessibilityString(b: StringBuilder, calEvent: Event?) {
+        b.append(calEvent!!.titleAndLocation)
+        b.append(PERIOD_SPACE)
+        val `when`: String?
+        var flags: Int = DateUtils.FORMAT_SHOW_DATE
+        if (calEvent!!.allDay) {
+            flags = flags or (DateUtils.FORMAT_UTC or DateUtils.FORMAT_SHOW_WEEKDAY)
+        } else {
+            flags = flags or DateUtils.FORMAT_SHOW_TIME
+            if (DateFormat.is24HourFormat(mContext)) {
+                flags = flags or DateUtils.FORMAT_24HOUR
+            }
+        }
+        `when` = Utils.formatDateRange(mContext, calEvent!!.startMillis, calEvent!!.endMillis,
+            flags)
+        b.append(`when`)
+        b.append(PERIOD_SPACE)
+    }
+
+    private inner class GotoBroadcaster(start: Time, end: Time) : Animation.AnimationListener {
+        private val mCounter: Int
+        private val mStart: Time
+        private val mEnd: Time
+        @Override
+        override fun onAnimationEnd(animation: Animation?) {
+            var view = mViewSwitcher.getCurrentView() as DayView
+            view.mViewStartX = 0
+            view = mViewSwitcher.getNextView() as DayView
+            view.mViewStartX = 0
+            if (mCounter == sCounter) {
+                mController.sendEvent(
+                    this as Object?, EventType.GO_TO, mStart, mEnd, null, -1,
+                    ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null
+                )
+            }
+        }
+
+        @Override
+        override fun onAnimationRepeat(animation: Animation?) {
+        }
+
+        @Override
+        override fun onAnimationStart(animation: Animation?) {
+        }
+
+        init {
+            mCounter = ++sCounter
+            mStart = start
+            mEnd = end
+        }
+    }
+
+    private fun switchViews(forward: Boolean, xOffSet: Float, width: Float, velocity: Float): View {
+        mAnimationDistance = width - xOffSet
+        if (DEBUG) {
+            Log.d(TAG, "switchViews($forward) O:$xOffSet Dist:$mAnimationDistance")
+        }
+        var progress: Float = Math.abs(xOffSet) / width
+        if (progress > 1.0f) {
+            progress = 1.0f
+        }
+        val inFromXValue: Float
+        val inToXValue: Float
+        val outFromXValue: Float
+        val outToXValue: Float
+        if (forward) {
+            inFromXValue = 1.0f - progress
+            inToXValue = 0.0f
+            outFromXValue = -progress
+            outToXValue = -1.0f
+        } else {
+            inFromXValue = progress - 1.0f
+            inToXValue = 0.0f
+            outFromXValue = progress
+            outToXValue = 1.0f
+        }
+        val start = Time(mBaseDate!!.timezone)
+        start.set(mController.time as Long)
+        if (forward) {
+            start.monthDay += mNumDays
+        } else {
+            start.monthDay -= mNumDays
+        }
+        mController.time = start.normalize(true)
+        var newSelected: Time? = start
+        if (mNumDays == 7) {
+            newSelected = Time(start)
+            adjustToBeginningOfWeek(start)
+        }
+        val end = Time(start)
+        end.monthDay += mNumDays - 1
+
+        // We have to allocate these animation objects each time we switch views
+        // because that is the only way to set the animation parameters.
+        val inAnimation = TranslateAnimation(
+            Animation.RELATIVE_TO_SELF, inFromXValue,
+            Animation.RELATIVE_TO_SELF, inToXValue,
+            Animation.ABSOLUTE, 0.0f,
+            Animation.ABSOLUTE, 0.0f
+        )
+        val outAnimation = TranslateAnimation(
+            Animation.RELATIVE_TO_SELF, outFromXValue,
+            Animation.RELATIVE_TO_SELF, outToXValue,
+            Animation.ABSOLUTE, 0.0f,
+            Animation.ABSOLUTE, 0.0f
+        )
+        val duration = calculateDuration(width - Math.abs(xOffSet), width, velocity)
+        inAnimation.setDuration(duration)
+        inAnimation.setInterpolator(mHScrollInterpolator)
+        outAnimation.setInterpolator(mHScrollInterpolator)
+        outAnimation.setDuration(duration)
+        outAnimation.setAnimationListener(GotoBroadcaster(start, end))
+        mViewSwitcher.setInAnimation(inAnimation)
+        mViewSwitcher.setOutAnimation(outAnimation)
+        var view = mViewSwitcher.getCurrentView() as DayView
+        view.cleanup()
+        mViewSwitcher.showNext()
+        view = mViewSwitcher.getCurrentView() as DayView
+        view.setSelected(newSelected, true, false)
+        view.requestFocus()
+        view.reloadEvents()
+        view.updateTitle()
+        view.restartCurrentTimeUpdates()
+        return view
+    }
+
+    // This is called after scrolling stops to move the selected hour
+    // to the visible part of the screen.
+    private fun resetSelectedHour() {
+        if (mSelectionHour < mFirstHour + 1) {
+            setSelectedHour(mFirstHour + 1)
+            setSelectedEvent(null)
+            mSelectedEvents.clear()
+            mComputeSelectedEvents = true
+        } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
+            setSelectedHour(mFirstHour + mNumHours - 3)
+            setSelectedEvent(null)
+            mSelectedEvents.clear()
+            mComputeSelectedEvents = true
+        }
+    }
+
+    private fun initFirstHour() {
+        mFirstHour = mSelectionHour - mNumHours / 5
+        if (mFirstHour < 0) {
+            mFirstHour = 0
+        } else if (mFirstHour + mNumHours > 24) {
+            mFirstHour = 24 - mNumHours
+        }
+    }
+
+    /**
+     * Recomputes the first full hour that is visible on screen after the
+     * screen is scrolled.
+     */
+    private fun computeFirstHour() {
+        // Compute the first full hour that is visible on screen
+        mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP)
+        mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY
+    }
+
+    private fun adjustHourSelection() {
+        if (mSelectionHour < 0) {
+            setSelectedHour(0)
+            if (mMaxAlldayEvents > 0) {
+                mPrevSelectedEvent = null
+                mSelectionAllday = true
+            }
+        }
+        if (mSelectionHour > 23) {
+            setSelectedHour(23)
+        }
+
+        // If the selected hour is at least 2 time slots from the top and
+        // bottom of the screen, then don't scroll the view.
+        if (mSelectionHour < mFirstHour + 1) {
+            // If there are all-days events for the selected day but there
+            // are no more normal events earlier in the day, then jump to
+            // the all-day event area.
+            // Exception 1: allow the user to scroll to 8am with the trackball
+            // before jumping to the all-day event area.
+            // Exception 2: if 12am is on screen, then allow the user to select
+            // 12am before going up to the all-day event area.
+            val daynum = mSelectionDay - mFirstJulianDay
+            if (daynum < mEarliestStartHour!!.size && daynum >= 0 && mMaxAlldayEvents > 0 &&
+                mEarliestStartHour!![daynum] > mSelectionHour &&
+                mFirstHour > 0 && mFirstHour < 8) {
+                mPrevSelectedEvent = null
+                mSelectionAllday = true
+                setSelectedHour(mFirstHour + 1)
+                return
+            }
+            if (mFirstHour > 0) {
+                mFirstHour -= 1
+                mViewStartY -= mCellHeight + HOUR_GAP
+                if (mViewStartY < 0) {
+                    mViewStartY = 0
+                }
+                return
+            }
+        }
+        if (mSelectionHour > mFirstHour + mNumHours - 3) {
+            if (mFirstHour < 24 - mNumHours) {
+                mFirstHour += 1
+                mViewStartY += mCellHeight + HOUR_GAP
+                if (mViewStartY > mMaxViewStartY) {
+                    mViewStartY = mMaxViewStartY
+                }
+                return
+            } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
+                mViewStartY = mMaxViewStartY
+            }
+        }
+    }
+
+    fun clearCachedEvents() {
+        mLastReloadMillis = 0
+    }
+
+    private val mCancelCallback: Runnable = object : Runnable {
+        override fun run() {
+            clearCachedEvents()
+        }
+    }
+
+    /* package */
+    fun reloadEvents() {
+        // Protect against this being called before this view has been
+        // initialized.
+//        if (mContext == null) {
+//            return;
+//        }
+
+        // Make sure our time zones are up to date
+        mTZUpdater.run()
+        setSelectedEvent(null)
+        mPrevSelectedEvent = null
+        mSelectedEvents.clear()
+
+        // The start date is the beginning of the week at 12am
+        val weekStart = Time(Utils.getTimeZone(mContext, mTZUpdater))
+        weekStart.set(mBaseDate)
+        weekStart.hour = 0
+        weekStart.minute = 0
+        weekStart.second = 0
+        val millis: Long = weekStart.normalize(true /* ignore isDst */)
+
+        // Avoid reloading events unnecessarily.
+        if (millis == mLastReloadMillis) {
+            return
+        }
+        mLastReloadMillis = millis
+
+        // load events in the background
+        // mContext.startProgressSpinner();
+        val events: ArrayList<Event> = ArrayList<Event>()
+        mEventLoader.loadEventsInBackground(mNumDays, events as ArrayList<Event?>, mFirstJulianDay,
+            object : Runnable {
+            override fun run() {
+                val fadeinEvents = mFirstJulianDay != mLoadedFirstJulianDay
+                mEvents = events
+                mLoadedFirstJulianDay = mFirstJulianDay
+                if (mAllDayEvents == null) {
+                    mAllDayEvents = ArrayList<Event>()
+                } else {
+                    mAllDayEvents?.clear()
+                }
+
+                // Create a shorter array for all day events
+                for (e in events) {
+                    if (e.drawAsAllday()) {
+                        mAllDayEvents?.add(e)
+                    }
+                }
+
+                // New events, new layouts
+                if (mLayouts == null || mLayouts!!.size < events.size) {
+                    mLayouts = arrayOfNulls<StaticLayout>(events.size)
+                } else {
+                    Arrays.fill(mLayouts, null)
+                }
+                if (mAllDayLayouts == null || mAllDayLayouts!!.size < mAllDayEvents!!.size) {
+                    mAllDayLayouts = arrayOfNulls<StaticLayout>(events.size)
+                } else {
+                    Arrays.fill(mAllDayLayouts, null)
+                }
+                computeEventRelations()
+                mRemeasure = true
+                mComputeSelectedEvents = true
+                recalc()
+
+                // Start animation to cross fade the events
+                if (fadeinEvents) {
+                    if (mEventsCrossFadeAnimation == null) {
+                        mEventsCrossFadeAnimation =
+                            ObjectAnimator.ofInt(this@DayView, "EventsAlpha", 0, 255)
+                        mEventsCrossFadeAnimation?.setDuration(EVENTS_CROSS_FADE_DURATION.toLong())
+                    }
+                    mEventsCrossFadeAnimation?.start()
+                } else {
+                    invalidate()
+                }
+            }
+        }, mCancelCallback)
+    }
+
+    var eventsAlpha: Int
+        get() = mEventsAlpha
+        set(alpha) {
+            mEventsAlpha = alpha
+            invalidate()
+        }
+
+    fun stopEventsAnimation() {
+        if (mEventsCrossFadeAnimation != null) {
+            mEventsCrossFadeAnimation?.cancel()
+        }
+        mEventsAlpha = 255
+    }
+
+    private fun computeEventRelations() {
+        // Compute the layout relation between each event before measuring cell
+        // width, as the cell width should be adjusted along with the relation.
+        //
+        // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
+        // We should mark them as "overwapped". Though they are not overwapped logically, but
+        // minimum cell height implicitly expands the cell height of A and it should look like
+        // (1:00pm - 1:15pm) after the cell height adjustment.
+
+        // Compute the space needed for the all-day events, if any.
+        // Make a pass over all the events, and keep track of the maximum
+        // number of all-day events in any one day.  Also, keep track of
+        // the earliest event in each day.
+        var maxAllDayEvents = 0
+        val events: ArrayList<Event> = mEvents
+        val len: Int = events.size
+        // Num of all-day-events on each day.
+        val eventsCount = IntArray(mLastJulianDay - mFirstJulianDay + 1)
+        Arrays.fill(eventsCount, 0)
+        for (ii in 0 until len) {
+            val event: Event = events.get(ii)
+            if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
+                continue
+            }
+            if (event.drawAsAllday()) {
+                // Count all the events being drawn as allDay events
+                val firstDay: Int = Math.max(event.startDay, mFirstJulianDay)
+                val lastDay: Int = Math.min(event.endDay, mLastJulianDay)
+                for (day in firstDay..lastDay) {
+                    val count = ++eventsCount[day - mFirstJulianDay]
+                    if (maxAllDayEvents < count) {
+                        maxAllDayEvents = count
+                    }
+                }
+                var daynum: Int = event.startDay - mFirstJulianDay
+                var durationDays: Int = event.endDay - event.startDay + 1
+                if (daynum < 0) {
+                    durationDays += daynum
+                    daynum = 0
+                }
+                if (daynum + durationDays > mNumDays) {
+                    durationDays = mNumDays - daynum
+                }
+                var day = daynum
+                while (durationDays > 0) {
+                    mHasAllDayEvent!![day] = true
+                    day++
+                    durationDays--
+                }
+            } else {
+                var daynum: Int = event.startDay - mFirstJulianDay
+                var hour: Int = event.startTime / 60
+                if (daynum >= 0 && hour < mEarliestStartHour!![daynum]) {
+                    mEarliestStartHour!![daynum] = hour
+                }
+
+                // Also check the end hour in case the event spans more than
+                // one day.
+                daynum = event.endDay - mFirstJulianDay
+                hour = event.endTime / 60
+                if (daynum < mNumDays && hour < mEarliestStartHour!![daynum]) {
+                    mEarliestStartHour!![daynum] = hour
+                }
+            }
+        }
+        mMaxAlldayEvents = maxAllDayEvents
+        initAllDayHeights()
+    }
+
+    @Override
+    protected override fun onDraw(canvas: Canvas) {
+        if (mRemeasure) {
+            remeasure(getWidth(), getHeight())
+            mRemeasure = false
+        }
+        canvas.save()
+        val yTranslate = (-mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight).toFloat()
+        // offset canvas by the current drag and header position
+        canvas.translate(-mViewStartX.toFloat(), yTranslate)
+        // clip to everything below the allDay area
+        val dest: Rect = mDestRect
+        dest.top = (mFirstCell - yTranslate).toInt()
+        dest.bottom = (mViewHeight - yTranslate).toInt()
+        dest.left = 0
+        dest.right = mViewWidth
+        canvas.save()
+        canvas.clipRect(dest)
+        // Draw the movable part of the view
+        doDraw(canvas)
+        // restore to having no clip
+        canvas.restore()
+        if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+            val xTranslate: Float
+            xTranslate = if (mViewStartX > 0) {
+                mViewWidth.toFloat()
+            } else {
+                -mViewWidth.toFloat()
+            }
+            // Move the canvas around to prep it for the next view
+            // specifically, shift it by a screen and undo the
+            // yTranslation which will be redone in the nextView's onDraw().
+            canvas.translate(xTranslate, -yTranslate)
+            val nextView = mViewSwitcher.getNextView() as DayView
+
+            // Prevent infinite recursive calls to onDraw().
+            nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE
+            nextView.onDraw(canvas)
+            // Move it back for this view
+            canvas.translate(-xTranslate, 0f)
+        } else {
+            // If we drew another view we already translated it back
+            // If we didn't draw another view we should be at the edge of the
+            // screen
+            canvas.translate(mViewStartX.toFloat(), -yTranslate)
+        }
+
+        // Draw the fixed areas (that don't scroll) directly to the canvas.
+        drawAfterScroll(canvas)
+        if (mComputeSelectedEvents && mUpdateToast) {
+            mUpdateToast = false
+        }
+        mComputeSelectedEvents = false
+
+        // Draw overscroll glow
+        if (!mEdgeEffectTop.isFinished()) {
+            if (DAY_HEADER_HEIGHT != 0) {
+                canvas.translate(0f, DAY_HEADER_HEIGHT.toFloat())
+            }
+            if (mEdgeEffectTop.draw(canvas)) {
+                invalidate()
+            }
+            if (DAY_HEADER_HEIGHT != 0) {
+                canvas.translate(0f, -DAY_HEADER_HEIGHT.toFloat())
+            }
+        }
+        if (!mEdgeEffectBottom.isFinished()) {
+            canvas.rotate(180f, mViewWidth.toFloat() / 2f, mViewHeight.toFloat() / 2f)
+            if (mEdgeEffectBottom.draw(canvas)) {
+                invalidate()
+            }
+        }
+        canvas.restore()
+    }
+
+    private fun drawAfterScroll(canvas: Canvas) {
+        val p: Paint = mPaint
+        val r: Rect = mRect
+        drawAllDayHighlights(r, canvas, p)
+        if (mMaxAlldayEvents != 0) {
+            drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p)
+            drawUpperLeftCorner(r, canvas, p)
+        }
+        drawScrollLine(r, canvas, p)
+        drawDayHeaderLoop(r, canvas, p)
+
+        // Draw the AM and PM indicators if we're in 12 hour mode
+        if (!mIs24HourFormat) {
+            drawAmPm(canvas, p)
+        }
+    }
+
+    // This isn't really the upper-left corner. It's the square area just
+    // below the upper-left corner, above the hours and to the left of the
+    // all-day area.
+    private fun drawUpperLeftCorner(r: Rect, canvas: Canvas, p: Paint) {
+        setupHourTextPaint(p)
+        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+            // Draw the allDay expand/collapse icon
+            if (mUseExpandIcon) {
+                mExpandAlldayDrawable.setBounds(mExpandAllDayRect)
+                mExpandAlldayDrawable.draw(canvas)
+            } else {
+                mCollapseAlldayDrawable.setBounds(mExpandAllDayRect)
+                mCollapseAlldayDrawable.draw(canvas)
+            }
+        }
+    }
+
+    private fun drawScrollLine(r: Rect, canvas: Canvas, p: Paint) {
+        val right = computeDayLeftPosition(mNumDays)
+        val y = mFirstCell - 1
+        p.setAntiAlias(false)
+        p.setStyle(Style.FILL)
+        p.setColor(mCalendarGridLineInnerHorizontalColor)
+        p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+        canvas.drawLine(GRID_LINE_LEFT_MARGIN, y.toFloat(), right.toFloat(), y.toFloat(), p)
+        p.setAntiAlias(true)
+    }
+
+    // Computes the x position for the left side of the given day (base 0)
+    private fun computeDayLeftPosition(day: Int): Int {
+        val effectiveWidth = mViewWidth - mHoursWidth
+        return day * effectiveWidth / mNumDays + mHoursWidth
+    }
+
+    private fun drawAllDayHighlights(r: Rect, canvas: Canvas, p: Paint) {
+        if (mFutureBgColor != 0) {
+            // First, color the labels area light gray
+            r.top = 0
+            r.bottom = DAY_HEADER_HEIGHT
+            r.left = 0
+            r.right = mViewWidth
+            p.setColor(mBgColor)
+            p.setStyle(Style.FILL)
+            canvas.drawRect(r, p)
+            // and the area that says All day
+            r.top = DAY_HEADER_HEIGHT
+            r.bottom = mFirstCell - 1
+            r.left = 0
+            r.right = mHoursWidth
+            canvas.drawRect(r, p)
+            var startIndex = -1
+            val todayIndex = mTodayJulianDay - mFirstJulianDay
+            if (todayIndex < 0) {
+                // Future
+                startIndex = 0
+            } else if (todayIndex >= 1 && todayIndex + 1 < mNumDays) {
+                // Multiday - tomorrow is visible.
+                startIndex = todayIndex + 1
+            }
+            if (startIndex >= 0) {
+                // Draw the future highlight
+                r.top = 0
+                r.bottom = mFirstCell - 1
+                r.left = computeDayLeftPosition(startIndex) + 1
+                r.right = computeDayLeftPosition(mNumDays)
+                p.setColor(mFutureBgColor)
+                p.setStyle(Style.FILL)
+                canvas.drawRect(r, p)
+            }
+        }
+    }
+
+    private fun drawDayHeaderLoop(r: Rect, canvas: Canvas, p: Paint) {
+        // Draw the horizontal day background banner
+        // p.setColor(mCalendarDateBannerBackground);
+        // r.top = 0;
+        // r.bottom = DAY_HEADER_HEIGHT;
+        // r.left = 0;
+        // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
+        // canvas.drawRect(r, p);
+        //
+        // Fill the extra space on the right side with the default background
+        // r.left = r.right;
+        // r.right = mViewWidth;
+        // p.setColor(mCalendarGridAreaBackground);
+        // canvas.drawRect(r, p);
+        if (mNumDays == 1 && ONE_DAY_HEADER_HEIGHT == 0) {
+            return
+        }
+        p.setTypeface(mBold)
+        p.setTextAlign(Paint.Align.RIGHT)
+        var cell = mFirstJulianDay
+        val dayNames: Array<String?>?
+        dayNames = if (mDateStrWidth < mCellWidth) {
+            mDayStrs
+        } else {
+            mDayStrs2Letter
+        }
+        p.setAntiAlias(true)
+        var day = 0
+        while (day < mNumDays) {
+            var dayOfWeek = day + mFirstVisibleDayOfWeek
+            if (dayOfWeek >= 14) {
+                dayOfWeek -= 14
+            }
+            var color = mCalendarDateBannerTextColor
+            if (mNumDays == 1) {
+                if (dayOfWeek == Time.SATURDAY) {
+                    color = mWeek_saturdayColor
+                } else if (dayOfWeek == Time.SUNDAY) {
+                    color = mWeek_sundayColor
+                }
+            } else {
+                val column = day % 7
+                if (Utils.isSaturday(column, mFirstDayOfWeek)) {
+                    color = mWeek_saturdayColor
+                } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
+                    color = mWeek_sundayColor
+                }
+            }
+            p.setColor(color)
+            drawDayHeader(dayNames!![dayOfWeek], day, cell, canvas, p)
+            day++
+            cell++
+        }
+        p.setTypeface(null)
+    }
+
+    private fun drawAmPm(canvas: Canvas, p: Paint) {
+        p.setColor(mCalendarAmPmLabel)
+        p.setTextSize(AMPM_TEXT_SIZE)
+        p.setTypeface(mBold)
+        p.setAntiAlias(true)
+        p.setTextAlign(Paint.Align.RIGHT)
+        var text = mAmString
+        if (mFirstHour >= 12) {
+            text = mPmString
+        }
+        var y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP
+        canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+        if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
+            // Also draw the "PM"
+            text = mPmString
+            y =
+                mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP) +
+                    2 * mHoursTextHeight + HOUR_GAP
+            canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+        }
+    }
+
+    private fun drawCurrentTimeLine(
+        r: Rect,
+        day: Int,
+        top: Int,
+        canvas: Canvas,
+        p: Paint
+    ) {
+        r.left = computeDayLeftPosition(day) - CURRENT_TIME_LINE_SIDE_BUFFER + 1
+        r.right = computeDayLeftPosition(day + 1) + CURRENT_TIME_LINE_SIDE_BUFFER + 1
+        r.top = top - CURRENT_TIME_LINE_TOP_OFFSET
+        r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight()
+        mCurrentTimeLine.setBounds(r)
+        mCurrentTimeLine.draw(canvas)
+        if (mAnimateToday) {
+            mCurrentTimeAnimateLine.setBounds(r)
+            mCurrentTimeAnimateLine.setAlpha(mAnimateTodayAlpha)
+            mCurrentTimeAnimateLine.draw(canvas)
+        }
+    }
+
+    private fun doDraw(canvas: Canvas) {
+        val p: Paint = mPaint
+        val r: Rect = mRect
+        if (mFutureBgColor != 0) {
+            drawBgColors(r, canvas, p)
+        }
+        drawGridBackground(r, canvas, p)
+        drawHours(r, canvas, p)
+
+        // Draw each day
+        var cell = mFirstJulianDay
+        p.setAntiAlias(false)
+        val alpha: Int = p.getAlpha()
+        p.setAlpha(mEventsAlpha)
+        var day = 0
+        while (day < mNumDays) {
+
+            // TODO Wow, this needs cleanup. drawEvents loop through all the
+            // events on every call.
+            drawEvents(cell, day, HOUR_GAP, canvas, p)
+            // If this is today
+            if (cell == mTodayJulianDay) {
+                val lineY: Int =
+                    mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+                        mCellHeight / 60 + 1
+
+                // And the current time shows up somewhere on the screen
+                if (lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
+                    drawCurrentTimeLine(r, day, lineY, canvas, p)
+                }
+            }
+            day++
+            cell++
+        }
+        p.setAntiAlias(true)
+        p.setAlpha(alpha)
+    }
+
+    private fun drawHours(r: Rect, canvas: Canvas, p: Paint) {
+        setupHourTextPaint(p)
+        var y = HOUR_GAP + mHoursTextHeight + HOURS_TOP_MARGIN
+        for (i in 0..23) {
+            val time = mHourStrs!![i]
+            canvas.drawText(time, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
+            y += mCellHeight + HOUR_GAP
+        }
+    }
+
+    private fun setupHourTextPaint(p: Paint) {
+        p.setColor(mCalendarHourLabelColor)
+        p.setTextSize(HOURS_TEXT_SIZE)
+        p.setTypeface(Typeface.DEFAULT)
+        p.setTextAlign(Paint.Align.RIGHT)
+        p.setAntiAlias(true)
+    }
+
+    private fun drawDayHeader(dayStr: String?, day: Int, cell: Int, canvas: Canvas, p: Paint) {
+        var dateNum = mFirstVisibleDate + day
+        var x: Int
+        if (dateNum > mMonthLength) {
+            dateNum -= mMonthLength
+        }
+        p.setAntiAlias(true)
+        val todayIndex = mTodayJulianDay - mFirstJulianDay
+        // Draw day of the month
+        val dateNumStr: String = dateNum.toString()
+        if (mNumDays > 1) {
+            val y = (DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN).toFloat()
+
+            // Draw day of the month
+            x = computeDayLeftPosition(day + 1) - DAY_HEADER_RIGHT_MARGIN
+            p.setTextAlign(Align.RIGHT)
+            p.setTextSize(DATE_HEADER_FONT_SIZE)
+            p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
+            canvas.drawText(dateNumStr as String, x.toFloat(), y, p)
+
+            // Draw day of the week
+            x -= (p.measureText(" $dateNumStr")).toInt()
+            p.setTextSize(DAY_HEADER_FONT_SIZE)
+            p.setTypeface(Typeface.DEFAULT)
+            canvas.drawText(dayStr as String, x.toFloat(), y, p)
+        } else {
+            val y = (ONE_DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN).toFloat()
+            p.setTextAlign(Align.LEFT)
+
+            // Draw day of the week
+            x = computeDayLeftPosition(day) + DAY_HEADER_ONE_DAY_LEFT_MARGIN
+            p.setTextSize(DAY_HEADER_FONT_SIZE)
+            p.setTypeface(Typeface.DEFAULT)
+            canvas.drawText(dayStr as String, x.toFloat(), y, p)
+
+            // Draw day of the month
+            x += (p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN).toInt()
+            p.setTextSize(DATE_HEADER_FONT_SIZE)
+            p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
+            canvas.drawText(dateNumStr, x.toFloat(), y, p)
+        }
+    }
+
+    private fun drawGridBackground(r: Rect, canvas: Canvas, p: Paint) {
+        val savedStyle: Style = p.getStyle()
+        val stopX = computeDayLeftPosition(mNumDays).toFloat()
+        var y = 0f
+        val deltaY = (mCellHeight + HOUR_GAP).toFloat()
+        var linesIndex = 0
+        val startY = 0f
+        val stopY = (HOUR_GAP + 24 * (mCellHeight + HOUR_GAP)).toFloat()
+        var x = mHoursWidth.toFloat()
+
+        // Draw the inner horizontal grid lines
+        p.setColor(mCalendarGridLineInnerHorizontalColor)
+        p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+        p.setAntiAlias(false)
+        y = 0f
+        linesIndex = 0
+        for (hour in 0..24) {
+            mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN
+            mLines[linesIndex++] = y
+            mLines[linesIndex++] = stopX
+            mLines[linesIndex++] = y
+            y += deltaY
+        }
+        if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
+            canvas.drawLines(mLines, 0, linesIndex, p)
+            linesIndex = 0
+            p.setColor(mCalendarGridLineInnerVerticalColor)
+        }
+
+        // Draw the inner vertical grid lines
+        for (day in 0..mNumDays) {
+            x = computeDayLeftPosition(day).toFloat()
+            mLines[linesIndex++] = x
+            mLines[linesIndex++] = startY
+            mLines[linesIndex++] = x
+            mLines[linesIndex++] = stopY
+        }
+        canvas.drawLines(mLines, 0, linesIndex, p)
+
+        // Restore the saved style.
+        p.setStyle(savedStyle)
+        p.setAntiAlias(true)
+    }
+
+    /**
+     * @param r
+     * @param canvas
+     * @param p
+     */
+    private fun drawBgColors(r: Rect, canvas: Canvas, p: Paint) {
+        val todayIndex = mTodayJulianDay - mFirstJulianDay
+        // Draw the hours background color
+        r.top = mDestRect.top
+        r.bottom = mDestRect.bottom
+        r.left = 0
+        r.right = mHoursWidth
+        p.setColor(mBgColor)
+        p.setStyle(Style.FILL)
+        p.setAntiAlias(false)
+        canvas.drawRect(r, p)
+
+        // Draw background for grid area
+        if (mNumDays == 1 && todayIndex == 0) {
+            // Draw a white background for the time later than current time
+            var lineY: Int =
+                mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+                    mCellHeight / 60 + 1
+            if (lineY < mViewStartY + mViewHeight) {
+                lineY = Math.max(lineY, mViewStartY)
+                r.left = mHoursWidth
+                r.right = mViewWidth
+                r.top = lineY
+                r.bottom = mViewStartY + mViewHeight
+                p.setColor(mFutureBgColor)
+                canvas.drawRect(r, p)
+            }
+        } else if (todayIndex >= 0 && todayIndex < mNumDays) {
+            // Draw today with a white background for the time later than current time
+            var lineY: Int =
+                mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
+                    mCellHeight / 60 + 1
+            if (lineY < mViewStartY + mViewHeight) {
+                lineY = Math.max(lineY, mViewStartY)
+                r.left = computeDayLeftPosition(todayIndex) + 1
+                r.right = computeDayLeftPosition(todayIndex + 1)
+                r.top = lineY
+                r.bottom = mViewStartY + mViewHeight
+                p.setColor(mFutureBgColor)
+                canvas.drawRect(r, p)
+            }
+
+            // Paint Tomorrow and later days with future color
+            if (todayIndex + 1 < mNumDays) {
+                r.left = computeDayLeftPosition(todayIndex + 1) + 1
+                r.right = computeDayLeftPosition(mNumDays)
+                r.top = mDestRect.top
+                r.bottom = mDestRect.bottom
+                p.setColor(mFutureBgColor)
+                canvas.drawRect(r, p)
+            }
+        } else if (todayIndex < 0) {
+            // Future
+            r.left = computeDayLeftPosition(0) + 1
+            r.right = computeDayLeftPosition(mNumDays)
+            r.top = mDestRect.top
+            r.bottom = mDestRect.bottom
+            p.setColor(mFutureBgColor)
+            canvas.drawRect(r, p)
+        }
+        p.setAntiAlias(true)
+    }
+
+    private fun computeMaxStringWidth(currentMax: Int, strings: Array<String?>, p: Paint): Int {
+        var maxWidthF = 0.0f
+        val len = strings.size
+        for (i in 0 until len) {
+            val width: Float = p.measureText(strings[i])
+            maxWidthF = Math.max(width, maxWidthF)
+        }
+        var maxWidth = (maxWidthF + 0.5).toInt()
+        if (maxWidth < currentMax) {
+            maxWidth = currentMax
+        }
+        return maxWidth
+    }
+
+    private fun saveSelectionPosition(left: Float, top: Float, right: Float, bottom: Float) {
+        mPrevBox.left = left.toInt()
+        mPrevBox.right = right.toInt()
+        mPrevBox.top = top.toInt()
+        mPrevBox.bottom = bottom.toInt()
+    }
+
+    private fun setupTextRect(r: Rect) {
+        if (r.bottom <= r.top || r.right <= r.left) {
+            r.bottom = r.top
+            r.right = r.left
+            return
+        }
+        if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
+            r.top += EVENT_TEXT_TOP_MARGIN
+            r.bottom -= EVENT_TEXT_BOTTOM_MARGIN
+        }
+        if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
+            r.left += EVENT_TEXT_LEFT_MARGIN
+            r.right -= EVENT_TEXT_RIGHT_MARGIN
+        }
+    }
+
+    private fun setupAllDayTextRect(r: Rect) {
+        if (r.bottom <= r.top || r.right <= r.left) {
+            r.bottom = r.top
+            r.right = r.left
+            return
+        }
+        if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
+            r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN
+            r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN
+        }
+        if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
+            r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+            r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
+        }
+    }
+
+    /**
+     * Return the layout for a numbered event. Create it if not already existing
+     */
+    private fun getEventLayout(
+        layouts: Array<StaticLayout?>?,
+        i: Int,
+        event: Event,
+        paint: Paint,
+        r: Rect
+    ): StaticLayout? {
+        if (i < 0 || i >= layouts!!.size) {
+            return null
+        }
+        var layout: StaticLayout? = layouts!![i]
+        // Check if we have already initialized the StaticLayout and that
+        // the width hasn't changed (due to vertical resizing which causes
+        // re-layout of events at min height)
+        if (layout == null || r.width() !== layout.getWidth()) {
+            val bob = SpannableStringBuilder()
+            if (event.title != null) {
+                // MAX - 1 since we add a space
+                bob.append(drawTextSanitizer(event.title.toString(),
+                    MAX_EVENT_TEXT_LEN - 1))
+                bob.setSpan(StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length, 0)
+                bob.append(' ')
+            }
+            if (event.location != null) {
+                bob.append(
+                    drawTextSanitizer(
+                        event.location.toString(),
+                        MAX_EVENT_TEXT_LEN - bob.length
+                    )
+                )
+            }
+            when (event.selfAttendeeStatus) {
+                Attendees.ATTENDEE_STATUS_INVITED -> paint.setColor(event.color)
+                Attendees.ATTENDEE_STATUS_DECLINED -> {
+                    paint.setColor(mEventTextColor)
+                    paint.setAlpha(Utils.DECLINED_EVENT_TEXT_ALPHA)
+                }
+                Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+                    Attendees.ATTENDEE_STATUS_TENTATIVE -> paint.setColor(
+                    mEventTextColor
+                )
+                else -> paint.setColor(mEventTextColor)
+            }
+
+            // Leave a one pixel boundary on the left and right of the rectangle for the event
+            layout = StaticLayout(
+                bob, 0, bob.length, TextPaint(paint), r.width(),
+                Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width()
+            )
+            layouts[i] = layout
+        }
+        layout.getPaint().setAlpha(mEventsAlpha)
+        return layout
+    }
+
+    private fun drawAllDayEvents(firstDay: Int, numDays: Int, canvas: Canvas, p: Paint) {
+        p.setTextSize(NORMAL_FONT_SIZE)
+        p.setTextAlign(Paint.Align.LEFT)
+        val eventTextPaint: Paint = mEventTextPaint
+        val startY = DAY_HEADER_HEIGHT.toFloat()
+        val stopY = startY + mAlldayHeight + ALLDAY_TOP_MARGIN
+        var x = 0f
+        var linesIndex = 0
+
+        // Draw the inner vertical grid lines
+        p.setColor(mCalendarGridLineInnerVerticalColor)
+        x = mHoursWidth.toFloat()
+        p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
+        // Line bounding the top of the all day area
+        mLines!![linesIndex++] = GRID_LINE_LEFT_MARGIN
+        mLines!![linesIndex++] = startY
+        mLines!![linesIndex++] = computeDayLeftPosition(mNumDays).toFloat()
+        mLines!![linesIndex++] = startY
+        for (day in 0..mNumDays) {
+            x = computeDayLeftPosition(day).toFloat()
+            mLines!![linesIndex++] = x
+            mLines!![linesIndex++] = startY
+            mLines!![linesIndex++] = x
+            mLines!![linesIndex++] = stopY
+        }
+        p.setAntiAlias(false)
+        canvas.drawLines(mLines, 0, linesIndex, p)
+        p.setStyle(Style.FILL)
+        val y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+        val lastDay = firstDay + numDays - 1
+        val events: ArrayList<Event>? = mAllDayEvents
+        val numEvents: Int = events!!.size
+        // Whether or not we should draw the more events text
+        var hasMoreEvents = false
+        // size of the allDay area
+        val drawHeight = mAlldayHeight.toFloat()
+        // max number of events being drawn in one day of the allday area
+        var numRectangles = mMaxAlldayEvents.toFloat()
+        // Where to cut off drawn allday events
+        var allDayEventClip = DAY_HEADER_HEIGHT + mAlldayHeight + ALLDAY_TOP_MARGIN
+        // The number of events that weren't drawn in each day
+        mSkippedAlldayEvents = IntArray(numDays)
+        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount &&
+            !mShowAllAllDayEvents && mAnimateDayHeight == 0) {
+            // We draw one fewer event than will fit so that more events text
+            // can be drawn
+            numRectangles = (mMaxUnexpandedAlldayEventCount - 1).toFloat()
+            // We also clip the events above the more events text
+            allDayEventClip -= MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+            hasMoreEvents = true
+        } else if (mAnimateDayHeight != 0) {
+            // clip at the end of the animating space
+            allDayEventClip = DAY_HEADER_HEIGHT + mAnimateDayHeight + ALLDAY_TOP_MARGIN
+        }
+        var alpha: Int = eventTextPaint.getAlpha()
+        eventTextPaint.setAlpha(mEventsAlpha)
+        for (i in 0 until numEvents) {
+            val event: Event = events!!.get(i)
+            var startDay: Int = event.startDay
+            var endDay: Int = event.endDay
+            if (startDay > lastDay || endDay < firstDay) {
+                continue
+            }
+            if (startDay < firstDay) {
+                startDay = firstDay
+            }
+            if (endDay > lastDay) {
+                endDay = lastDay
+            }
+            val startIndex = startDay - firstDay
+            val endIndex = endDay - firstDay
+            var height =
+                if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount)
+                    mAnimateDayEventHeight.toFloat() else drawHeight / numRectangles
+
+            // Prevent a single event from getting too big
+            if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
+                height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
+            }
+
+            // Leave a one-pixel space between the vertical day lines and the
+            // event rectangle.
+            event.left = computeDayLeftPosition(startIndex).toFloat()
+            event.right = computeDayLeftPosition(endIndex + 1).toFloat() - DAY_GAP
+            event.top = y + height * event.getColumn()
+            event.bottom = event.top + height - ALL_DAY_EVENT_RECT_BOTTOM_MARGIN
+            if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+                // check if we should skip this event. We skip if it starts
+                // after the clip bound or ends after the skip bound and we're
+                // not animating.
+                if (event.top >= allDayEventClip) {
+                    incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
+                    continue
+                } else if (event.bottom > allDayEventClip) {
+                    if (hasMoreEvents) {
+                        incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
+                        continue
+                    }
+                    event.bottom = allDayEventClip.toFloat()
+                }
+            }
+            val r: Rect = drawEventRect(
+                event, canvas, p, eventTextPaint, event.top.toInt(),
+                event.bottom.toInt()
+            )
+            setupAllDayTextRect(r)
+            val layout: StaticLayout? = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r)
+            drawEventText(layout, r, canvas, r.top, r.bottom, true)
+
+            // Check if this all-day event intersects the selected day
+            if (mSelectionAllday && mComputeSelectedEvents) {
+                if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
+                    mSelectedEvents.add(event)
+                }
+            }
+        }
+        eventTextPaint.setAlpha(alpha)
+        if (mMoreAlldayEventsTextAlpha != 0 && mSkippedAlldayEvents != null) {
+            // If the more allday text should be visible, draw it.
+            alpha = p.getAlpha()
+            p.setAlpha(mEventsAlpha)
+            p.setColor(mMoreAlldayEventsTextAlpha shl 24 and mMoreEventsTextColor)
+            for (i in mSkippedAlldayEvents!!.indices) {
+                if (mSkippedAlldayEvents!![i] > 0) {
+                    drawMoreAlldayEvents(canvas, mSkippedAlldayEvents!![i], i, p)
+                }
+            }
+            p.setAlpha(alpha)
+        }
+        if (mSelectionAllday) {
+            // Compute the neighbors for the list of all-day events that
+            // intersect the selected day.
+            computeAllDayNeighbors()
+
+            // Set the selection position to zero so that when we move down
+            // to the normal event area, we will highlight the topmost event.
+            saveSelectionPosition(0f, 0f, 0f, 0f)
+        }
+    }
+
+    // Helper method for counting the number of allday events skipped on each day
+    private fun incrementSkipCount(counts: IntArray?, startIndex: Int, endIndex: Int) {
+        if (counts == null || startIndex < 0 || endIndex > counts.size) {
+            return
+        }
+        for (i in startIndex..endIndex) {
+            counts[i]++
+        }
+    }
+
+    // Draws the "box +n" text for hidden allday events
+    protected fun drawMoreAlldayEvents(canvas: Canvas, remainingEvents: Int, day: Int, p: Paint) {
+        var x = computeDayLeftPosition(day) + EVENT_ALL_DAY_TEXT_LEFT_MARGIN
+        var y = (mAlldayHeight - .5f * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - (.5f *
+            EVENT_SQUARE_WIDTH) + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN).toInt()
+        val r: Rect = mRect
+        r.top = y
+        r.left = x
+        r.bottom = y + EVENT_SQUARE_WIDTH
+        r.right = x + EVENT_SQUARE_WIDTH
+        p.setColor(mMoreEventsTextColor)
+        p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
+        p.setStyle(Style.STROKE)
+        p.setAntiAlias(false)
+        canvas.drawRect(r, p)
+        p.setAntiAlias(true)
+        p.setStyle(Style.FILL)
+        p.setTextSize(EVENT_TEXT_FONT_SIZE)
+        val text: String =
+            mResources.getQuantityString(R.plurals.month_more_events, remainingEvents)
+        y += EVENT_SQUARE_WIDTH
+        x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING
+        canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(), p)
+    }
+
+    private fun computeAllDayNeighbors() {
+        val len: Int = mSelectedEvents.size
+        if (len == 0 || mSelectedEvent != null) {
+            return
+        }
+
+        // First, clear all the links
+        for (ii in 0 until len) {
+            val ev: Event = mSelectedEvents.get(ii)
+            ev.nextUp = null
+            ev.nextDown = null
+            ev.nextLeft = null
+            ev.nextRight = null
+        }
+
+        // For each event in the selected event list "mSelectedEvents", find
+        // its neighbors in the up and down directions. This could be done
+        // more efficiently by sorting on the Event.getColumn() field, but
+        // the list is expected to be very small.
+
+        // Find the event in the same row as the previously selected all-day
+        // event, if any.
+        var startPosition = -1
+        if (mPrevSelectedEvent != null && mPrevSelectedEvent!!.drawAsAllday()) {
+            startPosition = mPrevSelectedEvent?.getColumn() as Int
+        }
+        var maxPosition = -1
+        var startEvent: Event? = null
+        var maxPositionEvent: Event? = null
+        for (ii in 0 until len) {
+            val ev: Event = mSelectedEvents.get(ii)
+            val position: Int = ev.getColumn()
+            if (position == startPosition) {
+                startEvent = ev
+            } else if (position > maxPosition) {
+                maxPositionEvent = ev
+                maxPosition = position
+            }
+            for (jj in 0 until len) {
+                if (jj == ii) {
+                    continue
+                }
+                val neighbor: Event = mSelectedEvents.get(jj)
+                val neighborPosition: Int = neighbor.getColumn()
+                if (neighborPosition == position - 1) {
+                    ev.nextUp = neighbor
+                } else if (neighborPosition == position + 1) {
+                    ev.nextDown = neighbor
+                }
+            }
+        }
+        if (startEvent != null) {
+            setSelectedEvent(startEvent)
+        } else {
+            setSelectedEvent(maxPositionEvent)
+        }
+    }
+
+    private fun drawEvents(date: Int, dayIndex: Int, top: Int, canvas: Canvas, p: Paint) {
+        val eventTextPaint: Paint = mEventTextPaint
+        val left = computeDayLeftPosition(dayIndex) + 1
+        val cellWidth = computeDayLeftPosition(dayIndex + 1) - left + 1
+        val cellHeight = mCellHeight
+
+        // Use the selected hour as the selection region
+        val selectionArea: Rect = mSelectionRect
+        selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP)
+        selectionArea.bottom = selectionArea.top + cellHeight
+        selectionArea.left = left
+        selectionArea.right = selectionArea.left + cellWidth
+        val events: ArrayList<Event> = mEvents
+        val numEvents: Int = events.size
+        val geometry: EventGeometry = mEventGeometry
+        val viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight
+        val alpha: Int = eventTextPaint.getAlpha()
+        eventTextPaint.setAlpha(mEventsAlpha)
+        for (i in 0 until numEvents) {
+            val event: Event = events.get(i)
+            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+                continue
+            }
+
+            // Don't draw it if it is not visible
+            if (event.bottom < mViewStartY || event.top > viewEndY) {
+                continue
+            }
+            if (date == mSelectionDay && !mSelectionAllday && mComputeSelectedEvents &&
+                geometry.eventIntersectsSelection(event, selectionArea)
+            ) {
+                mSelectedEvents.add(event)
+            }
+            val r: Rect = drawEventRect(event, canvas, p, eventTextPaint, mViewStartY, viewEndY)
+            setupTextRect(r)
+
+            // Don't draw text if it is not visible
+            if (r.top > viewEndY || r.bottom < mViewStartY) {
+                continue
+            }
+            val layout: StaticLayout? = getEventLayout(mLayouts, i, event, eventTextPaint, r)
+            // TODO: not sure why we are 4 pixels off
+            drawEventText(
+                layout,
+                r,
+                canvas,
+                mViewStartY + 4,
+                mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight,
+                false
+            )
+        }
+        eventTextPaint.setAlpha(alpha)
+    }
+
+    private fun drawEventRect(
+        event: Event,
+        canvas: Canvas,
+        p: Paint,
+        eventTextPaint: Paint,
+        visibleTop: Int,
+        visibleBot: Int
+    ): Rect {
+        // Draw the Event Rect
+        val r: Rect = mRect
+        r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN, visibleTop)
+        r.bottom = Math.min(event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN, visibleBot)
+        r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
+        r.right = event.right.toInt()
+        var color: Int = event.color
+        when (event.selfAttendeeStatus) {
+            Attendees.ATTENDEE_STATUS_INVITED -> if (event !== mClickedEvent) {
+                p.setStyle(Style.STROKE)
+            }
+            Attendees.ATTENDEE_STATUS_DECLINED -> {
+                if (event !== mClickedEvent) {
+                    color = Utils.getDeclinedColorFromColor(color)
+                }
+                p.setStyle(Style.FILL_AND_STROKE)
+            }
+            Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
+                Attendees.ATTENDEE_STATUS_TENTATIVE -> p.setStyle(
+                Style.FILL_AND_STROKE
+            )
+            else -> p.setStyle(Style.FILL_AND_STROKE)
+        }
+        p.setAntiAlias(false)
+        val floorHalfStroke = Math.floor(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
+        val ceilHalfStroke = Math.ceil(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
+        r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN + floorHalfStroke, visibleTop)
+        r.bottom = Math.min(
+            event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN - ceilHalfStroke,
+            visibleBot
+        )
+        r.left += floorHalfStroke
+        r.right -= ceilHalfStroke
+        p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
+        p.setColor(color)
+        val alpha: Int = p.getAlpha()
+        p.setAlpha(mEventsAlpha)
+        canvas.drawRect(r, p)
+        p.setAlpha(alpha)
+        p.setStyle(Style.FILL)
+
+        // Setup rect for drawEventText which follows
+        r.top = event.top.toInt() + EVENT_RECT_TOP_MARGIN
+        r.bottom = event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN
+        r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
+        r.right = event.right.toInt() - EVENT_RECT_RIGHT_MARGIN
+        return r
+    }
+
+    private val drawTextSanitizerFilter: Pattern = Pattern.compile("[\t\n],")
+
+    // Sanitize a string before passing it to drawText or else we get little
+    // squares. For newlines and tabs before a comma, delete the character.
+    // Otherwise, just replace them with a space.
+    private fun drawTextSanitizer(string: String, maxEventTextLen: Int): String {
+        var string = string
+        val m: Matcher = drawTextSanitizerFilter.matcher(string)
+        string = m.replaceAll(",")
+        var len: Int = string.length
+        if (maxEventTextLen <= 0) {
+            string = ""
+            len = 0
+        } else if (len > maxEventTextLen) {
+            string = string.substring(0, maxEventTextLen)
+            len = maxEventTextLen
+        }
+        return string.replace('\n', ' ')
+    }
+
+    private fun drawEventText(
+        eventLayout: StaticLayout?,
+        rect: Rect,
+        canvas: Canvas,
+        top: Int,
+        bottom: Int,
+        center: Boolean
+    ) {
+        // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
+        val width: Int = rect.right - rect.left
+        val height: Int = rect.bottom - rect.top
+
+        // If the rectangle is too small for text, then return
+        if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
+            return
+        }
+        var totalLineHeight = 0
+        val lineCount: Int = eventLayout.getLineCount()
+        for (i in 0 until lineCount) {
+            val lineBottom: Int = eventLayout.getLineBottom(i)
+            totalLineHeight = if (lineBottom <= height) {
+                lineBottom
+            } else {
+                break
+            }
+        }
+
+        // + 2 is small workaround when the font is slightly bigger than the rect. This will
+        // still allow the text to be shown without overflowing into the other all day rects.
+        if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight + 2 < top) {
+            return
+        }
+
+        // Use a StaticLayout to format the string.
+        canvas.save()
+        //  canvas.translate(rect.left, rect.top + (rect.bottom - rect.top / 2));
+        val padding = if (center) (rect.bottom - rect.top - totalLineHeight) / 2 else 0
+        canvas.translate(rect.left.toFloat(), rect.top.toFloat() + padding)
+        rect.left = 0
+        rect.right = width
+        rect.top = 0
+        rect.bottom = totalLineHeight
+
+        // There's a bug somewhere. If this rect is outside of a previous
+        // cliprect, this becomes a no-op. What happens is that the text draw
+        // past the event rect. The current fix is to not draw the staticLayout
+        // at all if it is completely out of bound.
+        canvas.clipRect(rect)
+        eventLayout.draw(canvas)
+        canvas.restore()
+    }
+
+    // The following routines are called from the parent activity when certain
+    // touch events occur.
+    private fun doDown(ev: MotionEvent) {
+        mTouchMode = TOUCH_MODE_DOWN
+        mViewStartX = 0
+        mOnFlingCalled = false
+        mHandler?.removeCallbacks(mContinueScroll)
+        val x = ev.getX().toInt()
+        val y = ev.getY().toInt()
+
+        // Save selection information: we use setSelectionFromPosition to find the selected event
+        // in order to show the "clicked" color. But since it is also setting the selected info
+        // for new events, we need to restore the old info after calling the function.
+        val oldSelectedEvent: Event? = mSelectedEvent
+        val oldSelectionDay = mSelectionDay
+        val oldSelectionHour = mSelectionHour
+        if (setSelectionFromPosition(x, y, false)) {
+            // If a time was selected (a blue selection box is visible) and the click location
+            // is in the selected time, do not show a click on an event to prevent a situation
+            // of both a selection and an event are clicked when they overlap.
+            val pressedSelected = (mSelectionMode != SELECTION_HIDDEN &&
+                oldSelectionDay == mSelectionDay && oldSelectionHour == mSelectionHour)
+            if (!pressedSelected && mSelectedEvent != null) {
+                mSavedClickedEvent = mSelectedEvent
+                mDownTouchTime = System.currentTimeMillis()
+                postDelayed(mSetClick, mOnDownDelay.toLong())
+            } else {
+                eventClickCleanup()
+            }
+        }
+        mSelectedEvent = oldSelectedEvent
+        mSelectionDay = oldSelectionDay
+        mSelectionHour = oldSelectionHour
+        invalidate()
+    }
+
+    // Kicks off all the animations when the expand allday area is tapped
+    private fun doExpandAllDayClick() {
+        mShowAllAllDayEvents = !mShowAllAllDayEvents
+        ObjectAnimator.setFrameDelay(0)
+
+        // Determine the starting height
+        if (mAnimateDayHeight == 0) {
+            mAnimateDayHeight =
+                if (mShowAllAllDayEvents) mAlldayHeight - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+                else mAlldayHeight
+        }
+        // Cancel current animations
+        mCancellingAnimations = true
+        if (mAlldayAnimator != null) {
+            mAlldayAnimator?.cancel()
+        }
+        if (mAlldayEventAnimator != null) {
+            mAlldayEventAnimator?.cancel()
+        }
+        if (mMoreAlldayEventsAnimator != null) {
+            mMoreAlldayEventsAnimator?.cancel()
+        }
+        mCancellingAnimations = false
+        // get new animators
+        mAlldayAnimator = allDayAnimator
+        mAlldayEventAnimator = allDayEventAnimator
+        mMoreAlldayEventsAnimator = ObjectAnimator.ofInt(
+            this,
+            "moreAllDayEventsTextAlpha",
+            if (mShowAllAllDayEvents) MORE_EVENTS_MAX_ALPHA else 0,
+            if (mShowAllAllDayEvents) 0 else MORE_EVENTS_MAX_ALPHA
+        )
+
+        // Set up delays and start the animators
+        mAlldayAnimator?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION
+            else 0)
+        mAlldayAnimator?.start()
+        mMoreAlldayEventsAnimator?.setStartDelay(if (mShowAllAllDayEvents) 0
+            else ANIMATION_DURATION)
+        mMoreAlldayEventsAnimator?.setDuration(ANIMATION_SECONDARY_DURATION)
+        mMoreAlldayEventsAnimator?.start()
+        if (mAlldayEventAnimator != null) {
+            // This is the only animator that can return null, so check it
+            mAlldayEventAnimator
+                ?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION else 0)
+            mAlldayEventAnimator?.start()
+        }
+    }
+
+    /**
+     * Figures out the initial heights for allDay events and space when
+     * a view is being set up.
+     */
+    fun initAllDayHeights() {
+        if (mMaxAlldayEvents <= mMaxUnexpandedAlldayEventCount) {
+            return
+        }
+        if (mShowAllAllDayEvents) {
+            var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+            maxADHeight = Math.min(
+                maxADHeight,
+                (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+            )
+            mAnimateDayEventHeight = maxADHeight / mMaxAlldayEvents
+        } else {
+            mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+        }
+    } // First calculate the absolute max height
+    // Now expand to fit but not beyond the absolute max
+    // calculate the height of individual events in order to fit
+    // if there's nothing to animate just return
+
+    // Set up the animator with the calculated values
+    // Sets up an animator for changing the height of allday events
+    private val allDayEventAnimator: ObjectAnimator?
+        private get() {
+            // First calculate the absolute max height
+            var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+            // Now expand to fit but not beyond the absolute max
+            maxADHeight = Math.min(
+                maxADHeight,
+                (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+            )
+            // calculate the height of individual events in order to fit
+            val fitHeight = maxADHeight / mMaxAlldayEvents
+            val currentHeight = mAnimateDayEventHeight
+            val desiredHeight =
+                if (mShowAllAllDayEvents) fitHeight else MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+            // if there's nothing to animate just return
+            if (currentHeight == desiredHeight) {
+                return null
+            }
+
+            // Set up the animator with the calculated values
+            val animator: ObjectAnimator = ObjectAnimator.ofInt(
+                this, "animateDayEventHeight",
+                currentHeight, desiredHeight
+            )
+            animator.setDuration(ANIMATION_DURATION)
+            return animator
+        }
+
+    // Set up the animator with the calculated values
+    // Sets up an animator for changing the height of the allday area
+    private val allDayAnimator: ObjectAnimator
+        private get() {
+            // Calculate the absolute max height
+            var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
+            // Find the desired height but don't exceed abs max
+            maxADHeight = Math.min(
+                maxADHeight,
+                (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
+            )
+            // calculate the current and desired heights
+            val currentHeight = if (mAnimateDayHeight != 0) mAnimateDayHeight else mAlldayHeight
+            val desiredHeight =
+                if (mShowAllAllDayEvents) maxADHeight else (MAX_UNEXPANDED_ALLDAY_HEIGHT -
+                    MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - 1).toInt()
+
+            // Set up the animator with the calculated values
+            val animator: ObjectAnimator = ObjectAnimator.ofInt(
+                this, "animateDayHeight",
+                currentHeight, desiredHeight
+            )
+            animator.setDuration(ANIMATION_DURATION)
+            animator.addListener(object : AnimatorListenerAdapter() {
+                @Override
+                override fun onAnimationEnd(animation: Animator?) {
+                    if (!mCancellingAnimations) {
+                        // when finished, set this to 0 to signify not animating
+                        mAnimateDayHeight = 0
+                        mUseExpandIcon = !mShowAllAllDayEvents
+                    }
+                    mRemeasure = true
+                    invalidate()
+                }
+            })
+            return animator
+        }
+
+    // setter for the 'box +n' alpha text used by the animator
+    fun setMoreAllDayEventsTextAlpha(alpha: Int) {
+        mMoreAlldayEventsTextAlpha = alpha
+        invalidate()
+    }
+
+    // setter for the height of the allday area used by the animator
+    fun setAnimateDayHeight(height: Int) {
+        mAnimateDayHeight = height
+        mRemeasure = true
+        invalidate()
+    }
+
+    // setter for the height of allday events used by the animator
+    fun setAnimateDayEventHeight(height: Int) {
+        mAnimateDayEventHeight = height
+        mRemeasure = true
+        invalidate()
+    }
+
+    private fun doSingleTapUp(ev: MotionEvent) {
+        if (!mHandleActionUp || mScrolling) {
+            return
+        }
+        val x = ev.getX().toInt()
+        val y = ev.getY().toInt()
+        val selectedDay = mSelectionDay
+        val selectedHour = mSelectionHour
+        if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+            // check if the tap was in the allday expansion area
+            val bottom = mFirstCell
+            if (x < mHoursWidth && y > DAY_HEADER_HEIGHT && y < DAY_HEADER_HEIGHT + mAlldayHeight ||
+                !mShowAllAllDayEvents && mAnimateDayHeight == 0 && y < bottom && y >= bottom -
+                MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT
+            ) {
+                doExpandAllDayClick()
+                return
+            }
+        }
+        val validPosition = setSelectionFromPosition(x, y, false)
+        if (!validPosition) {
+            if (y < DAY_HEADER_HEIGHT) {
+                val selectedTime = Time(mBaseDate)
+                selectedTime.setJulianDay(mSelectionDay)
+                selectedTime.hour = mSelectionHour
+                selectedTime.normalize(true /* ignore isDst */)
+                mController.sendEvent(
+                    this as? Object, EventType.GO_TO, null, null, selectedTime, -1,
+                    ViewType.DAY, CalendarController.EXTRA_GOTO_DATE, null, null
+                )
+            }
+            return
+        }
+        val hasSelection = mSelectionMode != SELECTION_HIDDEN
+        val pressedSelected = ((hasSelection || mTouchExplorationEnabled) &&
+            selectedDay == mSelectionDay && selectedHour == mSelectionHour)
+        if (mSelectedEvent != null) {
+            // If the tap is on an event, launch the "View event" view
+            if (mIsAccessibilityEnabled) {
+                mAccessibilityMgr?.interrupt()
+            }
+            mSelectionMode = SELECTION_HIDDEN
+            var yLocation = ((mSelectedEvent!!.top + mSelectedEvent!!.bottom) / 2) as Int
+            // Y location is affected by the position of the event in the scrolling
+            // view (mViewStartY) and the presence of all day events (mFirstCell)
+            if (!mSelectedEvent!!.allDay) {
+                yLocation += mFirstCell - mViewStartY
+            }
+            mClickedYLocation = yLocation
+            val clearDelay: Long = CLICK_DISPLAY_DURATION + mOnDownDelay -
+                (System.currentTimeMillis() - mDownTouchTime)
+            if (clearDelay > 0) {
+                this.postDelayed(mClearClick, clearDelay)
+            } else {
+                this.post(mClearClick)
+            }
+        }
+        invalidate()
+    }
+
+    private fun doLongPress(ev: MotionEvent) {
+        eventClickCleanup()
+        if (mScrolling) {
+            return
+        }
+
+        // Scale gesture in progress
+        if (mStartingSpanY != 0f) {
+            return
+        }
+        val x = ev.getX().toInt()
+        val y = ev.getY().toInt()
+        val validPosition = setSelectionFromPosition(x, y, false)
+        if (!validPosition) {
+            // return if the touch wasn't on an area of concern
+            return
+        }
+        invalidate()
+        performLongClick()
+    }
+
+    private fun doScroll(e1: MotionEvent, e2: MotionEvent, deltaX: Float, deltaY: Float) {
+        cancelAnimation()
+        if (mStartingScroll) {
+            mInitialScrollX = 0f
+            mInitialScrollY = 0f
+            mStartingScroll = false
+        }
+        mInitialScrollX += deltaX
+        mInitialScrollY += deltaY
+        val distanceX = mInitialScrollX.toInt()
+        val distanceY = mInitialScrollY.toInt()
+        val focusY = getAverageY(e2)
+        if (mRecalCenterHour) {
+            // Calculate the hour that correspond to the average of the Y touch points
+            mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
+                (mCellHeight + DAY_GAP))
+            mRecalCenterHour = false
+        }
+
+        // If we haven't figured out the predominant scroll direction yet,
+        // then do it now.
+        if (mTouchMode == TOUCH_MODE_DOWN) {
+            val absDistanceX: Int = Math.abs(distanceX)
+            val absDistanceY: Int = Math.abs(distanceY)
+            mScrollStartY = mViewStartY
+            mPreviousDirection = 0
+            if (absDistanceX > absDistanceY) {
+                val slopFactor = if (mScaleGestureDetector.isInProgress()) 20 else 2
+                if (absDistanceX > mScaledPagingTouchSlop * slopFactor) {
+                    mTouchMode = TOUCH_MODE_HSCROLL
+                    mViewStartX = distanceX
+                    initNextView(-mViewStartX)
+                }
+            } else {
+                mTouchMode = TOUCH_MODE_VSCROLL
+            }
+        } else if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+            // We are already scrolling horizontally, so check if we
+            // changed the direction of scrolling so that the other week
+            // is now visible.
+            mViewStartX = distanceX
+            if (distanceX != 0) {
+                val direction = if (distanceX > 0) 1 else -1
+                if (direction != mPreviousDirection) {
+                    // The user has switched the direction of scrolling
+                    // so re-init the next view
+                    initNextView(-mViewStartX)
+                    mPreviousDirection = direction
+                }
+            }
+        }
+        if (mTouchMode and TOUCH_MODE_VSCROLL != 0) {
+            // Calculate the top of the visible region in the calendar grid.
+            // Increasing/decrease this will scroll the calendar grid up/down.
+            mViewStartY = ((mGestureCenterHour * (mCellHeight + DAY_GAP) -
+                focusY) + DAY_HEADER_HEIGHT + mAlldayHeight).toInt()
+
+            // If dragging while already at the end, do a glow
+            val pulledToY = (mScrollStartY + deltaY).toInt()
+            if (pulledToY < 0) {
+                mEdgeEffectTop.onPull(deltaY / mViewHeight)
+                if (!mEdgeEffectBottom.isFinished()) {
+                    mEdgeEffectBottom.onRelease()
+                }
+            } else if (pulledToY > mMaxViewStartY) {
+                mEdgeEffectBottom.onPull(deltaY / mViewHeight)
+                if (!mEdgeEffectTop.isFinished()) {
+                    mEdgeEffectTop.onRelease()
+                }
+            }
+            if (mViewStartY < 0) {
+                mViewStartY = 0
+                mRecalCenterHour = true
+            } else if (mViewStartY > mMaxViewStartY) {
+                mViewStartY = mMaxViewStartY
+                mRecalCenterHour = true
+            }
+            if (mRecalCenterHour) {
+                // Calculate the hour that correspond to the average of the Y touch points
+                mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
+                    (mCellHeight + DAY_GAP))
+                mRecalCenterHour = false
+            }
+            computeFirstHour()
+        }
+        mScrolling = true
+        mSelectionMode = SELECTION_HIDDEN
+        invalidate()
+    }
+
+    private fun getAverageY(me: MotionEvent): Float {
+        val count: Int = me.getPointerCount()
+        var focusY = 0f
+        for (i in 0 until count) {
+            focusY += me.getY(i)
+        }
+        focusY /= count.toFloat()
+        return focusY
+    }
+
+    private fun cancelAnimation() {
+        val `in`: Animation? = mViewSwitcher?.getInAnimation()
+        if (`in` != null) {
+            // cancel() doesn't terminate cleanly.
+            `in`?.scaleCurrentDuration(0f)
+        }
+        val out: Animation? = mViewSwitcher?.getOutAnimation()
+        if (out != null) {
+            // cancel() doesn't terminate cleanly.
+            out?.scaleCurrentDuration(0f)
+        }
+    }
+
+    private fun doFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float) {
+        cancelAnimation()
+        mSelectionMode = SELECTION_HIDDEN
+        eventClickCleanup()
+        mOnFlingCalled = true
+        if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+            // Horizontal fling.
+            // initNextView(deltaX);
+            mTouchMode = TOUCH_MODE_INITIAL_STATE
+            if (DEBUG) Log.d(TAG, "doFling: velocityX $velocityX")
+            val deltaX = e2.getX().toInt() - e1.getX().toInt()
+            switchViews(deltaX < 0, mViewStartX.toFloat(), mViewWidth.toFloat(), velocityX)
+            mViewStartX = 0
+            return
+        }
+        if (mTouchMode and TOUCH_MODE_VSCROLL == 0) {
+            if (DEBUG) Log.d(TAG, "doFling: no fling")
+            return
+        }
+
+        // Vertical fling.
+        mTouchMode = TOUCH_MODE_INITIAL_STATE
+        mViewStartX = 0
+        if (DEBUG) {
+            Log.d(TAG, "doFling: mViewStartY$mViewStartY velocityY $velocityY")
+        }
+
+        // Continue scrolling vertically
+        mScrolling = true
+        mScroller.fling(
+            0 /* startX */, mViewStartY /* startY */, 0 /* velocityX */,
+            (-velocityY).toInt(), 0 /* minX */, 0 /* maxX */, 0 /* minY */,
+            mMaxViewStartY /* maxY */, OVERFLING_DISTANCE, OVERFLING_DISTANCE
+        )
+
+        // When flinging down, show a glow when it hits the end only if it
+        // wasn't started at the top
+        if (velocityY > 0 && mViewStartY != 0) {
+            mCallEdgeEffectOnAbsorb = true
+        } else if (velocityY < 0 && mViewStartY != mMaxViewStartY) {
+            mCallEdgeEffectOnAbsorb = true
+        }
+        mHandler?.post(mContinueScroll)
+    }
+
+    private fun initNextView(deltaX: Int): Boolean {
+        // Change the view to the previous day or week
+        val view = mViewSwitcher.getNextView() as DayView
+        val date: Time? = view.mBaseDate
+        date?.set(mBaseDate)
+        val switchForward: Boolean
+        if (deltaX > 0) {
+            date!!.monthDay -= mNumDays
+            view.setSelectedDay(mSelectionDay - mNumDays)
+            switchForward = false
+        } else {
+            date!!.monthDay += mNumDays
+            view.setSelectedDay(mSelectionDay + mNumDays)
+            switchForward = true
+        }
+        date?.normalize(true /* ignore isDst */)
+        initView(view)
+        view.layout(getLeft(), getTop(), getRight(), getBottom())
+        view.reloadEvents()
+        return switchForward
+    }
+
+    // ScaleGestureDetector.OnScaleGestureListener
+    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+        mHandleActionUp = false
+        val gestureCenterInPixels: Float = detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight
+        mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP)
+        mStartingSpanY = Math.max(MIN_Y_SPAN.toFloat(),
+            Math.abs(detector.getCurrentSpanY().toFloat()))
+        mCellHeightBeforeScaleGesture = mCellHeight
+        if (DEBUG_SCALING) {
+            val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
+            Log.d(
+                TAG, "onScaleBegin: mGestureCenterHour:" + mGestureCenterHour +
+                    "\tViewStartHour: " + ViewStartHour + "\tmViewStartY:" + mViewStartY +
+                    "\tmCellHeight:" + mCellHeight + " SpanY:" + detector.getCurrentSpanY()
+            )
+        }
+        return true
+    }
+
+    // ScaleGestureDetector.OnScaleGestureListener
+    override fun onScale(detector: ScaleGestureDetector): Boolean {
+        val spanY: Float = Math.max(MIN_Y_SPAN.toFloat(),
+            Math.abs(detector.getCurrentSpanY().toFloat()))
+        mCellHeight = (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY).toInt()
+        if (mCellHeight < mMinCellHeight) {
+            // If mStartingSpanY is too small, even a small increase in the
+            // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
+            mStartingSpanY = spanY
+            mCellHeight = mMinCellHeight
+            mCellHeightBeforeScaleGesture = mMinCellHeight
+        } else if (mCellHeight > MAX_CELL_HEIGHT) {
+            mStartingSpanY = spanY
+            mCellHeight = MAX_CELL_HEIGHT
+            mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT
+        }
+        val gestureCenterInPixels = detector.getFocusY().toInt() - DAY_HEADER_HEIGHT - mAlldayHeight
+        mViewStartY = (mGestureCenterHour * (mCellHeight + DAY_GAP)).toInt() - gestureCenterInPixels
+        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
+        if (DEBUG_SCALING) {
+            val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
+            Log.d(
+                TAG, "onScale: mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: " +
+                    ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:" +
+                    mCellHeight + " SpanY:" + detector.getCurrentSpanY()
+            )
+        }
+        if (mViewStartY < 0) {
+            mViewStartY = 0
+            mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
+                (mCellHeight + DAY_GAP).toFloat())
+        } else if (mViewStartY > mMaxViewStartY) {
+            mViewStartY = mMaxViewStartY
+            mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
+                (mCellHeight + DAY_GAP).toFloat())
+        }
+        computeFirstHour()
+        mRemeasure = true
+        invalidate()
+        return true
+    }
+
+    // ScaleGestureDetector.OnScaleGestureListener
+    override fun onScaleEnd(detector: ScaleGestureDetector?) {
+        mScrollStartY = mViewStartY
+        mInitialScrollY = 0f
+        mInitialScrollX = 0f
+        mStartingSpanY = 0f
+    }
+
+    @Override
+    override fun onTouchEvent(ev: MotionEvent): Boolean {
+        val action: Int = ev.getAction()
+        if (DEBUG) Log.e(TAG, "" + action + " ev.getPointerCount() = " + ev.getPointerCount())
+        if (ev.getActionMasked() === MotionEvent.ACTION_DOWN ||
+            ev.getActionMasked() === MotionEvent.ACTION_UP ||
+            ev.getActionMasked() === MotionEvent.ACTION_POINTER_UP ||
+            ev.getActionMasked() === MotionEvent.ACTION_POINTER_DOWN
+        ) {
+            mRecalCenterHour = true
+        }
+        if (mTouchMode and TOUCH_MODE_HSCROLL == 0) {
+            mScaleGestureDetector.onTouchEvent(ev)
+        }
+        return when (action) {
+            MotionEvent.ACTION_DOWN -> {
+                mStartingScroll = true
+                if (DEBUG) {
+                    Log.e(
+                        TAG,
+                        "ACTION_DOWN ev.getDownTime = " + ev.getDownTime().toString() + " Cnt=" +
+                            ev.getPointerCount()
+                    )
+                }
+                val bottom =
+                    mAlldayHeight + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+                mTouchStartedInAlldayArea = if (ev.getY() < bottom) {
+                    true
+                } else {
+                    false
+                }
+                mHandleActionUp = true
+                mGestureDetector.onTouchEvent(ev)
+                true
+            }
+            MotionEvent.ACTION_MOVE -> {
+                if (DEBUG) Log.e(
+                    TAG,
+                    "ACTION_MOVE Cnt=" + ev.getPointerCount() + this@DayView
+                )
+                mGestureDetector.onTouchEvent(ev)
+                true
+            }
+            MotionEvent.ACTION_UP -> {
+                if (DEBUG) Log.e(
+                    TAG,
+                    "ACTION_UP Cnt=" + ev.getPointerCount() + mHandleActionUp
+                )
+                mEdgeEffectTop.onRelease()
+                mEdgeEffectBottom.onRelease()
+                mStartingScroll = false
+                mGestureDetector.onTouchEvent(ev)
+                if (!mHandleActionUp) {
+                    mHandleActionUp = true
+                    mViewStartX = 0
+                    invalidate()
+                    return true
+                }
+                if (mOnFlingCalled) {
+                    return true
+                }
+
+                // If we were scrolling, then reset the selected hour so that it
+                // is visible.
+                if (mScrolling) {
+                    mScrolling = false
+                    resetSelectedHour()
+                    invalidate()
+                }
+                if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
+                    mTouchMode = TOUCH_MODE_INITIAL_STATE
+                    if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
+                        // The user has gone beyond the threshold so switch views
+                        if (DEBUG) Log.d(
+                            TAG,
+                            "- horizontal scroll: switch views"
+                        )
+                        switchViews(
+                            mViewStartX > 0,
+                            mViewStartX.toFloat(),
+                            mViewWidth.toFloat(),
+                            0f
+                        )
+                        mViewStartX = 0
+                        return true
+                    } else {
+                        // Not beyond the threshold so invalidate which will cause
+                        // the view to snap back. Also call recalc() to ensure
+                        // that we have the correct starting date and title.
+                        if (DEBUG) Log.d(
+                            TAG,
+                            "- horizontal scroll: snap back"
+                        )
+                        recalc()
+                        invalidate()
+                        mViewStartX = 0
+                    }
+                }
+                true
+            }
+            MotionEvent.ACTION_CANCEL -> {
+                if (DEBUG) Log.e(
+                    TAG,
+                    "ACTION_CANCEL"
+                )
+                mGestureDetector.onTouchEvent(ev)
+                mScrolling = false
+                resetSelectedHour()
+                true
+            }
+            else -> {
+                if (DEBUG) Log.e(
+                    TAG,
+                    "Not MotionEvent " + ev.toString()
+                )
+                if (mGestureDetector.onTouchEvent(ev)) {
+                    true
+                } else super.onTouchEvent(ev)
+            }
+        }
+    }
+
+    override fun onCreateContextMenu(menu: ContextMenu, view: View?, menuInfo: ContextMenuInfo?) {
+        var item: MenuItem
+
+        // If the trackball is held down, then the context menu pops up and
+        // we never get onKeyUp() for the long-press. So check for it here
+        // and change the selection to the long-press state.
+        if (mSelectionMode != SELECTION_LONGPRESS) {
+            invalidate()
+        }
+        val startMillis = selectedTimeInMillis
+        val flags: Int = (DateUtils.FORMAT_SHOW_TIME
+            or DateUtils.FORMAT_CAP_NOON_MIDNIGHT
+            or DateUtils.FORMAT_SHOW_WEEKDAY)
+        val title: String? = Utils.formatDateRange(mContext, startMillis, startMillis, flags)
+        menu.setHeaderTitle(title)
+        mPopup?.dismiss()
+    }
+
+    /**
+     * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
+     * If the touch position is not within the displayed grid, then this
+     * method returns false.
+     *
+     * @param x the x position of the touch
+     * @param y the y position of the touch
+     * @param keepOldSelection - do not change the selection info (used for invoking accessibility
+     * messages)
+     * @return true if the touch position is valid
+     */
+    private fun setSelectionFromPosition(x: Int, y: Int, keepOldSelection: Boolean): Boolean {
+        var x = x
+        var savedEvent: Event? = null
+        var savedDay = 0
+        var savedHour = 0
+        var savedAllDay = false
+        if (keepOldSelection) {
+            // Store selection info and restore it at the end. This way, we can invoke the
+            // right accessibility message without affecting the selection.
+            savedEvent = mSelectedEvent
+            savedDay = mSelectionDay
+            savedHour = mSelectionHour
+            savedAllDay = mSelectionAllday
+        }
+        if (x < mHoursWidth) {
+            x = mHoursWidth
+        }
+        var day = (x - mHoursWidth) / (mCellWidth + DAY_GAP)
+        if (day >= mNumDays) {
+            day = mNumDays - 1
+        }
+        day += mFirstJulianDay
+        setSelectedDay(day)
+        if (y < DAY_HEADER_HEIGHT) {
+            sendAccessibilityEventAsNeeded(false)
+            return false
+        }
+        setSelectedHour(mFirstHour) /* First fully visible hour */
+        mSelectionAllday = if (y < mFirstCell) {
+            true
+        } else {
+            // y is now offset from top of the scrollable region
+            val adjustedY = y - mFirstCell
+            if (adjustedY < mFirstHourOffset) {
+                setSelectedHour(mSelectionHour - 1) /* In the partially visible hour */
+            } else {
+                setSelectedHour(
+                    mSelectionHour +
+                        (adjustedY - mFirstHourOffset) / (mCellHeight + HOUR_GAP)
+                )
+            }
+            false
+        }
+        findSelectedEvent(x, y)
+        sendAccessibilityEventAsNeeded(true)
+
+        // Restore old values
+        if (keepOldSelection) {
+            mSelectedEvent = savedEvent
+            mSelectionDay = savedDay
+            mSelectionHour = savedHour
+            mSelectionAllday = savedAllDay
+        }
+        return true
+    }
+
+    private fun findSelectedEvent(x: Int, y: Int) {
+        var y = y
+        val date = mSelectionDay
+        val cellWidth = mCellWidth
+        var events: ArrayList<Event>? = mEvents
+        var numEvents: Int = events!!.size
+        val left = computeDayLeftPosition(mSelectionDay - mFirstJulianDay)
+        val top = 0
+        setSelectedEvent(null)
+        mSelectedEvents.clear()
+        if (mSelectionAllday) {
+            var yDistance: Float
+            var minYdistance = 10000.0f // any large number
+            var closestEvent: Event? = null
+            val drawHeight = mAlldayHeight.toFloat()
+            val yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
+            var maxUnexpandedColumn = mMaxUnexpandedAlldayEventCount
+            if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
+                // Leave a gap for the 'box +n' text
+                maxUnexpandedColumn--
+            }
+            events = mAllDayEvents
+            numEvents = events!!.size
+            for (i in 0 until numEvents) {
+                val event: Event? = events?.get(i)
+                if (!event!!.drawAsAllday() ||
+                    !mShowAllAllDayEvents && event!!.getColumn() >= maxUnexpandedColumn
+                ) {
+                    // Don't check non-allday events or events that aren't shown
+                    continue
+                }
+                if (event!!.startDay <= mSelectionDay && event!!.endDay >= mSelectionDay) {
+                    val numRectangles =
+                        if (mShowAllAllDayEvents) mMaxAlldayEvents.toFloat()
+                        else mMaxUnexpandedAlldayEventCount.toFloat()
+                    var height = drawHeight / numRectangles
+                    if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
+                        height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
+                    }
+                    val eventTop: Float = yOffset + height * event?.getColumn()
+                    val eventBottom = eventTop + height
+                    if (eventTop < y && eventBottom > y) {
+                        // If the touch is inside the event rectangle, then
+                        // add the event.
+                        mSelectedEvents.add(event)
+                        closestEvent = event
+                        break
+                    } else {
+                        // Find the closest event
+                        yDistance = if (eventTop >= y) {
+                            eventTop - y
+                        } else {
+                            y - eventBottom
+                        }
+                        if (yDistance < minYdistance) {
+                            minYdistance = yDistance
+                            closestEvent = event
+                        }
+                    }
+                }
+            }
+            setSelectedEvent(closestEvent)
+            return
+        }
+
+        // Adjust y for the scrollable bitmap
+        y += mViewStartY - mFirstCell
+
+        // Use a region around (x,y) for the selection region
+        val region: Rect = mRect
+        region.left = x - 10
+        region.right = x + 10
+        region.top = y - 10
+        region.bottom = y + 10
+        val geometry: EventGeometry = mEventGeometry
+        for (i in 0 until numEvents) {
+            val event: Event? = events?.get(i)
+            // Compute the event rectangle.
+            if (!geometry.computeEventRect(date, left, top, cellWidth, event as Event)) {
+                continue
+            }
+
+            // If the event intersects the selection region, then add it to
+            // mSelectedEvents.
+            if (geometry.eventIntersectsSelection(event as Event, region)) {
+                mSelectedEvents.add(event as Event)
+            }
+        }
+
+        // If there are any events in the selected region, then assign the
+        // closest one to mSelectedEvent.
+        if (mSelectedEvents.size > 0) {
+            val len: Int = mSelectedEvents.size
+            var closestEvent: Event? = null
+            var minDist = (mViewWidth + mViewHeight).toFloat() // some large distance
+            for (index in 0 until len) {
+                val ev: Event? = mSelectedEvents?.get(index)
+                val dist: Float = geometry.pointToEvent(x.toFloat(), y.toFloat(), ev as Event)
+                if (dist < minDist) {
+                    minDist = dist
+                    closestEvent = ev
+                }
+            }
+            setSelectedEvent(closestEvent)
+
+            // Keep the selected hour and day consistent with the selected
+            // event. They could be different if we touched on an empty hour
+            // slot very close to an event in the previous hour slot. In
+            // that case we will select the nearby event.
+            val startDay: Int = mSelectedEvent!!.startDay
+            val endDay: Int = mSelectedEvent!!.endDay
+            if (mSelectionDay < startDay) {
+                setSelectedDay(startDay)
+            } else if (mSelectionDay > endDay) {
+                setSelectedDay(endDay)
+            }
+            val startHour: Int = mSelectedEvent!!.startTime / 60
+            val endHour: Int
+            endHour = if (mSelectedEvent!!.startTime < mSelectedEvent!!.endTime) {
+                (mSelectedEvent!!.endTime - 1) / 60
+            } else {
+                mSelectedEvent!!.endTime / 60
+            }
+            if (mSelectionHour < startHour && mSelectionDay == startDay) {
+                setSelectedHour(startHour)
+            } else if (mSelectionHour > endHour && mSelectionDay == endDay) {
+                setSelectedHour(endHour)
+            }
+        }
+    }
+
+    // Encapsulates the code to continue the scrolling after the
+    // finger is lifted. Instead of stopping the scroll immediately,
+    // the scroll continues to "free spin" and gradually slows down.
+    private inner class ContinueScroll : Runnable {
+        override fun run() {
+            mScrolling = mScrolling && mScroller.computeScrollOffset()
+            if (!mScrolling || mPaused) {
+                resetSelectedHour()
+                invalidate()
+                return
+            }
+            mViewStartY = mScroller.getCurrY()
+            if (mCallEdgeEffectOnAbsorb) {
+                if (mViewStartY < 0) {
+                    mEdgeEffectTop.onAbsorb(mLastVelocity.toInt())
+                    mCallEdgeEffectOnAbsorb = false
+                } else if (mViewStartY > mMaxViewStartY) {
+                    mEdgeEffectBottom.onAbsorb(mLastVelocity.toInt())
+                    mCallEdgeEffectOnAbsorb = false
+                }
+                mLastVelocity = mScroller.getCurrVelocity()
+            }
+            if (mScrollStartY == 0 || mScrollStartY == mMaxViewStartY) {
+                // Allow overscroll/springback only on a fling,
+                // not a pull/fling from the end
+                if (mViewStartY < 0) {
+                    mViewStartY = 0
+                } else if (mViewStartY > mMaxViewStartY) {
+                    mViewStartY = mMaxViewStartY
+                }
+            }
+            computeFirstHour()
+            mHandler?.post(this)
+            invalidate()
+        }
+    }
+
+    /**
+     * Cleanup the pop-up and timers.
+     */
+    fun cleanup() {
+        // Protect against null-pointer exceptions
+        if (mPopup != null) {
+            mPopup?.dismiss()
+        }
+        mPaused = true
+        mLastPopupEventID = INVALID_EVENT_ID
+        if (mHandler != null) {
+            mHandler?.removeCallbacks(mDismissPopup)
+            mHandler?.removeCallbacks(mUpdateCurrentTime)
+        }
+        Utils.setSharedPreference(
+            mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
+            mCellHeight
+        )
+        // Clear all click animations
+        eventClickCleanup()
+        // Turn off redraw
+        mRemeasure = false
+        // Turn off scrolling to make sure the view is in the correct state if we fling back to it
+        mScrolling = false
+    }
+
+    private fun eventClickCleanup() {
+        this.removeCallbacks(mClearClick)
+        this.removeCallbacks(mSetClick)
+        mClickedEvent = null
+        mSavedClickedEvent = null
+    }
+
+    private fun setSelectedEvent(e: Event?) {
+        mSelectedEvent = e
+        mSelectedEventForAccessibility = e
+    }
+
+    private fun setSelectedHour(h: Int) {
+        mSelectionHour = h
+        mSelectionHourForAccessibility = h
+    }
+
+    private fun setSelectedDay(d: Int) {
+        mSelectionDay = d
+        mSelectionDayForAccessibility = d
+    }
+
+    /**
+     * Restart the update timer
+     */
+    fun restartCurrentTimeUpdates() {
+        mPaused = false
+        if (mHandler != null) {
+            mHandler?.removeCallbacks(mUpdateCurrentTime)
+            mHandler?.post(mUpdateCurrentTime)
+        }
+    }
+
+    @Override
+    protected override fun onDetachedFromWindow() {
+        cleanup()
+        super.onDetachedFromWindow()
+    }
+
+    internal inner class DismissPopup : Runnable {
+        override fun run() {
+            // Protect against null-pointer exceptions
+            if (mPopup != null) {
+                mPopup?.dismiss()
+            }
+        }
+    }
+
+    internal inner class UpdateCurrentTime : Runnable {
+        override fun run() {
+            val currentTime: Long = System.currentTimeMillis()
+            mCurrentTime?.set(currentTime)
+            // % causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
+            if (!mPaused) {
+                mHandler?.postDelayed(
+                    mUpdateCurrentTime, UPDATE_CURRENT_TIME_DELAY -
+                        currentTime % UPDATE_CURRENT_TIME_DELAY
+                )
+            }
+            mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
+            invalidate()
+        }
+    }
+
+    internal inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+        @Override
+        override fun onSingleTapUp(ev: MotionEvent): Boolean {
+            if (DEBUG) Log.e(TAG, "GestureDetector.onSingleTapUp")
+            doSingleTapUp(ev)
+            return true
+        }
+
+        @Override
+        override fun onLongPress(ev: MotionEvent) {
+            if (DEBUG) Log.e(TAG, "GestureDetector.onLongPress")
+            doLongPress(ev)
+        }
+
+        @Override
+        override fun onScroll(
+            e1: MotionEvent,
+            e2: MotionEvent,
+            distanceX: Float,
+            distanceY: Float
+        ): Boolean {
+            var distanceY = distanceY
+            if (DEBUG) Log.e(TAG, "GestureDetector.onScroll")
+            eventClickCleanup()
+            if (mTouchStartedInAlldayArea) {
+                if (Math.abs(distanceX) < Math.abs(distanceY)) {
+                    // Make sure that click feedback is gone when you scroll from the
+                    // all day area
+                    invalidate()
+                    return false
+                }
+                // don't scroll vertically if this started in the allday area
+                distanceY = 0f
+            }
+            doScroll(e1, e2, distanceX, distanceY)
+            return true
+        }
+
+        @Override
+        override fun onFling(
+            e1: MotionEvent,
+            e2: MotionEvent,
+            velocityX: Float,
+            velocityY: Float
+        ): Boolean {
+            var velocityY = velocityY
+            if (DEBUG) Log.e(TAG, "GestureDetector.onFling")
+            if (mTouchStartedInAlldayArea) {
+                if (Math.abs(velocityX) < Math.abs(velocityY)) {
+                    return false
+                }
+                // don't fling vertically if this started in the allday area
+                velocityY = 0f
+            }
+            doFling(e1, e2, velocityX, velocityY)
+            return true
+        }
+
+        @Override
+        override fun onDown(ev: MotionEvent): Boolean {
+            if (DEBUG) Log.e(TAG, "GestureDetector.onDown")
+            doDown(ev)
+            return true
+        }
+    }
+
+    @Override
+    override fun onLongClick(v: View?): Boolean {
+        return true
+    }
+
+    private inner class ScrollInterpolator : Interpolator {
+        override fun getInterpolation(t: Float): Float {
+            var t = t
+            t -= 1.0f
+            t = t * t * t * t * t + 1
+            if ((1 - t) * mAnimationDistance < 1) {
+                cancelAnimation()
+            }
+            return t
+        }
+    }
+
+    private fun calculateDuration(delta: Float, width: Float, velocity: Float): Long {
+        /*
+         * Here we compute a "distance" that will be used in the computation of
+         * the overall snap duration. This is a function of the actual distance
+         * that needs to be traveled; we keep this value close to half screen
+         * size in order to reduce the variance in snap duration as a function
+         * of the distance the page needs to travel.
+         */
+        var velocity = velocity
+        val halfScreenSize = width / 2
+        val distanceRatio = delta / width
+        val distanceInfluenceForSnapDuration = distanceInfluenceForSnapDuration(distanceRatio)
+        val distance = halfScreenSize + halfScreenSize * distanceInfluenceForSnapDuration
+        velocity = Math.abs(velocity)
+        velocity = Math.max(MINIMUM_SNAP_VELOCITY.toFloat(), velocity)
+
+        /*
+         * we want the page's snap velocity to approximately match the velocity
+         * at which the user flings, so we scale the duration by a value near to
+         * the derivative of the scroll interpolator at zero, ie. 5. We use 6 to
+         * make it a little slower.
+         */
+        val duration: Long = 6L * Math.round(1000 * Math.abs(distance / velocity))
+        if (DEBUG) {
+            Log.e(
+                TAG, "halfScreenSize:" + halfScreenSize + " delta:" + delta + " distanceRatio:" +
+                    distanceRatio + " distance:" + distance + " velocity:" + velocity +
+                    " duration:" + duration + " distanceInfluenceForSnapDuration:" +
+                    distanceInfluenceForSnapDuration
+            )
+        }
+        return duration
+    }
+
+    /*
+     * We want the duration of the page snap animation to be influenced by the
+     * distance that the screen has to travel, however, we don't want this
+     * duration to be effected in a purely linear fashion. Instead, we use this
+     * method to moderate the effect that the distance of travel has on the
+     * overall snap duration.
+     */
+    private fun distanceInfluenceForSnapDuration(f: Float): Float {
+        var f = f
+        f -= 0.5f // center the values about 0.
+        f *= (0.3f * Math.PI / 2.0f).toFloat()
+        return Math.sin(f.toDouble()).toFloat()
+    }
+
+    companion object {
+        private const val TAG = "DayView"
+        private const val DEBUG = false
+        private const val DEBUG_SCALING = false
+        private const val PERIOD_SPACE = ". "
+        private var mScale = 0f // Used for supporting different screen densities
+        private const val INVALID_EVENT_ID: Long = -1 // This is used for remembering a null event
+
+        // Duration of the allday expansion
+        private const val ANIMATION_DURATION: Long = 400
+
+        // duration of the more allday event text fade
+        private const val ANIMATION_SECONDARY_DURATION: Long = 200
+
+        // duration of the scroll to go to a specified time
+        private const val GOTO_SCROLL_DURATION = 200
+
+        // duration for events' cross-fade animation
+        private const val EVENTS_CROSS_FADE_DURATION = 400
+
+        // duration to show the event clicked
+        private const val CLICK_DISPLAY_DURATION = 50
+        private const val MENU_DAY = 3
+        private const val MENU_EVENT_VIEW = 5
+        private const val MENU_EVENT_CREATE = 6
+        private const val MENU_EVENT_EDIT = 7
+        private const val MENU_EVENT_DELETE = 8
+        private var DEFAULT_CELL_HEIGHT = 64
+        private var MAX_CELL_HEIGHT = 150
+        private var MIN_Y_SPAN = 100
+        private val CALENDARS_PROJECTION = arrayOf<String>(
+            Calendars._ID, // 0
+            Calendars.CALENDAR_ACCESS_LEVEL, // 1
+            Calendars.OWNER_ACCOUNT
+        )
+        private const val CALENDARS_INDEX_ACCESS_LEVEL = 1
+        private const val CALENDARS_INDEX_OWNER_ACCOUNT = 2
+        private val CALENDARS_WHERE: String = Calendars._ID.toString() + "=%d"
+        private const val FROM_NONE = 0
+        private const val FROM_ABOVE = 1
+        private const val FROM_BELOW = 2
+        private const val FROM_LEFT = 4
+        private const val FROM_RIGHT = 8
+        private const val ACCESS_LEVEL_NONE = 0
+        private const val ACCESS_LEVEL_DELETE = 1
+        private const val ACCESS_LEVEL_EDIT = 2
+        private var mHorizontalSnapBackThreshold = 128
+
+        // Update the current time line every five minutes if the window is left open that long
+        private const val UPDATE_CURRENT_TIME_DELAY = 300000
+        private var mOnDownDelay = 0
+        protected var mStringBuilder: StringBuilder = StringBuilder(50)
+
+        // TODO recreate formatter when locale changes
+        protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault())
+
+        // The number of milliseconds to show the popup window
+        private const val POPUP_DISMISS_DELAY = 3000
+        private var GRID_LINE_LEFT_MARGIN = 0f
+        private const val GRID_LINE_INNER_WIDTH = 1f
+        private const val DAY_GAP = 1
+        private const val HOUR_GAP = 1
+
+        // This is the standard height of an allday event with no restrictions
+        private var SINGLE_ALLDAY_HEIGHT = 34
+
+        /**
+         * This is the minimum desired height of a allday event.
+         * When unexpanded, allday events will use this height.
+         * When expanded allDay events will attempt to grow to fit all
+         * events at this height.
+         */
+        private var MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = 28.0f // in pixels
+
+        /**
+         * This is how big the unexpanded allday height is allowed to be.
+         * It will get adjusted based on screen size
+         */
+        private var MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
+
+        /**
+         * This is the minimum size reserved for displaying regular events.
+         * The expanded allDay region can't expand into this.
+         */
+        private const val MIN_HOURS_HEIGHT = 180
+        private var ALLDAY_TOP_MARGIN = 1
+
+        // The largest a single allDay event will become.
+        private var MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34
+        private var HOURS_TOP_MARGIN = 2
+        private var HOURS_LEFT_MARGIN = 2
+        private var HOURS_RIGHT_MARGIN = 4
+        private var HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
+        private var NEW_EVENT_MARGIN = 4
+        private var NEW_EVENT_WIDTH = 2
+        private var NEW_EVENT_MAX_LENGTH = 16
+        private var CURRENT_TIME_LINE_SIDE_BUFFER = 4
+        private var CURRENT_TIME_LINE_TOP_OFFSET = 2
+
+        /* package */
+        const val MINUTES_PER_HOUR = 60
+
+        /* package */
+        const val MINUTES_PER_DAY = MINUTES_PER_HOUR * 24
+
+        /* package */
+        const val MILLIS_PER_MINUTE = 60 * 1000
+
+        /* package */
+        const val MILLIS_PER_HOUR = 3600 * 1000
+
+        /* package */
+        const val MILLIS_PER_DAY = MILLIS_PER_HOUR * 24
+
+        // More events text will transition between invisible and this alpha
+        private const val MORE_EVENTS_MAX_ALPHA = 0x4C
+        private var DAY_HEADER_ONE_DAY_LEFT_MARGIN = 0
+        private var DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5
+        private var DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6
+        private var DAY_HEADER_RIGHT_MARGIN = 4
+        private var DAY_HEADER_BOTTOM_MARGIN = 3
+        private var DAY_HEADER_FONT_SIZE = 14f
+        private var DATE_HEADER_FONT_SIZE = 32f
+        private var NORMAL_FONT_SIZE = 12f
+        private var EVENT_TEXT_FONT_SIZE = 12f
+        private var HOURS_TEXT_SIZE = 12f
+        private var AMPM_TEXT_SIZE = 9f
+        private var MIN_HOURS_WIDTH = 96
+        private var MIN_CELL_WIDTH_FOR_TEXT = 20
+        private const val MAX_EVENT_TEXT_LEN = 500
+
+        // smallest height to draw an event with
+        private var MIN_EVENT_HEIGHT = 24.0f // in pixels
+        private var CALENDAR_COLOR_SQUARE_SIZE = 10
+        private var EVENT_RECT_TOP_MARGIN = 1
+        private var EVENT_RECT_BOTTOM_MARGIN = 0
+        private var EVENT_RECT_LEFT_MARGIN = 1
+        private var EVENT_RECT_RIGHT_MARGIN = 0
+        private var EVENT_RECT_STROKE_WIDTH = 2
+        private var EVENT_TEXT_TOP_MARGIN = 2
+        private var EVENT_TEXT_BOTTOM_MARGIN = 2
+        private var EVENT_TEXT_LEFT_MARGIN = 6
+        private var EVENT_TEXT_RIGHT_MARGIN = 6
+        private var ALL_DAY_EVENT_RECT_BOTTOM_MARGIN = 1
+        private var EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
+        private var EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN
+        private var EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+        private var EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN
+
+        // margins and sizing for the expand allday icon
+        private var EXPAND_ALL_DAY_BOTTOM_MARGIN = 10
+
+        // sizing for "box +n" in allDay events
+        private var EVENT_SQUARE_WIDTH = 10
+        private var EVENT_LINE_PADDING = 4
+        private var NEW_EVENT_HINT_FONT_SIZE = 12
+        private var mEventTextColor = 0
+        private var mMoreEventsTextColor = 0
+        private var mWeek_saturdayColor = 0
+        private var mWeek_sundayColor = 0
+        private var mCalendarDateBannerTextColor = 0
+        private var mCalendarAmPmLabel = 0
+        private var mCalendarGridAreaSelected = 0
+        private var mCalendarGridLineInnerHorizontalColor = 0
+        private var mCalendarGridLineInnerVerticalColor = 0
+        private var mFutureBgColor = 0
+        private var mFutureBgColorRes = 0
+        private var mBgColor = 0
+        private var mNewEventHintColor = 0
+        private var mCalendarHourLabelColor = 0
+        private var mMoreAlldayEventsTextAlpha = MORE_EVENTS_MAX_ALPHA
+        private var mCellHeight = 0 // shared among all DayViews
+        private var mMinCellHeight = 32
+        private var mScaledPagingTouchSlop = 0
+
+        /**
+         * Whether to use the expand or collapse icon.
+         */
+        private var mUseExpandIcon = true
+
+        /**
+         * The height of the day names/numbers
+         */
+        private var DAY_HEADER_HEIGHT = 45
+
+        /**
+         * The height of the day names/numbers for multi-day views
+         */
+        private var MULTI_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
+
+        /**
+         * The height of the day names/numbers when viewing a single day
+         */
+        private var ONE_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
+
+        /**
+         * Whether or not to expand the allDay area to fill the screen
+         */
+        private var mShowAllAllDayEvents = false
+        private var sCounter = 0
+
+        /**
+         * The initial state of the touch mode when we enter this view.
+         */
+        private const val TOUCH_MODE_INITIAL_STATE = 0
+
+        /**
+         * Indicates we just received the touch event and we are waiting to see if
+         * it is a tap or a scroll gesture.
+         */
+        private const val TOUCH_MODE_DOWN = 1
+
+        /**
+         * Indicates the touch gesture is a vertical scroll
+         */
+        private const val TOUCH_MODE_VSCROLL = 0x20
+
+        /**
+         * Indicates the touch gesture is a horizontal scroll
+         */
+        private const val TOUCH_MODE_HSCROLL = 0x40
+
+        /**
+         * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+         */
+        private const val SELECTION_HIDDEN = 0
+        private const val SELECTION_PRESSED = 1 // D-pad down but not up yet
+        private const val SELECTION_SELECTED = 2
+        private const val SELECTION_LONGPRESS = 3
+
+        // The rest of this file was borrowed from Launcher2 - PagedView.java
+        private const val MINIMUM_SNAP_VELOCITY = 2200
+    }
+
+    init {
+        mContext = context
+        initAccessibilityVariables()
+        mResources = context!!.getResources()
+        mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint)
+        mNumDays = numDays
+        DATE_HEADER_FONT_SIZE =
+            mResources.getDimension(R.dimen.date_header_text_size).toInt().toFloat()
+        DAY_HEADER_FONT_SIZE =
+            mResources.getDimension(R.dimen.day_label_text_size).toInt().toFloat()
+        ONE_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.one_day_header_height).toInt()
+        DAY_HEADER_BOTTOM_MARGIN = mResources.getDimension(R.dimen.day_header_bottom_margin).toInt()
+        EXPAND_ALL_DAY_BOTTOM_MARGIN =
+            mResources.getDimension(R.dimen.all_day_bottom_margin).toInt()
+        HOURS_TEXT_SIZE = mResources.getDimension(R.dimen.hours_text_size).toInt().toFloat()
+        AMPM_TEXT_SIZE = mResources.getDimension(R.dimen.ampm_text_size).toInt().toFloat()
+        MIN_HOURS_WIDTH = mResources.getDimension(R.dimen.min_hours_width).toInt()
+        HOURS_LEFT_MARGIN = mResources.getDimension(R.dimen.hours_left_margin).toInt()
+        HOURS_RIGHT_MARGIN = mResources.getDimension(R.dimen.hours_right_margin).toInt()
+        MULTI_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.day_header_height).toInt()
+        val eventTextSizeId: Int
+        eventTextSizeId = if (mNumDays == 1) {
+            R.dimen.day_view_event_text_size
+        } else {
+            R.dimen.week_view_event_text_size
+        }
+        EVENT_TEXT_FONT_SIZE = mResources.getDimension(eventTextSizeId).toFloat()
+        NEW_EVENT_HINT_FONT_SIZE = mResources.getDimension(R.dimen.new_event_hint_text_size).toInt()
+        MIN_EVENT_HEIGHT = mResources.getDimension(R.dimen.event_min_height)
+        MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = MIN_EVENT_HEIGHT
+        EVENT_TEXT_TOP_MARGIN = mResources.getDimension(R.dimen.event_text_vertical_margin).toInt()
+        EVENT_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
+        EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
+        EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
+        EVENT_TEXT_LEFT_MARGIN = mResources
+            .getDimension(R.dimen.event_text_horizontal_margin).toInt()
+        EVENT_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+        EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+        EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
+        if (mScale == 0f) {
+            mScale = mResources.getDisplayMetrics().density
+            if (mScale != 1f) {
+                SINGLE_ALLDAY_HEIGHT *= mScale.toInt()
+                ALLDAY_TOP_MARGIN *= mScale.toInt()
+                MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale.toInt()
+                NORMAL_FONT_SIZE *= mScale
+                GRID_LINE_LEFT_MARGIN *= mScale
+                HOURS_TOP_MARGIN *= mScale.toInt()
+                MIN_CELL_WIDTH_FOR_TEXT *= mScale.toInt()
+                MAX_UNEXPANDED_ALLDAY_HEIGHT *= mScale.toInt()
+                mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
+                CURRENT_TIME_LINE_SIDE_BUFFER *= mScale.toInt()
+                CURRENT_TIME_LINE_TOP_OFFSET *= mScale.toInt()
+                MIN_Y_SPAN *= mScale.toInt()
+                MAX_CELL_HEIGHT *= mScale.toInt()
+                DEFAULT_CELL_HEIGHT *= mScale.toInt()
+                DAY_HEADER_HEIGHT *= mScale.toInt()
+                DAY_HEADER_RIGHT_MARGIN *= mScale.toInt()
+                DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale.toInt()
+                DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale.toInt()
+                DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale.toInt()
+                CALENDAR_COLOR_SQUARE_SIZE *= mScale.toInt()
+                EVENT_RECT_TOP_MARGIN *= mScale.toInt()
+                EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
+                ALL_DAY_EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
+                EVENT_RECT_LEFT_MARGIN *= mScale.toInt()
+                EVENT_RECT_RIGHT_MARGIN *= mScale.toInt()
+                EVENT_RECT_STROKE_WIDTH *= mScale.toInt()
+                EVENT_SQUARE_WIDTH *= mScale.toInt()
+                EVENT_LINE_PADDING *= mScale.toInt()
+                NEW_EVENT_MARGIN *= mScale.toInt()
+                NEW_EVENT_WIDTH *= mScale.toInt()
+                NEW_EVENT_MAX_LENGTH *= mScale.toInt()
+            }
+        }
+        HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
+        DAY_HEADER_HEIGHT = if (mNumDays == 1) ONE_DAY_HEADER_HEIGHT else MULTI_DAY_HEADER_HEIGHT
+        mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_indicator_holo_light)
+        mCurrentTimeAnimateLine = mResources
+            .getDrawable(R.drawable.timeline_indicator_activated_holo_light)
+        mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light)
+        mExpandAlldayDrawable = mResources.getDrawable(R.drawable.ic_expand_holo_light)
+        mCollapseAlldayDrawable = mResources.getDrawable(R.drawable.ic_collapse_holo_light)
+        mNewEventHintColor = mResources.getColor(R.color.new_event_hint_text_color)
+        mAcceptedOrTentativeEventBoxDrawable = mResources
+            .getDrawable(R.drawable.panel_month_event_holo_light)
+        mEventLoader = eventLoader as EventLoader
+        mEventGeometry = EventGeometry()
+        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT)
+        mEventGeometry.setHourGap(HOUR_GAP.toFloat())
+        mEventGeometry.setCellMargin(DAY_GAP)
+        mLastPopupEventID = INVALID_EVENT_ID
+        mController = controller as CalendarController
+        mViewSwitcher = viewSwitcher as ViewSwitcher
+        mGestureDetector = GestureDetector(context, CalendarGestureListener())
+        mScaleGestureDetector = ScaleGestureDetector(getContext(), this)
+        if (mCellHeight == 0) {
+            mCellHeight = Utils.getSharedPreference(
+                mContext,
+                GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT
+            )
+        }
+        mScroller = OverScroller(context)
+        mHScrollInterpolator = ScrollInterpolator()
+        mEdgeEffectTop = EdgeEffect(context)
+        mEdgeEffectBottom = EdgeEffect(context)
+        val vc: ViewConfiguration = ViewConfiguration.get(context)
+        mScaledPagingTouchSlop = vc.getScaledPagingTouchSlop()
+        mOnDownDelay = ViewConfiguration.getTapTimeout()
+        OVERFLING_DISTANCE = vc.getScaledOverflingDistance()
+        init(context as Context)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/Event.java b/src/com/android/calendar/Event.java
deleted file mode 100644
index 095e43e..0000000
--- a/src/com/android/calendar/Event.java
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Debug;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Instances;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicInteger;
-
-// TODO: should Event be Parcelable so it can be passed via Intents?
-public class Event implements Cloneable {
-
-    private static final String TAG = "CalEvent";
-    private static final boolean PROFILE = false;
-
-    /**
-     * The sort order is:
-     * 1) events with an earlier start (begin for normal events, startday for allday)
-     * 2) events with a later end (end for normal events, endday for allday)
-     * 3) the title (unnecessary, but nice)
-     *
-     * The start and end day is sorted first so that all day events are
-     * sorted correctly with respect to events that are >24 hours (and
-     * therefore show up in the allday area).
-     */
-    private static final String SORT_EVENTS_BY =
-            "begin ASC, end DESC, title ASC";
-    private static final String SORT_ALLDAY_BY =
-            "startDay ASC, endDay DESC, title ASC";
-    private static final String DISPLAY_AS_ALLDAY = "dispAllday";
-
-    private static final String EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0";
-    private static final String ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1";
-
-    // The projection to use when querying instances to build a list of events
-    public static final String[] EVENT_PROJECTION = new String[] {
-            Instances.TITLE,                 // 0
-            Instances.EVENT_LOCATION,        // 1
-            Instances.ALL_DAY,               // 2
-            Instances.DISPLAY_COLOR,         // 3 If SDK < 16, set to Instances.CALENDAR_COLOR.
-            Instances.EVENT_TIMEZONE,        // 4
-            Instances.EVENT_ID,              // 5
-            Instances.BEGIN,                 // 6
-            Instances.END,                   // 7
-            Instances._ID,                   // 8
-            Instances.START_DAY,             // 9
-            Instances.END_DAY,               // 10
-            Instances.START_MINUTE,          // 11
-            Instances.END_MINUTE,            // 12
-            Instances.HAS_ALARM,             // 13
-            Instances.RRULE,                 // 14
-            Instances.RDATE,                 // 15
-            Instances.SELF_ATTENDEE_STATUS,  // 16
-            Events.ORGANIZER,                // 17
-            Events.GUESTS_CAN_MODIFY,        // 18
-            Instances.ALL_DAY + "=1 OR (" + Instances.END + "-" + Instances.BEGIN + ")>="
-                    + DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY, // 19
-    };
-
-    // The indices for the projection array above.
-    private static final int PROJECTION_TITLE_INDEX = 0;
-    private static final int PROJECTION_LOCATION_INDEX = 1;
-    private static final int PROJECTION_ALL_DAY_INDEX = 2;
-    private static final int PROJECTION_COLOR_INDEX = 3;
-    private static final int PROJECTION_TIMEZONE_INDEX = 4;
-    private static final int PROJECTION_EVENT_ID_INDEX = 5;
-    private static final int PROJECTION_BEGIN_INDEX = 6;
-    private static final int PROJECTION_END_INDEX = 7;
-    private static final int PROJECTION_START_DAY_INDEX = 9;
-    private static final int PROJECTION_END_DAY_INDEX = 10;
-    private static final int PROJECTION_START_MINUTE_INDEX = 11;
-    private static final int PROJECTION_END_MINUTE_INDEX = 12;
-    private static final int PROJECTION_HAS_ALARM_INDEX = 13;
-    private static final int PROJECTION_RRULE_INDEX = 14;
-    private static final int PROJECTION_RDATE_INDEX = 15;
-    private static final int PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16;
-    private static final int PROJECTION_ORGANIZER_INDEX = 17;
-    private static final int PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX = 18;
-    private static final int PROJECTION_DISPLAY_AS_ALLDAY = 19;
-
-    static {
-        if (!Utils.isJellybeanOrLater()) {
-            EVENT_PROJECTION[PROJECTION_COLOR_INDEX] = Instances.CALENDAR_COLOR;
-        }
-    }
-
-    private static String mNoTitleString;
-    private static int mNoColorColor;
-
-    public long id;
-    public int color;
-    public CharSequence title;
-    public CharSequence location;
-    public boolean allDay;
-    public String organizer;
-    public boolean guestsCanModify;
-
-    public int startDay;       // start Julian day
-    public int endDay;         // end Julian day
-    public int startTime;      // Start and end time are in minutes since midnight
-    public int endTime;
-
-    public long startMillis;   // UTC milliseconds since the epoch
-    public long endMillis;     // UTC milliseconds since the epoch
-    private int mColumn;
-    private int mMaxColumns;
-
-    public boolean hasAlarm;
-    public boolean isRepeating;
-
-    public int selfAttendeeStatus;
-
-    // The coordinates of the event rectangle drawn on the screen.
-    public float left;
-    public float right;
-    public float top;
-    public float bottom;
-
-    // These 4 fields are used for navigating among events within the selected
-    // hour in the Day and Week view.
-    public Event nextRight;
-    public Event nextLeft;
-    public Event nextUp;
-    public Event nextDown;
-
-    @Override
-    public final Object clone() throws CloneNotSupportedException {
-        super.clone();
-        Event e = new Event();
-
-        e.title = title;
-        e.color = color;
-        e.location = location;
-        e.allDay = allDay;
-        e.startDay = startDay;
-        e.endDay = endDay;
-        e.startTime = startTime;
-        e.endTime = endTime;
-        e.startMillis = startMillis;
-        e.endMillis = endMillis;
-        e.hasAlarm = hasAlarm;
-        e.isRepeating = isRepeating;
-        e.selfAttendeeStatus = selfAttendeeStatus;
-        e.organizer = organizer;
-        e.guestsCanModify = guestsCanModify;
-
-        return e;
-    }
-
-    public final void copyTo(Event dest) {
-        dest.id = id;
-        dest.title = title;
-        dest.color = color;
-        dest.location = location;
-        dest.allDay = allDay;
-        dest.startDay = startDay;
-        dest.endDay = endDay;
-        dest.startTime = startTime;
-        dest.endTime = endTime;
-        dest.startMillis = startMillis;
-        dest.endMillis = endMillis;
-        dest.hasAlarm = hasAlarm;
-        dest.isRepeating = isRepeating;
-        dest.selfAttendeeStatus = selfAttendeeStatus;
-        dest.organizer = organizer;
-        dest.guestsCanModify = guestsCanModify;
-    }
-
-    public static final Event newInstance() {
-        Event e = new Event();
-
-        e.id = 0;
-        e.title = null;
-        e.color = 0;
-        e.location = null;
-        e.allDay = false;
-        e.startDay = 0;
-        e.endDay = 0;
-        e.startTime = 0;
-        e.endTime = 0;
-        e.startMillis = 0;
-        e.endMillis = 0;
-        e.hasAlarm = false;
-        e.isRepeating = false;
-        e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
-
-        return e;
-    }
-
-    /**
-     * Loads <i>days</i> days worth of instances starting at <i>startDay</i>.
-     */
-    public static void loadEvents(Context context, ArrayList<Event> events, int startDay, int days,
-            int requestId, AtomicInteger sequenceNumber) {
-
-        if (PROFILE) {
-            Debug.startMethodTracing("loadEvents");
-        }
-
-        Cursor cEvents = null;
-        Cursor cAllday = null;
-
-        events.clear();
-        try {
-            int endDay = startDay + days - 1;
-
-            // We use the byDay instances query to get a list of all events for
-            // the days we're interested in.
-            // The sort order is: events with an earlier start time occur
-            // first and if the start times are the same, then events with
-            // a later end time occur first. The later end time is ordered
-            // first so that long rectangles in the calendar views appear on
-            // the left side.  If the start and end times of two events are
-            // the same then we sort alphabetically on the title.  This isn't
-            // required for correctness, it just adds a nice touch.
-
-            // Respect the preference to show/hide declined events
-            SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-            boolean hideDeclined = prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED,
-                    false);
-
-            String where = EVENTS_WHERE;
-            String whereAllday = ALLDAY_WHERE;
-            if (hideDeclined) {
-                String hideString = " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
-                        + Attendees.ATTENDEE_STATUS_DECLINED;
-                where += hideString;
-                whereAllday += hideString;
-            }
-
-            cEvents = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
-                    endDay, where, null, SORT_EVENTS_BY);
-            cAllday = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
-                    endDay, whereAllday, null, SORT_ALLDAY_BY);
-
-            // Check if we should return early because there are more recent
-            // load requests waiting.
-            if (requestId != sequenceNumber.get()) {
-                return;
-            }
-
-            buildEventsFromCursor(events, cEvents, context, startDay, endDay);
-            buildEventsFromCursor(events, cAllday, context, startDay, endDay);
-
-        } finally {
-            if (cEvents != null) {
-                cEvents.close();
-            }
-            if (cAllday != null) {
-                cAllday.close();
-            }
-            if (PROFILE) {
-                Debug.stopMethodTracing();
-            }
-        }
-    }
-
-    /**
-     * Performs a query to return all visible instances in the given range
-     * that match the given selection. This is a blocking function and
-     * should not be done on the UI thread. This will cause an expansion of
-     * recurring events to fill this time range if they are not already
-     * expanded and will slow down for larger time ranges with many
-     * recurring events.
-     *
-     * @param cr The ContentResolver to use for the query
-     * @param projection The columns to return
-     * @param begin The start of the time range to query in UTC millis since
-     *            epoch
-     * @param end The end of the time range to query in UTC millis since
-     *            epoch
-     * @param selection Filter on the query as an SQL WHERE statement
-     * @param selectionArgs Args to replace any '?'s in the selection
-     * @param orderBy How to order the rows as an SQL ORDER BY statement
-     * @return A Cursor of instances matching the selection
-     */
-    private static final Cursor instancesQuery(ContentResolver cr, String[] projection,
-            int startDay, int endDay, String selection, String[] selectionArgs, String orderBy) {
-        String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?";
-        String[] WHERE_CALENDARS_ARGS = {"1"};
-        String DEFAULT_SORT_ORDER = "begin ASC";
-
-        Uri.Builder builder = Instances.CONTENT_BY_DAY_URI.buildUpon();
-        ContentUris.appendId(builder, startDay);
-        ContentUris.appendId(builder, endDay);
-        if (TextUtils.isEmpty(selection)) {
-            selection = WHERE_CALENDARS_SELECTED;
-            selectionArgs = WHERE_CALENDARS_ARGS;
-        } else {
-            selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
-            if (selectionArgs != null && selectionArgs.length > 0) {
-                selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1);
-                selectionArgs[selectionArgs.length - 1] = WHERE_CALENDARS_ARGS[0];
-            } else {
-                selectionArgs = WHERE_CALENDARS_ARGS;
-            }
-        }
-        return cr.query(builder.build(), projection, selection, selectionArgs,
-                orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
-    }
-
-    /**
-     * Adds all the events from the cursors to the events list.
-     *
-     * @param events The list of events
-     * @param cEvents Events to add to the list
-     * @param context
-     * @param startDay
-     * @param endDay
-     */
-    public static void buildEventsFromCursor(
-            ArrayList<Event> events, Cursor cEvents, Context context, int startDay, int endDay) {
-        if (cEvents == null || events == null) {
-            Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!");
-            return;
-        }
-
-        int count = cEvents.getCount();
-
-        if (count == 0) {
-            return;
-        }
-
-        Resources res = context.getResources();
-        mNoTitleString = res.getString(R.string.no_title_label);
-        mNoColorColor = res.getColor(R.color.event_center);
-        // Sort events in two passes so we ensure the allday and standard events
-        // get sorted in the correct order
-        cEvents.moveToPosition(-1);
-        while (cEvents.moveToNext()) {
-            Event e = generateEventFromCursor(cEvents);
-            if (e.startDay > endDay || e.endDay < startDay) {
-                continue;
-            }
-            events.add(e);
-        }
-    }
-
-    /**
-     * @param cEvents Cursor pointing at event
-     * @return An event created from the cursor
-     */
-    private static Event generateEventFromCursor(Cursor cEvents) {
-        Event e = new Event();
-
-        e.id = cEvents.getLong(PROJECTION_EVENT_ID_INDEX);
-        e.title = cEvents.getString(PROJECTION_TITLE_INDEX);
-        e.location = cEvents.getString(PROJECTION_LOCATION_INDEX);
-        e.allDay = cEvents.getInt(PROJECTION_ALL_DAY_INDEX) != 0;
-        e.organizer = cEvents.getString(PROJECTION_ORGANIZER_INDEX);
-        e.guestsCanModify = cEvents.getInt(PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX) != 0;
-
-        if (e.title == null || e.title.length() == 0) {
-            e.title = mNoTitleString;
-        }
-
-        if (!cEvents.isNull(PROJECTION_COLOR_INDEX)) {
-            // Read the color from the database
-            e.color = Utils.getDisplayColorFromColor(cEvents.getInt(PROJECTION_COLOR_INDEX));
-        } else {
-            e.color = mNoColorColor;
-        }
-
-        long eStart = cEvents.getLong(PROJECTION_BEGIN_INDEX);
-        long eEnd = cEvents.getLong(PROJECTION_END_INDEX);
-
-        e.startMillis = eStart;
-        e.startTime = cEvents.getInt(PROJECTION_START_MINUTE_INDEX);
-        e.startDay = cEvents.getInt(PROJECTION_START_DAY_INDEX);
-
-        e.endMillis = eEnd;
-        e.endTime = cEvents.getInt(PROJECTION_END_MINUTE_INDEX);
-        e.endDay = cEvents.getInt(PROJECTION_END_DAY_INDEX);
-
-        e.hasAlarm = cEvents.getInt(PROJECTION_HAS_ALARM_INDEX) != 0;
-
-        // Check if this is a repeating event
-        String rrule = cEvents.getString(PROJECTION_RRULE_INDEX);
-        String rdate = cEvents.getString(PROJECTION_RDATE_INDEX);
-        if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
-            e.isRepeating = true;
-        } else {
-            e.isRepeating = false;
-        }
-
-        e.selfAttendeeStatus = cEvents.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX);
-        return e;
-    }
-
-    /**
-     * Computes a position for each event.  Each event is displayed
-     * as a non-overlapping rectangle.  For normal events, these rectangles
-     * are displayed in separate columns in the week view and day view.  For
-     * all-day events, these rectangles are displayed in separate rows along
-     * the top.  In both cases, each event is assigned two numbers: N, and
-     * Max, that specify that this event is the Nth event of Max number of
-     * events that are displayed in a group. The width and position of each
-     * rectangle depend on the maximum number of rectangles that occur at
-     * the same time.
-     *
-     * @param eventsList the list of events, sorted into increasing time order
-     * @param minimumDurationMillis minimum duration acceptable as cell height of each event
-     * rectangle in millisecond. Should be 0 when it is not determined.
-     */
-    /* package */ static void computePositions(ArrayList<Event> eventsList,
-            long minimumDurationMillis) {
-        if (eventsList == null) {
-            return;
-        }
-
-        // Compute the column positions separately for the all-day events
-        doComputePositions(eventsList, minimumDurationMillis, false);
-        doComputePositions(eventsList, minimumDurationMillis, true);
-    }
-
-    private static void doComputePositions(ArrayList<Event> eventsList,
-            long minimumDurationMillis, boolean doAlldayEvents) {
-        final ArrayList<Event> activeList = new ArrayList<Event>();
-        final ArrayList<Event> groupList = new ArrayList<Event>();
-
-        if (minimumDurationMillis < 0) {
-            minimumDurationMillis = 0;
-        }
-
-        long colMask = 0;
-        int maxCols = 0;
-        for (Event event : eventsList) {
-            // Process all-day events separately
-            if (event.drawAsAllday() != doAlldayEvents)
-                continue;
-
-           if (!doAlldayEvents) {
-                colMask = removeNonAlldayActiveEvents(
-                        event, activeList.iterator(), minimumDurationMillis, colMask);
-            } else {
-                colMask = removeAlldayActiveEvents(event, activeList.iterator(), colMask);
-            }
-
-            // If the active list is empty, then reset the max columns, clear
-            // the column bit mask, and empty the groupList.
-            if (activeList.isEmpty()) {
-                for (Event ev : groupList) {
-                    ev.setMaxColumns(maxCols);
-                }
-                maxCols = 0;
-                colMask = 0;
-                groupList.clear();
-            }
-
-            // Find the first empty column.  Empty columns are represented by
-            // zero bits in the column mask "colMask".
-            int col = findFirstZeroBit(colMask);
-            if (col == 64)
-                col = 63;
-            colMask |= (1L << col);
-            event.setColumn(col);
-            activeList.add(event);
-            groupList.add(event);
-            int len = activeList.size();
-            if (maxCols < len)
-                maxCols = len;
-        }
-        for (Event ev : groupList) {
-            ev.setMaxColumns(maxCols);
-        }
-    }
-
-    private static long removeAlldayActiveEvents(Event event, Iterator<Event> iter, long colMask) {
-        // Remove the inactive allday events. An event on the active list
-        // becomes inactive when the end day is less than the current event's
-        // start day.
-        while (iter.hasNext()) {
-            final Event active = iter.next();
-            if (active.endDay < event.startDay) {
-                colMask &= ~(1L << active.getColumn());
-                iter.remove();
-            }
-        }
-        return colMask;
-    }
-
-    private static long removeNonAlldayActiveEvents(
-            Event event, Iterator<Event> iter, long minDurationMillis, long colMask) {
-        long start = event.getStartMillis();
-        // Remove the inactive events. An event on the active list
-        // becomes inactive when its end time is less than or equal to
-        // the current event's start time.
-        while (iter.hasNext()) {
-            final Event active = iter.next();
-
-            final long duration = Math.max(
-                    active.getEndMillis() - active.getStartMillis(), minDurationMillis);
-            if ((active.getStartMillis() + duration) <= start) {
-                colMask &= ~(1L << active.getColumn());
-                iter.remove();
-            }
-        }
-        return colMask;
-    }
-
-    public static int findFirstZeroBit(long val) {
-        for (int ii = 0; ii < 64; ++ii) {
-            if ((val & (1L << ii)) == 0)
-                return ii;
-        }
-        return 64;
-    }
-
-    public final void dump() {
-        Log.e("Cal", "+-----------------------------------------+");
-        Log.e("Cal", "+        id = " + id);
-        Log.e("Cal", "+     color = " + color);
-        Log.e("Cal", "+     title = " + title);
-        Log.e("Cal", "+  location = " + location);
-        Log.e("Cal", "+    allDay = " + allDay);
-        Log.e("Cal", "+  startDay = " + startDay);
-        Log.e("Cal", "+    endDay = " + endDay);
-        Log.e("Cal", "+ startTime = " + startTime);
-        Log.e("Cal", "+   endTime = " + endTime);
-        Log.e("Cal", "+ organizer = " + organizer);
-        Log.e("Cal", "+  guestwrt = " + guestsCanModify);
-    }
-
-    public final boolean intersects(int julianDay, int startMinute,
-            int endMinute) {
-        if (endDay < julianDay) {
-            return false;
-        }
-
-        if (startDay > julianDay) {
-            return false;
-        }
-
-        if (endDay == julianDay) {
-            if (endTime < startMinute) {
-                return false;
-            }
-            // An event that ends at the start minute should not be considered
-            // as intersecting the given time span, but don't exclude
-            // zero-length (or very short) events.
-            if (endTime == startMinute
-                    && (startTime != endTime || startDay != endDay)) {
-                return false;
-            }
-        }
-
-        if (startDay == julianDay && startTime > endMinute) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns the event title and location separated by a comma.  If the
-     * location is already part of the title (at the end of the title), then
-     * just the title is returned.
-     *
-     * @return the event title and location as a String
-     */
-    public String getTitleAndLocation() {
-        String text = title.toString();
-
-        // Append the location to the title, unless the title ends with the
-        // location (for example, "meeting in building 42" ends with the
-        // location).
-        if (location != null) {
-            String locationString = location.toString();
-            if (!text.endsWith(locationString)) {
-                text += ", " + locationString;
-            }
-        }
-        return text;
-    }
-
-    public void setColumn(int column) {
-        mColumn = column;
-    }
-
-    public int getColumn() {
-        return mColumn;
-    }
-
-    public void setMaxColumns(int maxColumns) {
-        mMaxColumns = maxColumns;
-    }
-
-    public int getMaxColumns() {
-        return mMaxColumns;
-    }
-
-    public void setStartMillis(long startMillis) {
-        this.startMillis = startMillis;
-    }
-
-    public long getStartMillis() {
-        return startMillis;
-    }
-
-    public void setEndMillis(long endMillis) {
-        this.endMillis = endMillis;
-    }
-
-    public long getEndMillis() {
-        return endMillis;
-    }
-
-    public boolean drawAsAllday() {
-        // Use >= so we'll pick up Exchange allday events
-        return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS;
-    }
-}
diff --git a/src/com/android/calendar/Event.kt b/src/com/android/calendar/Event.kt
new file mode 100644
index 0000000..c21a0a0
--- /dev/null
+++ b/src/com/android/calendar/Event.kt
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.res.Resources
+import android.database.Cursor
+import android.net.Uri
+import android.os.Debug
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Events
+import android.provider.CalendarContract.Instances
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.util.Log
+
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Iterator
+import java.util.concurrent.atomic.AtomicInteger
+
+// TODO: should Event be Parcelable so it can be passed via Intents?
+class Event : Cloneable {
+    companion object {
+        private const val TAG = "CalEvent"
+        private const val PROFILE = false
+
+        /**
+         * The sort order is:
+         * 1) events with an earlier start (begin for normal events, startday for allday)
+         * 2) events with a later end (end for normal events, endday for allday)
+         * 3) the title (unnecessary, but nice)
+         *
+         * The start and end day is sorted first so that all day events are
+         * sorted correctly with respect to events that are >24 hours (and
+         * therefore show up in the allday area).
+         */
+        private const val SORT_EVENTS_BY = "begin ASC, end DESC, title ASC"
+        private const val SORT_ALLDAY_BY = "startDay ASC, endDay DESC, title ASC"
+        private const val DISPLAY_AS_ALLDAY = "dispAllday"
+        private const val EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0"
+        private const val ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1"
+
+        // The projection to use when querying instances to build a list of events
+        @JvmField
+        val EVENT_PROJECTION = arrayOf<String>(
+            Instances.TITLE, // 0
+            Instances.EVENT_LOCATION, // 1
+            Instances.ALL_DAY, // 2
+            Instances.DISPLAY_COLOR, // 3 If SDK < 16, set to Instances.CALENDAR_COLOR.
+            Instances.EVENT_TIMEZONE, // 4
+            Instances.EVENT_ID, // 5
+            Instances.BEGIN, // 6
+            Instances.END,  // 7
+            Instances._ID,  // 8
+            Instances.START_DAY, // 9
+            Instances.END_DAY, // 10
+            Instances.START_MINUTE, // 11
+            Instances.END_MINUTE, // 12
+            Instances.HAS_ALARM, // 13
+            Instances.RRULE,  // 14
+            Instances.RDATE,  // 15
+            Instances.SELF_ATTENDEE_STATUS, // 16
+            Events.ORGANIZER, // 17
+            Events.GUESTS_CAN_MODIFY, // 18
+            Instances.ALL_DAY.toString() + "=1 OR (" + Instances.END + "-" +
+                Instances.BEGIN + ")>=" +
+                DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY
+        )
+
+        // The indices for the projection array above.
+        private const val PROJECTION_TITLE_INDEX = 0
+        private const val PROJECTION_LOCATION_INDEX = 1
+        private const val PROJECTION_ALL_DAY_INDEX = 2
+        private const val PROJECTION_COLOR_INDEX = 3
+        private const val PROJECTION_TIMEZONE_INDEX = 4
+        private const val PROJECTION_EVENT_ID_INDEX = 5
+        private const val PROJECTION_BEGIN_INDEX = 6
+        private const val PROJECTION_END_INDEX = 7
+        private const val PROJECTION_START_DAY_INDEX = 9
+        private const val PROJECTION_END_DAY_INDEX = 10
+        private const val PROJECTION_START_MINUTE_INDEX = 11
+        private const val PROJECTION_END_MINUTE_INDEX = 12
+        private const val PROJECTION_HAS_ALARM_INDEX = 13
+        private const val PROJECTION_RRULE_INDEX = 14
+        private const val PROJECTION_RDATE_INDEX = 15
+        private const val PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16
+        private const val PROJECTION_ORGANIZER_INDEX = 17
+        private const val PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX = 18
+        private const val PROJECTION_DISPLAY_AS_ALLDAY = 19
+        private var mNoTitleString: String? = null
+        private var mNoColorColor = 0
+        @JvmStatic fun newInstance(): Event {
+            val e = Event()
+            e.id = 0
+            e.title = null
+            e.color = 0
+            e.location = null
+            e.allDay = false
+            e.startDay = 0
+            e.endDay = 0
+            e.startTime = 0
+            e.endTime = 0
+            e.startMillis = 0
+            e.endMillis = 0
+            e.hasAlarm = false
+            e.isRepeating = false
+            e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE
+            return e
+        }
+
+        /**
+         * Loads *days* days worth of instances starting at *startDay*.
+         */
+        @JvmStatic fun loadEvents(
+            context: Context?,
+            events: ArrayList<Event?>,
+            startDay: Int,
+            days: Int,
+            requestId: Int,
+            sequenceNumber: AtomicInteger?
+        ) {
+            if (PROFILE) {
+                Debug.startMethodTracing("loadEvents")
+            }
+            var cEvents: Cursor? = null
+            var cAllday: Cursor? = null
+            events.clear()
+            try {
+                val endDay = startDay + days - 1
+
+                // We use the byDay instances query to get a list of all events for
+                // the days we're interested in.
+                // The sort order is: events with an earlier start time occur
+                // first and if the start times are the same, then events with
+                // a later end time occur first. The later end time is ordered
+                // first so that long rectangles in the calendar views appear on
+                // the left side.  If the start and end times of two events are
+                // the same then we sort alphabetically on the title.  This isn't
+                // required for correctness, it just adds a nice touch.
+
+                // Respect the preference to show/hide declined events
+                val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+                val hideDeclined: Boolean = prefs?.getBoolean(
+                    GeneralPreferences.KEY_HIDE_DECLINED,
+                    false
+                ) as Boolean
+                var where = EVENTS_WHERE
+                var whereAllday = ALLDAY_WHERE
+                if (hideDeclined) {
+                    val hideString = (" AND " + Instances.SELF_ATTENDEE_STATUS.toString() + "!=" +
+                        Attendees.ATTENDEE_STATUS_DECLINED)
+                    where += hideString
+                    whereAllday += hideString
+                }
+                cEvents = instancesQuery(
+                    context?.getContentResolver(), EVENT_PROJECTION, startDay,
+                    endDay, where, null, SORT_EVENTS_BY
+                )
+                cAllday = instancesQuery(
+                    context?.getContentResolver(), EVENT_PROJECTION, startDay,
+                    endDay, whereAllday, null, SORT_ALLDAY_BY
+                )
+
+                // Check if we should return early because there are more recent
+                // load requests waiting.
+                if (requestId != sequenceNumber?.get()) {
+                    return
+                }
+                buildEventsFromCursor(events, cEvents, context, startDay, endDay)
+                buildEventsFromCursor(events, cAllday, context, startDay, endDay)
+            } finally {
+                if (cEvents != null) {
+                    cEvents.close()
+                }
+                if (cAllday != null) {
+                    cAllday.close()
+                }
+                if (PROFILE) {
+                    Debug.stopMethodTracing()
+                }
+            }
+        }
+
+        /**
+         * Performs a query to return all visible instances in the given range
+         * that match the given selection. This is a blocking function and
+         * should not be done on the UI thread. This will cause an expansion of
+         * recurring events to fill this time range if they are not already
+         * expanded and will slow down for larger time ranges with many
+         * recurring events.
+         *
+         * @param cr The ContentResolver to use for the query
+         * @param projection The columns to return
+         * @param begin The start of the time range to query in UTC millis since
+         * epoch
+         * @param end The end of the time range to query in UTC millis since
+         * epoch
+         * @param selection Filter on the query as an SQL WHERE statement
+         * @param selectionArgs Args to replace any '?'s in the selection
+         * @param orderBy How to order the rows as an SQL ORDER BY statement
+         * @return A Cursor of instances matching the selection
+         */
+        @JvmStatic private fun instancesQuery(
+            cr: ContentResolver?,
+            projection: Array<String>,
+            startDay: Int,
+            endDay: Int,
+            selection: String,
+            selectionArgs: Array<String?>?,
+            orderBy: String?
+        ): Cursor? {
+            var selection = selection
+            var selectionArgs = selectionArgs
+            val WHERE_CALENDARS_SELECTED: String = Calendars.VISIBLE.toString() + "=?"
+            val WHERE_CALENDARS_ARGS = arrayOf<String?>("1")
+            val DEFAULT_SORT_ORDER = "begin ASC"
+            val builder: Uri.Builder = Instances.CONTENT_BY_DAY_URI.buildUpon()
+            ContentUris.appendId(builder, startDay.toLong())
+            ContentUris.appendId(builder, endDay.toLong())
+            if (TextUtils.isEmpty(selection)) {
+                selection = WHERE_CALENDARS_SELECTED
+                selectionArgs = WHERE_CALENDARS_ARGS
+            } else {
+                selection = "($selection) AND $WHERE_CALENDARS_SELECTED"
+                if (selectionArgs != null && selectionArgs.size > 0) {
+                    selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.size + 1)
+                    selectionArgs[selectionArgs.size - 1] = WHERE_CALENDARS_ARGS[0]
+                } else {
+                    selectionArgs = WHERE_CALENDARS_ARGS
+                }
+            }
+            return cr?.query(
+                builder.build(), projection, selection, selectionArgs,
+                orderBy ?: DEFAULT_SORT_ORDER
+            )
+        }
+
+        /**
+         * Adds all the events from the cursors to the events list.
+         *
+         * @param events The list of events
+         * @param cEvents Events to add to the list
+         * @param context
+         * @param startDay
+         * @param endDay
+         */
+        @JvmStatic fun buildEventsFromCursor(
+            events: ArrayList<Event?>?,
+            cEvents: Cursor?,
+            context: Context?,
+            startDay: Int,
+            endDay: Int
+        ) {
+            if (cEvents == null || events == null) {
+                Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!")
+                return
+            }
+            val count: Int = cEvents.getCount()
+            if (count == 0) {
+                return
+            }
+            val res: Resources? = context?.getResources()
+            mNoTitleString = res?.getString(R.string.no_title_label)
+            mNoColorColor = res?.getColor(R.color.event_center) as Int
+            // Sort events in two passes so we ensure the allday and standard events
+            // get sorted in the correct order
+            cEvents.moveToPosition(-1)
+            while (cEvents.moveToNext()) {
+                val e = generateEventFromCursor(cEvents)
+                if (e.startDay > endDay || e.endDay < startDay) {
+                    continue
+                }
+                events.add(e)
+            }
+        }
+
+        /**
+         * @param cEvents Cursor pointing at event
+         * @return An event created from the cursor
+         */
+        @JvmStatic private fun generateEventFromCursor(cEvents: Cursor): Event {
+            val e = Event()
+            e.id = cEvents.getLong(PROJECTION_EVENT_ID_INDEX)
+            e.title = cEvents.getString(PROJECTION_TITLE_INDEX)
+            e.location = cEvents.getString(PROJECTION_LOCATION_INDEX)
+            e.allDay = cEvents.getInt(PROJECTION_ALL_DAY_INDEX) !== 0
+            e.organizer = cEvents.getString(PROJECTION_ORGANIZER_INDEX)
+            e.guestsCanModify = cEvents.getInt(PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX) !== 0
+            if (e.title == null || e.title!!.length == 0) {
+                e.title = mNoTitleString
+            }
+            if (!cEvents.isNull(PROJECTION_COLOR_INDEX)) {
+                // Read the color from the database
+                e.color = Utils.getDisplayColorFromColor(cEvents.getInt(PROJECTION_COLOR_INDEX))
+            } else {
+                e.color = mNoColorColor
+            }
+            val eStart: Long = cEvents.getLong(PROJECTION_BEGIN_INDEX)
+            val eEnd: Long = cEvents.getLong(PROJECTION_END_INDEX)
+            e.startMillis = eStart
+            e.startTime = cEvents.getInt(PROJECTION_START_MINUTE_INDEX)
+            e.startDay = cEvents.getInt(PROJECTION_START_DAY_INDEX)
+            e.endMillis = eEnd
+            e.endTime = cEvents.getInt(PROJECTION_END_MINUTE_INDEX)
+            e.endDay = cEvents.getInt(PROJECTION_END_DAY_INDEX)
+            e.hasAlarm = cEvents.getInt(PROJECTION_HAS_ALARM_INDEX) !== 0
+
+            // Check if this is a repeating event
+            val rrule: String = cEvents.getString(PROJECTION_RRULE_INDEX)
+            val rdate: String = cEvents.getString(PROJECTION_RDATE_INDEX)
+            if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
+                e.isRepeating = true
+            } else {
+                e.isRepeating = false
+            }
+            e.selfAttendeeStatus = cEvents.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX)
+            return e
+        }
+
+        /**
+         * Computes a position for each event.  Each event is displayed
+         * as a non-overlapping rectangle.  For normal events, these rectangles
+         * are displayed in separate columns in the week view and day view.  For
+         * all-day events, these rectangles are displayed in separate rows along
+         * the top.  In both cases, each event is assigned two numbers: N, and
+         * Max, that specify that this event is the Nth event of Max number of
+         * events that are displayed in a group. The width and position of each
+         * rectangle depend on the maximum number of rectangles that occur at
+         * the same time.
+         *
+         * @param eventsList the list of events, sorted into increasing time order
+         * @param minimumDurationMillis minimum duration acceptable as cell height of each event
+         * rectangle in millisecond. Should be 0 when it is not determined.
+         */
+        /* package */
+        @JvmStatic fun computePositions(
+            eventsList: ArrayList<Event>?,
+            minimumDurationMillis: Long
+        ) {
+            if (eventsList == null) {
+                return
+            }
+
+            // Compute the column positions separately for the all-day events
+            doComputePositions(eventsList, minimumDurationMillis, false)
+            doComputePositions(eventsList, minimumDurationMillis, true)
+        }
+
+        @JvmStatic private fun doComputePositions(
+            eventsList: ArrayList<Event>,
+            minimumDurationMillis: Long,
+            doAlldayEvents: Boolean
+        ) {
+            var minimumDurationMillis = minimumDurationMillis
+            val activeList: ArrayList<Event> = ArrayList<Event>()
+            val groupList: ArrayList<Event> = ArrayList<Event>()
+            if (minimumDurationMillis < 0) {
+                minimumDurationMillis = 0
+            }
+            var colMask: Long = 0
+            var maxCols = 0
+            for (event in eventsList) {
+                // Process all-day events separately
+                if (event.drawAsAllday() != doAlldayEvents) continue
+                colMask = if (!doAlldayEvents) {
+                    removeNonAlldayActiveEvents(
+                        event, activeList.iterator() as Iterator<Event>,
+                        minimumDurationMillis, colMask
+                    )
+                } else {
+                    removeAlldayActiveEvents(event, activeList.iterator()
+                        as Iterator<Event>, colMask)
+                }
+
+                // If the active list is empty, then reset the max columns, clear
+                // the column bit mask, and empty the groupList.
+                if (activeList.isEmpty()) {
+                    for (ev in groupList) {
+                        ev.maxColumns = maxCols
+                    }
+                    maxCols = 0
+                    colMask = 0
+                    groupList.clear()
+                }
+
+                // Find the first empty column.  Empty columns are represented by
+                // zero bits in the column mask "colMask".
+                var col = findFirstZeroBit(colMask)
+                if (col == 64) col = 63
+                colMask = colMask or (1L shl col)
+                event.column = col
+                activeList.add(event)
+                groupList.add(event)
+                val len: Int = activeList.size
+                if (maxCols < len) maxCols = len
+            }
+            for (ev in groupList) {
+                ev.maxColumns = maxCols
+            }
+        }
+
+        @JvmStatic private fun removeAlldayActiveEvents(
+            event: Event,
+            iter: Iterator<Event>,
+            colMask: Long
+        ): Long {
+            // Remove the inactive allday events. An event on the active list
+            // becomes inactive when the end day is less than the current event's
+            // start day.
+            var colMask = colMask
+            while (iter.hasNext()) {
+                val active = iter.next()
+                if (active.endDay < event.startDay) {
+                    colMask = colMask and (1L shl active.column).inv()
+                    iter.remove()
+                }
+            }
+            return colMask
+        }
+
+        @JvmStatic private fun removeNonAlldayActiveEvents(
+            event: Event,
+            iter: Iterator<Event>,
+            minDurationMillis: Long,
+            colMask: Long
+        ): Long {
+            var colMask = colMask
+            val start = event.getStartMillis()
+            // Remove the inactive events. An event on the active list
+            // becomes inactive when its end time is less than or equal to
+            // the current event's start time.
+            while (iter.hasNext()) {
+                val active = iter.next()
+                val duration: Long = Math.max(
+                    active.getEndMillis() - active.getStartMillis(), minDurationMillis
+                )
+                if (active.getStartMillis() + duration <= start) {
+                    colMask = colMask and (1L shl active.column).inv()
+                    iter.remove()
+                }
+            }
+            return colMask
+        }
+
+        @JvmStatic fun findFirstZeroBit(`val`: Long): Int {
+            for (ii in 0..63) {
+                if (`val` and (1L shl ii) == 0L) return ii
+            }
+            return 64
+        }
+
+        init {
+            if (!Utils.isJellybeanOrLater()) {
+                EVENT_PROJECTION[PROJECTION_COLOR_INDEX] = Instances.CALENDAR_COLOR
+            }
+        }
+    }
+
+    @JvmField var id: Long = 0
+    @JvmField var color = 0
+    @JvmField var title: CharSequence? = null
+    @JvmField var location: CharSequence? = null
+    @JvmField var allDay = false
+    @JvmField var organizer: String? = null
+    @JvmField var guestsCanModify = false
+    @JvmField var startDay = 0 // start Julian day
+    @JvmField var endDay = 0 // end Julian day
+    @JvmField var startTime = 0 // Start and end time are in minutes since midnight
+    @JvmField var endTime = 0
+    @JvmField var startMillis = 0L // UTC milliseconds since the epoch
+    @JvmField var endMillis = 0L // UTC milliseconds since the epoch
+    @JvmField var column = 0
+    @JvmField var maxColumns = 0
+    @JvmField var hasAlarm = false
+    @JvmField var isRepeating = false
+    @JvmField var selfAttendeeStatus = 0
+
+    // The coordinates of the event rectangle drawn on the screen.
+    @JvmField var left = 0f
+    @JvmField var right = 0f
+    @JvmField var top = 0f
+    @JvmField var bottom = 0f
+
+    // These 4 fields are used for navigating among events within the selected
+    // hour in the Day and Week view.
+    @JvmField var nextRight: Event? = null
+    @JvmField var nextLeft: Event? = null
+    @JvmField var nextUp: Event? = null
+    @JvmField var nextDown: Event? = null
+    @Override
+    @Throws(CloneNotSupportedException::class)
+    override fun clone(): Object {
+        super.clone()
+        val e = Event()
+        e.title = title
+        e.color = color
+        e.location = location
+        e.allDay = allDay
+        e.startDay = startDay
+        e.endDay = endDay
+        e.startTime = startTime
+        e.endTime = endTime
+        e.startMillis = startMillis
+        e.endMillis = endMillis
+        e.hasAlarm = hasAlarm
+        e.isRepeating = isRepeating
+        e.selfAttendeeStatus = selfAttendeeStatus
+        e.organizer = organizer
+        e.guestsCanModify = guestsCanModify
+        return e as Object
+    }
+
+    fun copyTo(dest: Event) {
+        dest.id = id
+        dest.title = title
+        dest.color = color
+        dest.location = location
+        dest.allDay = allDay
+        dest.startDay = startDay
+        dest.endDay = endDay
+        dest.startTime = startTime
+        dest.endTime = endTime
+        dest.startMillis = startMillis
+        dest.endMillis = endMillis
+        dest.hasAlarm = hasAlarm
+        dest.isRepeating = isRepeating
+        dest.selfAttendeeStatus = selfAttendeeStatus
+        dest.organizer = organizer
+        dest.guestsCanModify = guestsCanModify
+    }
+
+    fun dump() {
+        Log.e("Cal", "+-----------------------------------------+")
+        Log.e("Cal", "+        id = $id")
+        Log.e("Cal", "+     color = $color")
+        Log.e("Cal", "+     title = $title")
+        Log.e("Cal", "+  location = $location")
+        Log.e("Cal", "+    allDay = $allDay")
+        Log.e("Cal", "+  startDay = $startDay")
+        Log.e("Cal", "+    endDay = $endDay")
+        Log.e("Cal", "+ startTime = $startTime")
+        Log.e("Cal", "+   endTime = $endTime")
+        Log.e("Cal", "+ organizer = $organizer")
+        Log.e("Cal", "+  guestwrt = $guestsCanModify")
+    }
+
+    fun intersects(
+        julianDay: Int,
+        startMinute: Int,
+        endMinute: Int
+    ): Boolean {
+        if (endDay < julianDay) {
+            return false
+        }
+        if (startDay > julianDay) {
+            return false
+        }
+        if (endDay == julianDay) {
+            if (endTime < startMinute) {
+                return false
+            }
+            // An event that ends at the start minute should not be considered
+            // as intersecting the given time span, but don't exclude
+            // zero-length (or very short) events.
+            if (endTime == startMinute &&
+                (startTime != endTime || startDay != endDay)) {
+                return false
+            }
+        }
+        return !(startDay == julianDay && startTime > endMinute)
+    }
+
+    /**
+     * Returns the event title and location separated by a comma.  If the
+     * location is already part of the title (at the end of the title), then
+     * just the title is returned.
+     *
+     * @return the event title and location as a String
+     */
+    val titleAndLocation: String
+        get() {
+            var text = title.toString()
+
+            // Append the location to the title, unless the title ends with the
+            // location (for example, "meeting in building 42" ends with the
+            // location).
+            if (location != null) {
+                val locationString = location.toString()
+                if (!text.endsWith(locationString)) {
+                    text += ", $locationString"
+                }
+            }
+            return text
+        }
+
+    // TODO(damianpatel): this getter will likely not be
+    // needed once DayView.java is converted
+    fun getColumn(): Int {
+        return column
+    }
+
+    fun setStartMillis(startMillis: Long) {
+        this.startMillis = startMillis
+    }
+
+    fun getStartMillis(): Long {
+        return startMillis
+    }
+
+    fun setEndMillis(endMillis: Long) {
+        this.endMillis = endMillis
+    }
+
+    fun getEndMillis(): Long {
+        return endMillis
+    }
+
+    fun drawAsAllday(): Boolean {
+        // Use >= so we'll pick up Exchange allday events
+        return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/EventGeometry.java b/src/com/android/calendar/EventGeometry.java
deleted file mode 100644
index cdecb49..0000000
--- a/src/com/android/calendar/EventGeometry.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.graphics.Rect;
-
-public class EventGeometry {
-    // This is the space from the grid line to the event rectangle.
-    private int mCellMargin = 0;
-
-    private float mMinuteHeight;
-
-    private float mHourGap;
-    private float mMinEventHeight;
-
-    void setCellMargin(int cellMargin) {
-        mCellMargin = cellMargin;
-    }
-
-    public void setHourGap(float gap) {
-        mHourGap = gap;
-    }
-
-    public void setMinEventHeight(float height) {
-        mMinEventHeight = height;
-    }
-
-    public void setHourHeight(float height) {
-        mMinuteHeight = height / 60.0f;
-    }
-
-    // Computes the rectangle coordinates of the given event on the screen.
-    // Returns true if the rectangle is visible on the screen.
-    public boolean computeEventRect(int date, int left, int top, int cellWidth, Event event) {
-        if (event.drawAsAllday()) {
-            return false;
-        }
-
-        float cellMinuteHeight = mMinuteHeight;
-        int startDay = event.startDay;
-        int endDay = event.endDay;
-
-        if (startDay > date || endDay < date) {
-            return false;
-        }
-
-        int startTime = event.startTime;
-        int endTime = event.endTime;
-
-        // If the event started on a previous day, then show it starting
-        // at the beginning of this day.
-        if (startDay < date) {
-            startTime = 0;
-        }
-
-        // If the event ends on a future day, then show it extending to
-        // the end of this day.
-        if (endDay > date) {
-            endTime = DayView.MINUTES_PER_DAY;
-        }
-
-        int col = event.getColumn();
-        int maxCols = event.getMaxColumns();
-        int startHour = startTime / 60;
-        int endHour = endTime / 60;
-
-        // If the end point aligns on a cell boundary then count it as
-        // ending in the previous cell so that we don't cross the border
-        // between hours.
-        if (endHour * 60 == endTime)
-            endHour -= 1;
-
-        event.top = top;
-        event.top += (int) (startTime * cellMinuteHeight);
-        event.top += startHour * mHourGap;
-
-        event.bottom = top;
-        event.bottom += (int) (endTime * cellMinuteHeight);
-        event.bottom += endHour * mHourGap - 1;
-
-        // Make the rectangle be at least mMinEventHeight pixels high
-        if (event.bottom < event.top + mMinEventHeight) {
-            event.bottom = event.top + mMinEventHeight;
-        }
-
-        float colWidth = (float) (cellWidth - (maxCols + 1) * mCellMargin) / (float) maxCols;
-        event.left = left + col * (colWidth + mCellMargin);
-        event.right = event.left + colWidth;
-        return true;
-    }
-
-    /**
-     * Returns true if this event intersects the selection region.
-     */
-    boolean eventIntersectsSelection(Event event, Rect selection) {
-        if (event.left < selection.right && event.right >= selection.left
-                && event.top < selection.bottom && event.bottom >= selection.top) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Computes the distance from the given point to the given event.
-     */
-    float pointToEvent(float x, float y, Event event) {
-        float left = event.left;
-        float right = event.right;
-        float top = event.top;
-        float bottom = event.bottom;
-
-        if (x >= left) {
-            if (x <= right) {
-                if (y >= top) {
-                    if (y <= bottom) {
-                        // x,y is inside the event rectangle
-                        return 0f;
-                    }
-                    // x,y is below the event rectangle
-                    return y - bottom;
-                }
-                // x,y is above the event rectangle
-                return top - y;
-            }
-
-            // x > right
-            float dx = x - right;
-            if (y < top) {
-                // the upper right corner
-                float dy = top - y;
-                return (float) Math.sqrt(dx * dx + dy * dy);
-            }
-            if (y > bottom) {
-                // the lower right corner
-                float dy = y - bottom;
-                return (float) Math.sqrt(dx * dx + dy * dy);
-            }
-            // x,y is to the right of the event rectangle
-            return dx;
-        }
-        // x < left
-        float dx = left - x;
-        if (y < top) {
-            // the upper left corner
-            float dy = top - y;
-            return (float) Math.sqrt(dx * dx + dy * dy);
-        }
-        if (y > bottom) {
-            // the lower left corner
-            float dy = y - bottom;
-            return (float) Math.sqrt(dx * dx + dy * dy);
-        }
-        // x,y is to the left of the event rectangle
-        return dx;
-    }
-}
diff --git a/src/com/android/calendar/EventGeometry.kt b/src/com/android/calendar/EventGeometry.kt
new file mode 100644
index 0000000..43fc3e7
--- /dev/null
+++ b/src/com/android/calendar/EventGeometry.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.graphics.Rect
+
+class EventGeometry {
+    // This is the space from the grid line to the event rectangle.
+    private var mCellMargin = 0
+    private var mMinuteHeight = 0f
+    private var mHourGap = 0f
+    private var mMinEventHeight = 0f
+    fun setCellMargin(cellMargin: Int) {
+        mCellMargin = cellMargin
+    }
+
+    fun setHourGap(gap: Float) {
+        mHourGap = gap
+    }
+
+    fun setMinEventHeight(height: Float) {
+        mMinEventHeight = height
+    }
+
+    fun setHourHeight(height: Float) {
+        mMinuteHeight = height / 60.0f
+    }
+
+    // Computes the rectangle coordinates of the given event on the screen.
+    // Returns true if the rectangle is visible on the screen.
+    fun computeEventRect(date: Int, left: Int, top: Int, cellWidth: Int, event: Event): Boolean {
+        if (event.drawAsAllday()) {
+            return false
+        }
+        val cellMinuteHeight = mMinuteHeight
+        val startDay: Int = event.startDay
+        val endDay: Int = event.endDay
+        if (startDay > date || endDay < date) {
+            return false
+        }
+        var startTime: Int = event.startTime
+        var endTime: Int = event.endTime
+
+        // If the event started on a previous day, then show it starting
+        // at the beginning of this day.
+        if (startDay < date) {
+            startTime = 0
+        }
+
+        // If the event ends on a future day, then show it extending to
+        // the end of this day.
+        if (endDay > date) {
+            endTime = DayView.MINUTES_PER_DAY
+        }
+        val col: Int = event.column
+        val maxCols: Int = event.maxColumns
+        val startHour = startTime / 60
+        var endHour = endTime / 60
+
+        // If the end point aligns on a cell boundary then count it as
+        // ending in the previous cell so that we don't cross the border
+        // between hours.
+        if (endHour * 60 == endTime) endHour -= 1
+        event.top = top as Float
+        event.top += (startTime * cellMinuteHeight).toInt()
+        event.top += startHour * mHourGap
+        event.bottom = top as Float
+        event.bottom += (endTime * cellMinuteHeight).toInt()
+        event.bottom += endHour * mHourGap - 1
+
+        // Make the rectangle be at least mMinEventHeight pixels high
+        if (event.bottom < event.top + mMinEventHeight) {
+            event.bottom = event.top + mMinEventHeight
+        }
+        val colWidth = (cellWidth - (maxCols + 1) * mCellMargin).toFloat() / maxCols.toFloat()
+        event.left = left + col * (colWidth + mCellMargin)
+        event.right = event.left + colWidth
+        return true
+    }
+
+    /**
+     * Returns true if this event intersects the selection region.
+     */
+    fun eventIntersectsSelection(event: Event, selection: Rect): Boolean {
+        return if (event.left < selection.right && event.right >= selection.left &&
+            event.top < selection.bottom && event.bottom >= selection.top) {
+            true
+        } else false
+    }
+
+    /**
+     * Computes the distance from the given point to the given event.
+     */
+    fun pointToEvent(x: Float, y: Float, event: Event): Float {
+        val left: Float = event.left
+        val right: Float = event.right
+        val top: Float = event.top
+        val bottom: Float = event.bottom
+        if (x >= left) {
+            if (x <= right) {
+                return if (y >= top) {
+                    if (y <= bottom) {
+                        // x,y is inside the event rectangle
+                        0f
+                    } else y - bottom
+                    // x,y is below the event rectangle
+                } else top - y
+                // x,y is above the event rectangle
+            }
+
+            // x > right
+            val dx = x - right
+            if (y < top) {
+                // the upper right corner
+                val dy = top - y
+                return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+            }
+            if (y > bottom) {
+                // the lower right corner
+                val dy = y - bottom
+                return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+            }
+            // x,y is to the right of the event rectangle
+            return dx
+        }
+        // x < left
+        val dx = left - x
+        if (y < top) {
+            // the upper left corner
+            val dy = top - y
+            return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+        }
+        if (y > bottom) {
+            // the lower left corner
+            val dy = y - bottom
+            return (Math.sqrt(dx as Double * dx + dy as Double * dy)) as Float
+        }
+        // x,y is to the left of the event rectangle
+        return dx
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/EventInfoActivity.java b/src/com/android/calendar/EventInfoActivity.java
deleted file mode 100644
index 626e099..0000000
--- a/src/com/android/calendar/EventInfoActivity.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.calendar;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.util.Log;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class EventInfoActivity extends Activity {
-    private static final String TAG = "EventInfoActivity";
-    private EventInfoFragment mInfoFragment;
-    private long mStartMillis, mEndMillis;
-    private long mEventId;
-
-    // Create an observer so that we can update the views whenever a
-    // Calendar event changes.
-    private final ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return false;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            if (selfChange) return;
-            if (mInfoFragment != null) {
-                mInfoFragment.reloadEvents();
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Get the info needed for the fragment
-        Intent intent = getIntent();
-        int attendeeResponse = 0;
-        mEventId = -1;
-        boolean isDialog = false;
-
-        if (icicle != null) {
-            mEventId = icicle.getLong(EventInfoFragment.BUNDLE_KEY_EVENT_ID);
-            mStartMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_START_MILLIS);
-            mEndMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_END_MILLIS);
-            attendeeResponse = icicle.getInt(EventInfoFragment.BUNDLE_KEY_ATTENDEE_RESPONSE);
-            isDialog = icicle.getBoolean(EventInfoFragment.BUNDLE_KEY_IS_DIALOG);
-        } else if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
-            mStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0);
-            mEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0);
-            attendeeResponse = intent.getIntExtra(ATTENDEE_STATUS,
-                    Attendees.ATTENDEE_STATUS_NONE);
-            Uri data = intent.getData();
-            if (data != null) {
-                try {
-                    List<String> pathSegments = data.getPathSegments();
-                    int size = pathSegments.size();
-                    if (size > 2 && "EventTime".equals(pathSegments.get(2))) {
-                        // Support non-standard VIEW intent format:
-                        //dat = content://com.android.calendar/events/[id]/EventTime/[start]/[end]
-                        mEventId = Long.parseLong(pathSegments.get(1));
-                        if (size > 4) {
-                            mStartMillis = Long.parseLong(pathSegments.get(3));
-                            mEndMillis = Long.parseLong(pathSegments.get(4));
-                        }
-                    } else {
-                        mEventId = Long.parseLong(data.getLastPathSegment());
-                    }
-                } catch (NumberFormatException e) {
-                    if (mEventId == -1) {
-                        // do nothing here , deal with it later
-                    } else if (mStartMillis == 0 || mEndMillis ==0) {
-                        // Parsing failed on the start or end time , make sure the times were not
-                        // pulled from the intent's extras and reset them.
-                        mStartMillis = 0;
-                        mEndMillis = 0;
-                    }
-                }
-            }
-        }
-
-        if (mEventId == -1) {
-            Log.w(TAG, "No event id");
-            Toast.makeText(this, R.string.event_not_found, Toast.LENGTH_SHORT).show();
-            finish();
-        }
-
-        // If we do not support showing full screen event info in this configuration,
-        // close the activity and show the event in AllInOne.
-        Resources res = getResources();
-        if (!res.getBoolean(R.bool.agenda_show_event_info_full_screen)
-                && !res.getBoolean(R.bool.show_event_info_full_screen)) {
-            CalendarController.getInstance(this)
-                    .launchViewEvent(mEventId, mStartMillis, mEndMillis, attendeeResponse);
-            finish();
-            return;
-        }
-
-        setContentView(R.layout.simple_frame_layout);
-
-        // Get the fragment if exists
-        mInfoFragment = (EventInfoFragment)
-                getFragmentManager().findFragmentById(R.id.main_frame);
-
-
-        // Remove the application title
-        ActionBar bar = getActionBar();
-        if (bar != null) {
-            bar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
-        }
-
-        // Create a new fragment if none exists
-        if (mInfoFragment == null) {
-            FragmentManager fragmentManager = getFragmentManager();
-            FragmentTransaction ft = fragmentManager.beginTransaction();
-            mInfoFragment = new EventInfoFragment(this, mEventId, mStartMillis, mEndMillis,
-                    attendeeResponse, isDialog, (isDialog ?
-                            EventInfoFragment.DIALOG_WINDOW_STYLE :
-                                EventInfoFragment.FULL_WINDOW_STYLE));
-            ft.replace(R.id.main_frame, mInfoFragment);
-            ft.commit();
-        }
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        // From the Android Dev Guide: "It's important to note that when
-        // onNewIntent(Intent) is called, the Activity has not been restarted,
-        // so the getIntent() method will still return the Intent that was first
-        // received with onCreate(). This is why setIntent(Intent) is called
-        // inside onNewIntent(Intent) (just in case you call getIntent() at a
-        // later time)."
-        setIntent(intent);
-    }
-
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        getContentResolver().registerContentObserver(CalendarContract.Events.CONTENT_URI,
-                true, mObserver);
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        getContentResolver().unregisterContentObserver(mObserver);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-    }
-}
diff --git a/src/com/android/calendar/EventInfoActivity.kt b/src/com/android/calendar/EventInfoActivity.kt
new file mode 100644
index 0000000..c0a1b9c
--- /dev/null
+++ b/src/com/android/calendar/EventInfoActivity.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS
+import android.app.ActionBar
+import android.app.Activity
+import android.app.FragmentManager
+import android.app.FragmentTransaction
+import android.content.Intent
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.provider.CalendarContract
+import android.provider.CalendarContract.Attendees
+import android.util.Log
+import android.widget.Toast
+
+class EventInfoActivity : Activity() {
+    private var mInfoFragment: EventInfoFragment? = null
+    private var mStartMillis: Long = 0
+    private var mEndMillis: Long = 0
+    private var mEventId: Long = 0
+
+    // Create an observer so that we can update the views whenever a
+    // Calendar event changes.
+    private val mObserver: ContentObserver = object : ContentObserver(Handler()) {
+        @Override
+        override fun deliverSelfNotifications(): Boolean {
+            return false
+        }
+
+        @Override
+        override fun onChange(selfChange: Boolean) {
+            if (selfChange) return
+            val temp = mInfoFragment
+            if (temp != null) {
+                mInfoFragment?.reloadEvents()
+            }
+        }
+    }
+
+    @Override
+    protected override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+
+        // Get the info needed for the fragment
+        val intent: Intent = getIntent()
+        var attendeeResponse = 0
+        mEventId = -1
+        var isDialog = false
+        if (icicle != null) {
+            mEventId = icicle.getLong(EventInfoFragment.BUNDLE_KEY_EVENT_ID)
+            mStartMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_START_MILLIS)
+            mEndMillis = icicle.getLong(EventInfoFragment.BUNDLE_KEY_END_MILLIS)
+            attendeeResponse = icicle.getInt(EventInfoFragment.BUNDLE_KEY_ATTENDEE_RESPONSE)
+            isDialog = icicle.getBoolean(EventInfoFragment.BUNDLE_KEY_IS_DIALOG)
+        } else if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+            mStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0)
+            mEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0)
+            attendeeResponse = intent.getIntExtra(
+                    ATTENDEE_STATUS,
+                    Attendees.ATTENDEE_STATUS_NONE
+            )
+            val data: Uri? = intent.getData()
+            if (data != null) {
+                try {
+                    val pathSegments = data.getPathSegments()
+                    val size: Int = pathSegments.size
+                    if (size > 2 && "EventTime".equals(pathSegments[2])) {
+                        // Support non-standard VIEW intent format:
+                        // dat = content://com.android.calendar/events/[id]/EventTime/[start]/[end]
+                        mEventId = pathSegments[1].toLong()
+                        if (size > 4) {
+                            mStartMillis = pathSegments[3].toLong()
+                            mEndMillis = pathSegments[4].toLong()
+                        }
+                    } else {
+                        mEventId = data.getLastPathSegment() as Long
+                    }
+                } catch (e: NumberFormatException) {
+                    if (mEventId == -1L) {
+                        // do nothing here , deal with it later
+                    } else if (mStartMillis == 0L || mEndMillis == 0L) {
+                        // Parsing failed on the start or end time , make sure the times were not
+                        // pulled from the intent's extras and reset them.
+                        mStartMillis = 0
+                        mEndMillis = 0
+                    }
+                }
+            }
+        }
+        if (mEventId == -1L) {
+            Log.w(TAG, "No event id")
+            Toast.makeText(this, R.string.event_not_found, Toast.LENGTH_SHORT).show()
+            finish()
+        }
+
+        // If we do not support showing full screen event info in this configuration,
+        // close the activity and show the event in AllInOne.
+        val res: Resources = getResources()
+        if (!res.getBoolean(R.bool.agenda_show_event_info_full_screen) &&
+                !res.getBoolean(R.bool.show_event_info_full_screen)
+        ) {
+            CalendarController.getInstance(this)
+                    ?.launchViewEvent(mEventId, mStartMillis, mEndMillis, attendeeResponse)
+            finish()
+            return
+        }
+        setContentView(R.layout.simple_frame_layout)
+
+        // Get the fragment if exists
+        mInfoFragment = getFragmentManager().findFragmentById(R.id.main_frame) as EventInfoFragment
+
+        // Remove the application title
+        val bar: ActionBar? = getActionBar()
+        if (bar != null) {
+            bar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP or ActionBar.DISPLAY_SHOW_HOME)
+        }
+
+        // Create a new fragment if none exists
+        if (mInfoFragment == null) {
+            val fragmentManager: FragmentManager = getFragmentManager()
+            val ft: FragmentTransaction = fragmentManager.beginTransaction()
+            mInfoFragment = EventInfoFragment(
+                    this,
+                    mEventId,
+                    mStartMillis,
+                    mEndMillis,
+                    attendeeResponse,
+                    isDialog,
+                    if (isDialog) EventInfoFragment.DIALOG_WINDOW_STYLE
+                    else EventInfoFragment.FULL_WINDOW_STYLE
+            )
+            ft.replace(R.id.main_frame, mInfoFragment)
+            ft.commit()
+        }
+    }
+
+    @Override
+    protected override fun onNewIntent(intent: Intent?) {
+        // From the Android Dev Guide: "It's important to note that when
+        // onNewIntent(Intent) is called, the Activity has not been restarted,
+        // so the getIntent() method will still return the Intent that was first
+        // received with onCreate(). This is why setIntent(Intent) is called
+        // inside onNewIntent(Intent) (just in case you call getIntent() at a
+        // later time)."
+        setIntent(intent)
+    }
+
+    @Override
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+    }
+
+    @Override
+    protected override fun onResume() {
+        super.onResume()
+        getContentResolver().registerContentObserver(
+                CalendarContract.Events.CONTENT_URI,
+                true, mObserver
+        )
+    }
+
+    @Override
+    protected override fun onPause() {
+        super.onPause()
+        getContentResolver().unregisterContentObserver(mObserver)
+    }
+
+    @Override
+    protected override fun onDestroy() {
+        super.onDestroy()
+    }
+
+    companion object {
+        private const val TAG = "EventInfoActivity"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java
deleted file mode 100644
index 0aa83d0..0000000
--- a/src/com/android/calendar/EventInfoFragment.java
+++ /dev/null
@@ -1,877 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.app.Service;
-import android.content.ActivityNotFoundException;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Reminders;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.QuickContact;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.text.method.LinkMovementMethod;
-import android.text.method.MovementMethod;
-import android.text.style.ForegroundColorSpan;
-import android.text.util.Rfc822Token;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.RadioGroup.OnCheckedChangeListener;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.calendar.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.alerts.QuickResponseActivity;
-import com.android.calendarcommon2.DateException;
-import com.android.calendarcommon2.Duration;
-import com.android.calendarcommon2.EventRecurrence;
-import com.android.colorpicker.HsvColorComparator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
-        CalendarController.EventHandler, OnClickListener {
-
-    public static final boolean DEBUG = false;
-
-    public static final String TAG = "EventInfoFragment";
-
-    protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
-    protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
-    protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
-    protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
-    protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
-    protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
-    protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color";
-    protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init";
-    protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color";
-    protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key";
-    protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init";
-    protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color";
-    protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init";
-    protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
-    protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE =
-            "key_user_set_attendee_response";
-    protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE =
-            "key_tentative_user_response";
-    protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events";
-    protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes";
-    protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods";
-
-
-    private static final String PERIOD_SPACE = ". ";
-
-    private static final String NO_EVENT_COLOR = "";
-
-    /**
-     * These are the corresponding indices into the array of strings
-     * "R.array.change_response_labels" in the resource file.
-     */
-    static final int UPDATE_SINGLE = 0;
-    static final int UPDATE_ALL = 1;
-
-    // Style of view
-    public static final int FULL_WINDOW_STYLE = 0;
-    public static final int DIALOG_WINDOW_STYLE = 1;
-
-    private int mWindowStyle = DIALOG_WINDOW_STYLE;
-
-    // Query tokens for QueryHandler
-    private static final int TOKEN_QUERY_EVENT = 1 << 0;
-    private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
-    private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
-    private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
-    private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
-    private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5;
-    private static final int TOKEN_QUERY_COLORS = 1 << 6;
-
-    private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
-            | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
-            | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS;
-
-    private int mCurrentQuery = 0;
-
-    private static final String[] EVENT_PROJECTION = new String[] {
-        Events._ID,                  // 0  do not remove; used in DeleteEventHelper
-        Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
-        Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
-        Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
-        Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
-        Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
-        Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
-        Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
-        Events.DESCRIPTION,          // 8
-        Events.EVENT_LOCATION,       // 9
-        Calendars.CALENDAR_ACCESS_LEVEL, // 10
-        Events.CALENDAR_COLOR,       // 11
-        Events.EVENT_COLOR,          // 12
-        Events.HAS_ATTENDEE_DATA,    // 13
-        Events.ORGANIZER,            // 14
-        Events.HAS_ALARM,            // 15
-        Calendars.MAX_REMINDERS,     // 16
-        Calendars.ALLOWED_REMINDERS, // 17
-        Events.CUSTOM_APP_PACKAGE,   // 18
-        Events.CUSTOM_APP_URI,       // 19
-        Events.DTEND,                // 20
-        Events.DURATION,             // 21
-        Events.ORIGINAL_SYNC_ID      // 22 do not remove; used in DeleteEventHelper
-    };
-    private static final int EVENT_INDEX_ID = 0;
-    private static final int EVENT_INDEX_TITLE = 1;
-    private static final int EVENT_INDEX_RRULE = 2;
-    private static final int EVENT_INDEX_ALL_DAY = 3;
-    private static final int EVENT_INDEX_CALENDAR_ID = 4;
-    private static final int EVENT_INDEX_DTSTART = 5;
-    private static final int EVENT_INDEX_SYNC_ID = 6;
-    private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
-    private static final int EVENT_INDEX_DESCRIPTION = 8;
-    private static final int EVENT_INDEX_EVENT_LOCATION = 9;
-    private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
-    private static final int EVENT_INDEX_CALENDAR_COLOR = 11;
-    private static final int EVENT_INDEX_EVENT_COLOR = 12;
-    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
-    private static final int EVENT_INDEX_ORGANIZER = 14;
-    private static final int EVENT_INDEX_HAS_ALARM = 15;
-    private static final int EVENT_INDEX_MAX_REMINDERS = 16;
-    private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17;
-    private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18;
-    private static final int EVENT_INDEX_CUSTOM_APP_URI = 19;
-    private static final int EVENT_INDEX_DTEND = 20;
-    private static final int EVENT_INDEX_DURATION = 21;
-
-    static {
-        if (!Utils.isJellybeanOrLater()) {
-            EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // nonessential value
-            EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // nonessential value
-        }
-    }
-
-    static final String[] CALENDARS_PROJECTION = new String[] {
-        Calendars._ID,           // 0
-        Calendars.CALENDAR_DISPLAY_NAME,  // 1
-        Calendars.OWNER_ACCOUNT, // 2
-        Calendars.CAN_ORGANIZER_RESPOND, // 3
-        Calendars.ACCOUNT_NAME, // 4
-        Calendars.ACCOUNT_TYPE  // 5
-    };
-    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
-    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
-    static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
-    static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
-    static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5;
-
-    static final String CALENDARS_WHERE = Calendars._ID + "=?";
-    static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
-    static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?";
-
-    public static final int COLORS_INDEX_COLOR = 1;
-    public static final int COLORS_INDEX_COLOR_KEY = 2;
-
-    private View mView;
-
-    private Uri mUri;
-    private long mEventId;
-    private Cursor mEventCursor;
-    private Cursor mCalendarsCursor;
-
-    private static float mScale = 0; // Used for supporting different screen densities
-
-    private static int mCustomAppIconSize = 32;
-
-    private long mStartMillis;
-    private long mEndMillis;
-    private boolean mAllDay;
-
-    private boolean mOwnerCanRespond;
-    private String mSyncAccountName;
-    private String mCalendarOwnerAccount;
-    private boolean mIsBusyFreeCalendar;
-
-    private int mOriginalAttendeeResponse;
-    private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
-    private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
-    private int mWhichEvents = -1;
-    // Used as the temporary response until the dialog is confirmed. It is also
-    // able to be used as a state marker for configuration changes.
-    private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
-    private boolean mHasAlarm;
-    // Used to prevent saving changes in event if it is being deleted.
-    private boolean mEventDeletionStarted = false;
-
-    private TextView mTitle;
-    private TextView mWhenDateTime;
-    private TextView mWhere;
-    private Menu mMenu = null;
-    private View mHeadlines;
-    private ScrollView mScrollView;
-    private View mLoadingMsgView;
-    private View mErrorMsgView;
-    private ObjectAnimator mAnimateAlpha;
-    private long mLoadingMsgStartTime;
-
-    private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
-    private int mOriginalColor = -1;
-    private boolean mOriginalColorInitialized = false;
-    private int mCalendarColor = -1;
-    private boolean mCalendarColorInitialized = false;
-    private int mCurrentColor = -1;
-    private boolean mCurrentColorInitialized = false;
-    private int mCurrentColorKey = -1;
-
-    private static final int FADE_IN_TIME = 300;   // in milliseconds
-    private static final int LOADING_MSG_DELAY = 600;   // in milliseconds
-    private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
-    private boolean mNoCrossFade = false;  // Used to prevent repeated cross-fade
-    private RadioGroup mResponseRadioGroup;
-
-    ArrayList<String> mToEmails = new ArrayList<String>();
-    ArrayList<String> mCcEmails = new ArrayList<String>();
-
-
-    private final Runnable mTZUpdater = new Runnable() {
-        @Override
-        public void run() {
-            updateEvent(mView);
-        }
-    };
-
-    private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
-        @Override
-        public void run() {
-            // Since this is run after a delay, make sure to only show the message
-            // if the event's data is not shown yet.
-            if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
-                mLoadingMsgStartTime = System.currentTimeMillis();
-                mLoadingMsgView.setAlpha(1);
-            }
-        }
-    };
-
-    private static int mDialogWidth = 500;
-    private static int mDialogHeight = 600;
-    private static int DIALOG_TOP_MARGIN = 8;
-    private boolean mIsDialog = false;
-    private boolean mIsPaused = true;
-    private boolean mDismissOnResume = false;
-    private int mX = -1;
-    private int mY = -1;
-    private int mMinTop;         // Dialog cannot be above this location
-    private boolean mIsTabletConfig;
-    private Activity mActivity;
-    private Context mContext;
-
-    private CalendarController mController;
-
-    private void sendAccessibilityEventIfQueryDone(int token) {
-        mCurrentQuery |= token;
-        if (mCurrentQuery == TOKEN_QUERY_ALL) {
-            sendAccessibilityEvent();
-        }
-    }
-
-    public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
-            int attendeeResponse, boolean isDialog, int windowStyle) {
-
-        Resources r = context.getResources();
-        if (mScale == 0) {
-            mScale = context.getResources().getDisplayMetrics().density;
-            if (mScale != 1) {
-                mCustomAppIconSize *= mScale;
-                if (isDialog) {
-                    DIALOG_TOP_MARGIN *= mScale;
-                }
-            }
-        }
-        if (isDialog) {
-            setDialogSize(r);
-        }
-        mIsDialog = isDialog;
-
-        setStyle(DialogFragment.STYLE_NO_TITLE, 0);
-        mUri = uri;
-        mStartMillis = startMillis;
-        mEndMillis = endMillis;
-        mAttendeeResponseFromIntent = attendeeResponse;
-        mWindowStyle = windowStyle;
-    }
-
-    // This is currently required by the fragment manager.
-    public EventInfoFragment() {
-    }
-
-    public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
-            int attendeeResponse, boolean isDialog, int windowStyle) {
-        this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
-                endMillis, attendeeResponse, isDialog, windowStyle);
-        mEventId = eventId;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        if (mIsDialog) {
-            applyDialogParams();
-        }
-
-        final Activity activity = getActivity();
-        mContext = activity;
-    }
-
-    private void applyDialogParams() {
-        Dialog dialog = getDialog();
-        dialog.setCanceledOnTouchOutside(true);
-
-        Window window = dialog.getWindow();
-        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
-        WindowManager.LayoutParams a = window.getAttributes();
-        a.dimAmount = .4f;
-
-        a.width = mDialogWidth;
-        a.height = mDialogHeight;
-
-
-        // On tablets , do smart positioning of dialog
-        // On phones , use the whole screen
-
-        if (mX != -1 || mY != -1) {
-            a.x = mX - mDialogWidth / 2;
-            a.y = mY - mDialogHeight / 2;
-            if (a.y < mMinTop) {
-                a.y = mMinTop + DIALOG_TOP_MARGIN;
-            }
-            a.gravity = Gravity.LEFT | Gravity.TOP;
-        }
-        window.setAttributes(a);
-    }
-
-    public void setDialogParams(int x, int y, int minTop) {
-        mX = x;
-        mY = y;
-        mMinTop = minTop;
-    }
-
-    // Implements OnCheckedChangeListener
-    @Override
-    public void onCheckedChanged(RadioGroup group, int checkedId) {
-    }
-
-    public void onNothingSelected(AdapterView<?> parent) {
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        mController.deregisterEventHandler(R.layout.event_info);
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mActivity = activity;
-        // Ensure that mIsTabletConfig is set before creating the menu.
-        mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
-        mController = CalendarController.getInstance(mActivity);
-        mController.registerEventHandler(R.layout.event_info, this);
-
-        if (!mIsDialog) {
-            setHasOptionsMenu(true);
-        }
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        if (mWindowStyle == DIALOG_WINDOW_STYLE) {
-            mView = inflater.inflate(R.layout.event_info_dialog, container, false);
-        } else {
-            mView = inflater.inflate(R.layout.event_info, container, false);
-        }
-        mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
-        mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
-        mErrorMsgView = mView.findViewById(R.id.event_info_error_msg);
-        mTitle = (TextView) mView.findViewById(R.id.title);
-        mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
-        mWhere = (TextView) mView.findViewById(R.id.where);
-        mHeadlines = mView.findViewById(R.id.event_info_headline);
-
-        mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
-
-        mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
-        mAnimateAlpha.setDuration(FADE_IN_TIME);
-        mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
-            int defLayerType;
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                // Use hardware layer for better performance during animation
-                defLayerType = mScrollView.getLayerType();
-                mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                // Ensure that the loading message is gone before showing the
-                // event info
-                mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
-                mLoadingMsgView.setVisibility(View.GONE);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mScrollView.setLayerType(defLayerType, null);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mScrollView.setLayerType(defLayerType, null);
-                // Do not cross fade after the first time
-                mNoCrossFade = true;
-            }
-        });
-
-        mLoadingMsgView.setAlpha(0);
-        mScrollView.setAlpha(0);
-        mErrorMsgView.setVisibility(View.INVISIBLE);
-        mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
-
-        // Hide Edit/Delete buttons if in full screen mode on a phone
-        if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
-            mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE);
-        }
-
-        return mView;
-    }
-
-    private void updateTitle() {
-        Resources res = getActivity().getResources();
-        getActivity().setTitle(res.getString(R.string.event_info_title));
-    }
-
-    /**
-     * Initializes the event cursor, which is expected to point to the first
-     * (and only) result from a query.
-     * @return false if the cursor is empty, true otherwise
-     */
-    private boolean initEventCursor() {
-        if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
-            return false;
-        }
-        mEventCursor.moveToFirst();
-        mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
-        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
-        // mHasAlarm will be true if it was saved in the event already.
-        mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : false;
-        return true;
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        // Show color/edit/delete buttons only in non-dialog configuration
-        if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
-            inflater.inflate(R.menu.event_info_title_bar, menu);
-            mMenu = menu;
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-
-        // If we're a dialog we don't want to handle menu buttons
-        if (mIsDialog) {
-            return false;
-        }
-        // Handles option menu selections:
-        // Home button - close event info activity and start the main calendar
-        // one
-        // Edit button - start the event edit activity and close the info
-        // activity
-        // Delete button - start a delete query that calls a runnable that close
-        // the info activity
-
-        final int itemId = item.getItemId();
-        if (itemId == android.R.id.home) {
-            Utils.returnToCalendarHome(mContext);
-            mActivity.finish();
-            return true;
-        } else if (itemId == R.id.info_action_edit) {
-            mActivity.finish();
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mEventCursor != null) {
-            mEventCursor.close();
-        }
-        if (mCalendarsCursor != null) {
-            mCalendarsCursor.close();
-        }
-        super.onDestroy();
-    }
-
-    /**
-     * Creates an exception to a recurring event.  The only change we're making is to the
-     * "self attendee status" value.  The provider will take care of updating the corresponding
-     * Attendees.attendeeStatus entry.
-     *
-     * @param eventId The recurring event.
-     * @param status The new value for selfAttendeeStatus.
-     */
-    private void createExceptionResponse(long eventId, int status) {
-        ContentValues values = new ContentValues();
-        values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
-        values.put(Events.SELF_ATTENDEE_STATUS, status);
-        values.put(Events.STATUS, Events.STATUS_CONFIRMED);
-
-        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
-        Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
-                String.valueOf(eventId));
-        ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
-   }
-
-    public static int getResponseFromButtonId(int buttonId) {
-        return Attendees.ATTENDEE_STATUS_NONE;
-    }
-
-    public static int findButtonIdForResponse(int response) {
-        return -1;
-    }
-
-    private void displayEventNotFound() {
-        mErrorMsgView.setVisibility(View.VISIBLE);
-        mScrollView.setVisibility(View.GONE);
-        mLoadingMsgView.setVisibility(View.GONE);
-    }
-
-    private void updateEvent(View view) {
-        if (mEventCursor == null || view == null) {
-            return;
-        }
-
-        Context context = view.getContext();
-        if (context == null) {
-            return;
-        }
-
-        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
-        if (eventName == null || eventName.length() == 0) {
-            eventName = getActivity().getString(R.string.no_title_label);
-        }
-
-        // 3rd parties might not have specified the start/end time when firing the
-        // Events.CONTENT_URI intent.  Update these with values read from the db.
-        if (mStartMillis == 0 && mEndMillis == 0) {
-            mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
-            mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
-            if (mEndMillis == 0) {
-                String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
-                if (!TextUtils.isEmpty(duration)) {
-                    try {
-                        Duration d = new Duration();
-                        d.parse(duration);
-                        long endMillis = mStartMillis + d.getMillis();
-                        if (endMillis >= mStartMillis) {
-                            mEndMillis = endMillis;
-                        } else {
-                            Log.d(TAG, "Invalid duration string: " + duration);
-                        }
-                    } catch (DateException e) {
-                        Log.d(TAG, "Error parsing duration string " + duration, e);
-                    }
-                }
-                if (mEndMillis == 0) {
-                    mEndMillis = mStartMillis;
-                }
-            }
-        }
-
-        mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
-        String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
-        String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
-        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
-        String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
-
-        mHeadlines.setBackgroundColor(mCurrentColor);
-
-        // What
-        if (eventName != null) {
-            setTextCommon(view, R.id.title, eventName);
-        }
-
-        // When
-        // Set the date and repeats (if any)
-        String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
-
-        Resources resources = context.getResources();
-        String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
-                System.currentTimeMillis(), localTimezone, mAllDay, context);
-
-        String displayedTimezone = null;
-        if (!mAllDay) {
-            displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
-                    eventTimezone);
-        }
-        // Display the datetime.  Make the timezone (if any) transparent.
-        if (displayedTimezone == null) {
-            setTextCommon(view, R.id.when_datetime, displayedDatetime);
-        } else {
-            int timezoneIndex = displayedDatetime.length();
-            displayedDatetime += "  " + displayedTimezone;
-            SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
-            ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
-                    resources.getColor(R.color.event_info_headline_transparent_color));
-            sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
-                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-            setTextCommon(view, R.id.when_datetime, sb);
-        }
-
-        view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
-
-        // Organizer view is setup in the updateCalendar method
-
-
-        // Where
-        if (location == null || location.trim().length() == 0) {
-            setVisibilityCommon(view, R.id.where, View.GONE);
-        } else {
-            final TextView textView = mWhere;
-            if (textView != null) {
-                textView.setText(location.trim());
-            }
-        }
-
-        // Launch Custom App
-        if (Utils.isJellybeanOrLater()) {
-            updateCustomAppButton();
-        }
-    }
-
-    private void updateCustomAppButton() {
-        setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
-        return;
-    }
-
-    private void sendAccessibilityEvent() {
-        AccessibilityManager am =
-            (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
-        if (!am.isEnabled()) {
-            return;
-        }
-
-        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-        event.setClassName(EventInfoFragment.class.getName());
-        event.setPackageName(getActivity().getPackageName());
-        List<CharSequence> text = event.getText();
-
-        if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
-            int id = mResponseRadioGroup.getCheckedRadioButtonId();
-            if (id != View.NO_ID) {
-                text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
-                text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
-                        .getText() + PERIOD_SPACE));
-            }
-        }
-
-        am.sendAccessibilityEvent(event);
-    }
-
-    private void updateCalendar(View view) {
-
-        mCalendarOwnerAccount = "";
-        if (mCalendarsCursor != null && mEventCursor != null) {
-            mCalendarsCursor.moveToFirst();
-            String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-            mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
-            mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
-            mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
-
-            setVisibilityCommon(view, R.id.organizer_container, View.GONE);
-            mIsBusyFreeCalendar =
-                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
-
-            if (!mIsBusyFreeCalendar) {
-
-                View b = mView.findViewById(R.id.edit);
-                b.setEnabled(true);
-                b.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        // For dialogs, just close the fragment
-                        // For full screen, close activity on phone, leave it for tablet
-                        if (mIsDialog) {
-                            EventInfoFragment.this.dismiss();
-                        }
-                        else if (!mIsTabletConfig){
-                            getActivity().finish();
-                        }
-                    }
-                });
-            }
-            View button;
-            if ((!mIsDialog && !mIsTabletConfig ||
-                    mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
-                mActivity.invalidateOptionsMenu();
-            }
-        } else {
-            setVisibilityCommon(view, R.id.calendar, View.GONE);
-            sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
-        }
-    }
-
-    private void setTextCommon(View view, int id, CharSequence text) {
-        TextView textView = (TextView) view.findViewById(id);
-        if (textView == null)
-            return;
-        textView.setText(text);
-    }
-
-    private void setVisibilityCommon(View view, int id, int visibility) {
-        View v = view.findViewById(id);
-        if (v != null) {
-            v.setVisibility(visibility);
-        }
-        return;
-    }
-
-    @Override
-    public void onPause() {
-        mIsPaused = true;
-        super.onPause();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mIsDialog) {
-            setDialogSize(getActivity().getResources());
-            applyDialogParams();
-        }
-        mIsPaused = false;
-        if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
-            int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
-            mResponseRadioGroup.check(buttonId);
-        }
-    }
-
-    @Override
-    public void eventsChanged() {
-    }
-
-    @Override
-    public long getSupportedEventTypes() {
-        return EventType.EVENTS_CHANGED;
-    }
-
-    @Override
-    public void handleEvent(EventInfo event) {
-        reloadEvents();
-    }
-
-    public void reloadEvents() {
-    }
-
-    @Override
-    public void onClick(View view) {
-    }
-
-    public long getEventId() {
-        return mEventId;
-    }
-
-    public long getStartMillis() {
-        return mStartMillis;
-    }
-    public long getEndMillis() {
-        return mEndMillis;
-    }
-    private void setDialogSize(Resources r) {
-        mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
-        mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
-    }
-}
diff --git a/src/com/android/calendar/EventInfoFragment.kt b/src/com/android/calendar/EventInfoFragment.kt
new file mode 100644
index 0000000..2f007ab
--- /dev/null
+++ b/src/com/android/calendar/EventInfoFragment.kt
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.Activity
+import android.app.Dialog
+import android.app.DialogFragment
+import android.app.Service
+import android.content.ContentProviderOperation
+import android.content.ContentUris
+import android.content.ContentValues
+import android.content.Context
+import android.content.res.Resources
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Events
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.TextUtils
+import android.text.style.ForegroundColorSpan
+import android.util.Log
+import android.util.SparseIntArray
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.widget.AdapterView
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import android.widget.RadioGroup.OnCheckedChangeListener
+import android.widget.ScrollView
+import android.widget.TextView
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import com.android.calendarcommon2.DateException
+import com.android.calendarcommon2.Duration
+import java.util.ArrayList
+
+class EventInfoFragment : DialogFragment, OnCheckedChangeListener, CalendarController.EventHandler,
+    OnClickListener {
+    private var mWindowStyle = DIALOG_WINDOW_STYLE
+    private var mCurrentQuery = 0
+
+    companion object {
+        const val DEBUG = false
+        const val TAG = "EventInfoFragment"
+        internal const val BUNDLE_KEY_EVENT_ID = "key_event_id"
+        internal const val BUNDLE_KEY_START_MILLIS = "key_start_millis"
+        internal const val BUNDLE_KEY_END_MILLIS = "key_end_millis"
+        internal const val BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog"
+        internal const val BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible"
+        internal const val BUNDLE_KEY_WINDOW_STYLE = "key_window_style"
+        internal const val BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color"
+        internal const val BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init"
+        internal const val BUNDLE_KEY_CURRENT_COLOR = "key_current_color"
+        internal const val BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key"
+        internal const val BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init"
+        internal const val BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color"
+        internal const val BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init"
+        internal const val BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response"
+        internal const val BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE = "key_user_set_attendee_response"
+        internal const val BUNDLE_KEY_TENTATIVE_USER_RESPONSE = "key_tentative_user_response"
+        internal const val BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events"
+        internal const val BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes"
+        internal const val BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods"
+        private const val PERIOD_SPACE = ". "
+        private const val NO_EVENT_COLOR = ""
+
+        /**
+         * These are the corresponding indices into the array of strings
+         * "R.array.change_response_labels" in the resource file.
+         */
+        const val UPDATE_SINGLE = 0
+        const val UPDATE_ALL = 1
+
+        // Style of view
+        const val FULL_WINDOW_STYLE = 0
+        const val DIALOG_WINDOW_STYLE = 1
+
+        // Query tokens for QueryHandler
+        private const val TOKEN_QUERY_EVENT = 1 shl 0
+        private const val TOKEN_QUERY_CALENDARS = 1 shl 1
+        private const val TOKEN_QUERY_ATTENDEES = 1 shl 2
+        private const val TOKEN_QUERY_DUPLICATE_CALENDARS = 1 shl 3
+        private const val TOKEN_QUERY_REMINDERS = 1 shl 4
+        private const val TOKEN_QUERY_VISIBLE_CALENDARS = 1 shl 5
+        private const val TOKEN_QUERY_COLORS = 1 shl 6
+        private const val TOKEN_QUERY_ALL = (TOKEN_QUERY_DUPLICATE_CALENDARS
+            or TOKEN_QUERY_ATTENDEES or TOKEN_QUERY_CALENDARS or TOKEN_QUERY_EVENT
+            or TOKEN_QUERY_REMINDERS or TOKEN_QUERY_VISIBLE_CALENDARS or TOKEN_QUERY_COLORS)
+        private val EVENT_PROJECTION = arrayOf<String>(
+            Events._ID, // 0  do not remove; used in DeleteEventHelper
+            Events.TITLE,  // 1  do not remove; used in DeleteEventHelper
+            Events.RRULE,  // 2  do not remove; used in DeleteEventHelper
+            Events.ALL_DAY, // 3  do not remove; used in DeleteEventHelper
+            Events.CALENDAR_ID, // 4  do not remove; used in DeleteEventHelper
+            Events.DTSTART, // 5  do not remove; used in DeleteEventHelper
+            Events._SYNC_ID, // 6  do not remove; used in DeleteEventHelper
+            Events.EVENT_TIMEZONE, // 7  do not remove; used in DeleteEventHelper
+            Events.DESCRIPTION, // 8
+            Events.EVENT_LOCATION, // 9
+            Calendars.CALENDAR_ACCESS_LEVEL, // 10
+            Events.CALENDAR_COLOR, // 11
+            Events.EVENT_COLOR, // 12
+            Events.HAS_ATTENDEE_DATA, // 13
+            Events.ORGANIZER,  // 14
+            Events.HAS_ALARM,  // 15
+            Calendars.MAX_REMINDERS, // 16
+            Calendars.ALLOWED_REMINDERS, // 17
+            Events.CUSTOM_APP_PACKAGE, // 18
+            Events.CUSTOM_APP_URI, // 19
+            Events.DTEND, // 20
+            Events.DURATION, // 21
+            Events.ORIGINAL_SYNC_ID // 22 do not remove; used in DeleteEventHelper
+        )
+        private const val EVENT_INDEX_ID = 0
+        private const val EVENT_INDEX_TITLE = 1
+        private const val EVENT_INDEX_RRULE = 2
+        private const val EVENT_INDEX_ALL_DAY = 3
+        private const val EVENT_INDEX_CALENDAR_ID = 4
+        private const val EVENT_INDEX_DTSTART = 5
+        private const val EVENT_INDEX_SYNC_ID = 6
+        private const val EVENT_INDEX_EVENT_TIMEZONE = 7
+        private const val EVENT_INDEX_DESCRIPTION = 8
+        private const val EVENT_INDEX_EVENT_LOCATION = 9
+        private const val EVENT_INDEX_ACCESS_LEVEL = 10
+        private const val EVENT_INDEX_CALENDAR_COLOR = 11
+        private const val EVENT_INDEX_EVENT_COLOR = 12
+        private const val EVENT_INDEX_HAS_ATTENDEE_DATA = 13
+        private const val EVENT_INDEX_ORGANIZER = 14
+        private const val EVENT_INDEX_HAS_ALARM = 15
+        private const val EVENT_INDEX_MAX_REMINDERS = 16
+        private const val EVENT_INDEX_ALLOWED_REMINDERS = 17
+        private const val EVENT_INDEX_CUSTOM_APP_PACKAGE = 18
+        private const val EVENT_INDEX_CUSTOM_APP_URI = 19
+        private const val EVENT_INDEX_DTEND = 20
+        private const val EVENT_INDEX_DURATION = 21
+        val CALENDARS_PROJECTION = arrayOf<String>(
+            Calendars._ID, // 0
+            Calendars.CALENDAR_DISPLAY_NAME, // 1
+            Calendars.OWNER_ACCOUNT, // 2
+            Calendars.CAN_ORGANIZER_RESPOND, // 3
+            Calendars.ACCOUNT_NAME, // 4
+            Calendars.ACCOUNT_TYPE // 5
+        )
+        const val CALENDARS_INDEX_DISPLAY_NAME = 1
+        const val CALENDARS_INDEX_OWNER_ACCOUNT = 2
+        const val CALENDARS_INDEX_OWNER_CAN_RESPOND = 3
+        const val CALENDARS_INDEX_ACCOUNT_NAME = 4
+        const val CALENDARS_INDEX_ACCOUNT_TYPE = 5
+        val CALENDARS_WHERE: String = Calendars._ID.toString() + "=?"
+        val CALENDARS_DUPLICATE_NAME_WHERE: String =
+            Calendars.CALENDAR_DISPLAY_NAME.toString() + "=?"
+        val CALENDARS_VISIBLE_WHERE: String = Calendars.VISIBLE.toString() + "=?"
+        const val COLORS_INDEX_COLOR = 1
+        const val COLORS_INDEX_COLOR_KEY = 2
+        private var mScale = 0f // Used for supporting different screen densities
+        private var mCustomAppIconSize = 32
+        private const val FADE_IN_TIME = 300 // in milliseconds
+        private const val LOADING_MSG_DELAY = 600 // in milliseconds
+        private const val LOADING_MSG_MIN_DISPLAY_TIME = 600
+        private var mDialogWidth = 500
+        private var mDialogHeight = 600
+        private var DIALOG_TOP_MARGIN = 8
+        fun getResponseFromButtonId(buttonId: Int): Int {
+            return Attendees.ATTENDEE_STATUS_NONE
+        }
+
+        fun findButtonIdForResponse(response: Int): Int {
+            return -1
+        }
+
+        init {
+            if (!Utils.isJellybeanOrLater()) {
+                EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID // nonessential value
+                EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID // nonessential value
+            }
+        }
+    }
+
+    private var mView: View? = null
+    private var mUri: Uri? = null
+    var eventId: Long = 0
+        private set
+    private val mEventCursor: Cursor? = null
+    private val mCalendarsCursor: Cursor? = null
+    var startMillis: Long = 0
+        private set
+    var endMillis: Long = 0
+        private set
+    private var mAllDay = false
+    private var mOwnerCanRespond = false
+    private var mSyncAccountName: String? = null
+    private var mCalendarOwnerAccount: String? = null
+    private var mIsBusyFreeCalendar = false
+    private val mOriginalAttendeeResponse = 0
+    private var mAttendeeResponseFromIntent: Int = Attendees.ATTENDEE_STATUS_NONE
+    private val mUserSetResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+    private val mWhichEvents = -1
+
+    // Used as the temporary response until the dialog is confirmed. It is also
+    // able to be used as a state marker for configuration changes.
+    private val mTentativeUserSetResponse: Int = Attendees.ATTENDEE_STATUS_NONE
+    private var mHasAlarm = false
+
+    // Used to prevent saving changes in event if it is being deleted.
+    private val mEventDeletionStarted = false
+    private var mTitle: TextView? = null
+    private var mWhenDateTime: TextView? = null
+    private var mWhere: TextView? = null
+    private var mMenu: Menu? = null
+    private var mHeadlines: View? = null
+    private var mScrollView: ScrollView? = null
+    private var mLoadingMsgView: View? = null
+    private var mErrorMsgView: View? = null
+    private var mAnimateAlpha: ObjectAnimator? = null
+    private var mLoadingMsgStartTime: Long = 0
+    private val mDisplayColorKeyMap: SparseIntArray = SparseIntArray()
+    private val mOriginalColor = -1
+    private val mOriginalColorInitialized = false
+    private val mCalendarColor = -1
+    private val mCalendarColorInitialized = false
+    private val mCurrentColor = -1
+    private val mCurrentColorInitialized = false
+    private val mCurrentColorKey = -1
+    private var mNoCrossFade = false // Used to prevent repeated cross-fade
+    private var mResponseRadioGroup: RadioGroup? = null
+    var mToEmails: ArrayList<String> = ArrayList<String>()
+    var mCcEmails: ArrayList<String> = ArrayList<String>()
+    private val mTZUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            updateEvent(mView)
+        }
+    }
+    private val mLoadingMsgAlphaUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            // Since this is run after a delay, make sure to only show the message
+            // if the event's data is not shown yet.
+            if (!mAnimateAlpha!!.isRunning() && mScrollView!!.getAlpha() == 0f) {
+                mLoadingMsgStartTime = System.currentTimeMillis()
+                mLoadingMsgView?.setAlpha(1f)
+            }
+        }
+    }
+    private var mIsDialog = false
+    private var mIsPaused = true
+    private val mDismissOnResume = false
+    private var mX = -1
+    private var mY = -1
+    private var mMinTop = 0 // Dialog cannot be above this location
+    private var mIsTabletConfig = false
+    private var mActivity: Activity? = null
+    private var mContext: Context? = null
+    private var mController: CalendarController? = null
+    private fun sendAccessibilityEventIfQueryDone(token: Int) {
+        mCurrentQuery = mCurrentQuery or token
+        if (mCurrentQuery == TOKEN_QUERY_ALL) {
+            sendAccessibilityEvent()
+        }
+    }
+
+    constructor(
+        context: Context,
+        uri: Uri?,
+        startMillis: Long,
+        endMillis: Long,
+        attendeeResponse: Int,
+        isDialog: Boolean,
+        windowStyle: Int
+    ) {
+        val r: Resources = context.getResources()
+        if (mScale == 0f) {
+            mScale = context.getResources().getDisplayMetrics().density
+            if (mScale != 1f) {
+                mCustomAppIconSize *= mScale.toInt()
+                if (isDialog) {
+                    DIALOG_TOP_MARGIN *= mScale.toInt()
+                }
+            }
+        }
+        if (isDialog) {
+            setDialogSize(r)
+        }
+        mIsDialog = isDialog
+        setStyle(DialogFragment.STYLE_NO_TITLE, 0)
+        mUri = uri
+        this.startMillis = startMillis
+        this.endMillis = endMillis
+        mAttendeeResponseFromIntent = attendeeResponse
+        mWindowStyle = windowStyle
+    }
+
+    // This is currently required by the fragment manager.
+    constructor() {}
+    constructor(
+        context: Context?,
+        eventId: Long,
+        startMillis: Long,
+        endMillis: Long,
+        attendeeResponse: Int,
+        isDialog: Boolean,
+        windowStyle: Int
+    ) : this(
+        context as Context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
+        endMillis, attendeeResponse, isDialog, windowStyle
+    ) {
+        this.eventId = eventId
+    }
+
+    @Override
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        if (mIsDialog) {
+            applyDialogParams()
+        }
+        val activity: Activity = getActivity()
+        mContext = activity
+    }
+
+    private fun applyDialogParams() {
+        val dialog: Dialog = getDialog()
+        dialog.setCanceledOnTouchOutside(true)
+        val window: Window? = dialog.getWindow()
+        window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        val a: WindowManager.LayoutParams? = window?.getAttributes()
+        a!!.dimAmount = .4f
+        a!!.width = mDialogWidth
+        a!!.height = mDialogHeight
+
+        // On tablets , do smart positioning of dialog
+        // On phones , use the whole screen
+        if (mX != -1 || mY != -1) {
+            a!!.x = mX - mDialogWidth / 2
+            a!!.y = mY - mDialogHeight / 2
+            if (a!!.y < mMinTop) {
+                a!!.y = mMinTop + DIALOG_TOP_MARGIN
+            }
+            a!!.gravity = Gravity.LEFT or Gravity.TOP
+        }
+        window?.setAttributes(a)
+    }
+
+    fun setDialogParams(x: Int, y: Int, minTop: Int) {
+        mX = x
+        mY = y
+        mMinTop = minTop
+    }
+
+    // Implements OnCheckedChangeListener
+    @Override
+    override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
+    }
+
+    fun onNothingSelected(parent: AdapterView<*>?) {}
+    @Override
+    override fun onDetach() {
+        super.onDetach()
+        mController?.deregisterEventHandler(R.layout.event_info)
+    }
+
+    @Override
+    override fun onAttach(activity: Activity?) {
+        super.onAttach(activity)
+        mActivity = activity
+        // Ensure that mIsTabletConfig is set before creating the menu.
+        mIsTabletConfig = Utils.getConfigBool(mActivity as Context, R.bool.tablet_config)
+        mController = CalendarController.getInstance(mActivity)
+        mController?.registerEventHandler(R.layout.event_info, this)
+        if (!mIsDialog) {
+            setHasOptionsMenu(true)
+        }
+    }
+
+    @Override
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        mView = if (mWindowStyle == DIALOG_WINDOW_STYLE) {
+            inflater.inflate(R.layout.event_info_dialog, container, false)
+        } else {
+            inflater.inflate(R.layout.event_info, container, false)
+        }
+        mScrollView = mView?.findViewById(R.id.event_info_scroll_view) as ScrollView
+        mLoadingMsgView = mView?.findViewById(R.id.event_info_loading_msg)
+        mErrorMsgView = mView?.findViewById(R.id.event_info_error_msg)
+        mTitle = mView?.findViewById(R.id.title) as TextView
+        mWhenDateTime = mView?.findViewById(R.id.when_datetime) as TextView
+        mWhere = mView?.findViewById(R.id.where) as TextView
+        mHeadlines = mView?.findViewById(R.id.event_info_headline)
+        mResponseRadioGroup = mView?.findViewById(R.id.response_value) as RadioGroup
+        mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0f, 1f)
+        mAnimateAlpha?.setDuration(FADE_IN_TIME.toLong())
+        mAnimateAlpha?.addListener(object : AnimatorListenerAdapter() {
+            var defLayerType = 0
+            @Override
+            override fun onAnimationStart(animation: Animator?) {
+                // Use hardware layer for better performance during animation
+                defLayerType = mScrollView?.getLayerType() as Int
+                mScrollView?.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+                // Ensure that the loading message is gone before showing the
+                // event info
+                mLoadingMsgView?.removeCallbacks(mLoadingMsgAlphaUpdater)
+                mLoadingMsgView?.setVisibility(View.GONE)
+            }
+
+            @Override
+            override fun onAnimationCancel(animation: Animator?) {
+                mScrollView?.setLayerType(defLayerType, null)
+            }
+
+            @Override
+            override fun onAnimationEnd(animation: Animator?) {
+                mScrollView?.setLayerType(defLayerType, null)
+                // Do not cross fade after the first time
+                mNoCrossFade = true
+            }
+        })
+        mLoadingMsgView?.setAlpha(0f)
+        mScrollView?.setAlpha(0f)
+        mErrorMsgView?.setVisibility(View.INVISIBLE)
+        mLoadingMsgView?.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY.toLong())
+
+        // Hide Edit/Delete buttons if in full screen mode on a phone
+        if (!mIsDialog && !mIsTabletConfig || mWindowStyle == FULL_WINDOW_STYLE) {
+            mView?.findViewById<View>(R.id.event_info_buttons_container)?.setVisibility(View.GONE)
+        }
+        return mView
+    }
+
+    private fun updateTitle() {
+        val res: Resources = getActivity().getResources()
+        getActivity().setTitle(res.getString(R.string.event_info_title))
+    }
+
+    /**
+     * Initializes the event cursor, which is expected to point to the first
+     * (and only) result from a query.
+     * @return false if the cursor is empty, true otherwise
+     */
+    private fun initEventCursor(): Boolean {
+        if (mEventCursor == null || mEventCursor.getCount() === 0) {
+            return false
+        }
+        mEventCursor.moveToFirst()
+        eventId = mEventCursor.getInt(EVENT_INDEX_ID).toLong()
+        val rRule: String = mEventCursor.getString(EVENT_INDEX_RRULE)
+        // mHasAlarm will be true if it was saved in the event already.
+        mHasAlarm = if (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) === 1) true else false
+        return true
+    }
+
+    @Override
+    override fun onSaveInstanceState(outState: Bundle?) {
+        super.onSaveInstanceState(outState)
+    }
+
+    @Override
+    override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater) {
+        super.onCreateOptionsMenu(menu, inflater)
+        // Show color/edit/delete buttons only in non-dialog configuration
+        if (!mIsDialog && !mIsTabletConfig || mWindowStyle == FULL_WINDOW_STYLE) {
+            inflater.inflate(R.menu.event_info_title_bar, menu)
+            mMenu = menu
+        }
+    }
+
+    @Override
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+
+        // If we're a dialog we don't want to handle menu buttons
+        if (mIsDialog) {
+            return false
+        }
+        // Handles option menu selections:
+        // Home button - close event info activity and start the main calendar
+        // one
+        // Edit button - start the event edit activity and close the info
+        // activity
+        // Delete button - start a delete query that calls a runnable that close
+        // the info activity
+        val itemId: Int = item.getItemId()
+        if (itemId == android.R.id.home) {
+            Utils.returnToCalendarHome(mContext as Context)
+            mActivity?.finish()
+            return true
+        } else if (itemId == R.id.info_action_edit) {
+            mActivity?.finish()
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    @Override
+    override fun onStop() {
+        super.onStop()
+    }
+
+    @Override
+    override fun onDestroy() {
+        if (mEventCursor != null) {
+            mEventCursor.close()
+        }
+        if (mCalendarsCursor != null) {
+            mCalendarsCursor.close()
+        }
+        super.onDestroy()
+    }
+
+    /**
+     * Creates an exception to a recurring event.  The only change we're making is to the
+     * "self attendee status" value.  The provider will take care of updating the corresponding
+     * Attendees.attendeeStatus entry.
+     *
+     * @param eventId The recurring event.
+     * @param status The new value for selfAttendeeStatus.
+     */
+    private fun createExceptionResponse(eventId: Long, status: Int) {
+        val values = ContentValues()
+        values.put(Events.ORIGINAL_INSTANCE_TIME, startMillis)
+        values.put(Events.SELF_ATTENDEE_STATUS, status)
+        values.put(Events.STATUS, Events.STATUS_CONFIRMED)
+        val ops: ArrayList<ContentProviderOperation> = ArrayList<ContentProviderOperation>()
+        val exceptionUri: Uri = Uri.withAppendedPath(
+            Events.CONTENT_EXCEPTION_URI,
+            eventId.toString()
+        )
+        ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build())
+    }
+
+    private fun displayEventNotFound() {
+        mErrorMsgView?.setVisibility(View.VISIBLE)
+        mScrollView?.setVisibility(View.GONE)
+        mLoadingMsgView?.setVisibility(View.GONE)
+    }
+
+    private fun updateEvent(view: View?) {
+        if (mEventCursor == null || view == null) {
+            return
+        }
+        val context: Context = view.getContext() ?: return
+        var eventName: String = mEventCursor.getString(EVENT_INDEX_TITLE)
+        if (eventName == null || eventName.length == 0) {
+            eventName = getActivity().getString(R.string.no_title_label)
+        }
+
+        // 3rd parties might not have specified the start/end time when firing the
+        // Events.CONTENT_URI intent.  Update these with values read from the db.
+        if (startMillis == 0L && endMillis == 0L) {
+            startMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART)
+            endMillis = mEventCursor.getLong(EVENT_INDEX_DTEND)
+            if (endMillis == 0L) {
+                val duration: String = mEventCursor.getString(EVENT_INDEX_DURATION)
+                if (!TextUtils.isEmpty(duration)) {
+                    try {
+                        val d = Duration()
+                        d.parse(duration)
+                        val endMillis: Long = startMillis + d.getMillis()
+                        if (endMillis >= startMillis) {
+                            this.endMillis = endMillis
+                        } else {
+                            Log.d(TAG, "Invalid duration string: $duration")
+                        }
+                    } catch (e: DateException) {
+                        Log.d(TAG, "Error parsing duration string $duration", e)
+                    }
+                }
+                if (endMillis == 0L) {
+                    endMillis = startMillis
+                }
+            }
+        }
+        mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) !== 0
+        val location: String = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION)
+        val description: String = mEventCursor.getString(EVENT_INDEX_DESCRIPTION)
+        val rRule: String = mEventCursor.getString(EVENT_INDEX_RRULE)
+        val eventTimezone: String = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE)
+        mHeadlines?.setBackgroundColor(mCurrentColor)
+
+        // What
+        if (eventName != null) {
+            setTextCommon(view, R.id.title, eventName)
+        }
+
+        // When
+        // Set the date and repeats (if any)
+        val localTimezone: String? = Utils.getTimeZone(mActivity, mTZUpdater)
+        val resources: Resources = context.getResources()
+        var displayedDatetime: String? = Utils.getDisplayedDatetime(
+            startMillis, endMillis,
+            System.currentTimeMillis(), localTimezone as String, mAllDay, context
+        )
+        var displayedTimezone: String? = null
+        if (!mAllDay) {
+            displayedTimezone = Utils.getDisplayedTimezone(
+                startMillis, localTimezone,
+                eventTimezone
+            )
+        }
+        // Display the datetime.  Make the timezone (if any) transparent.
+        if (displayedTimezone == null) {
+            setTextCommon(view, R.id.when_datetime, displayedDatetime as CharSequence)
+        } else {
+            val timezoneIndex: Int = displayedDatetime!!.length
+            displayedDatetime += "  $displayedTimezone"
+            val sb = SpannableStringBuilder(displayedDatetime)
+            val transparentColorSpan = ForegroundColorSpan(
+                resources.getColor(R.color.event_info_headline_transparent_color)
+            )
+            sb.setSpan(
+                transparentColorSpan, timezoneIndex, displayedDatetime!!.length,
+                Spannable.SPAN_INCLUSIVE_INCLUSIVE
+            )
+            setTextCommon(view, R.id.when_datetime, sb)
+        }
+        view.findViewById<View>(R.id.when_repeat).setVisibility(View.GONE)
+
+        // Organizer view is setup in the updateCalendar method
+
+        // Where
+        if (location == null || location.trim().length == 0) {
+            setVisibilityCommon(view, R.id.where, View.GONE)
+        } else {
+            val textView: TextView? = mWhere
+            if (textView != null) {
+                textView.setText(location.trim())
+            }
+        }
+
+        // Launch Custom App
+        if (Utils.isJellybeanOrLater()) {
+            updateCustomAppButton()
+        }
+    }
+
+    private fun updateCustomAppButton() {
+        setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE)
+        return
+    }
+
+    private fun sendAccessibilityEvent() {
+        val am: AccessibilityManager =
+            getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        if (!am.isEnabled()) {
+            return
+        }
+        val event: AccessibilityEvent =
+            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+        event.setClassName(EventInfoFragment::class.java.getName())
+        event.setPackageName(getActivity().getPackageName())
+        var text = event.getText()
+        if (mResponseRadioGroup?.getVisibility() == View.VISIBLE) {
+            val id: Int = mResponseRadioGroup!!.getCheckedRadioButtonId()
+            if (id != View.NO_ID) {
+                text.add((getView()?.findViewById(R.id.response_label) as TextView)?.getText())
+                text.add(
+                    (mResponseRadioGroup?.findViewById(id) as RadioButton)
+                        .getText().toString() + PERIOD_SPACE
+                )
+            }
+        }
+        am.sendAccessibilityEvent(event)
+    }
+
+    private fun updateCalendar(view: View?) {
+        mCalendarOwnerAccount = ""
+        if (mCalendarsCursor != null && mEventCursor != null) {
+            mCalendarsCursor.moveToFirst()
+            val tempAccount: String = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT)
+            mCalendarOwnerAccount = tempAccount ?: ""
+            mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) !== 0
+            mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME)
+            setVisibilityCommon(view, R.id.organizer_container, View.GONE)
+            mIsBusyFreeCalendar =
+                mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) === Calendars.CAL_ACCESS_FREEBUSY
+            if (!mIsBusyFreeCalendar) {
+                val b: View? = mView?.findViewById(R.id.edit)
+                b?.setEnabled(true)
+                b?.setOnClickListener(object : OnClickListener {
+                    @Override
+                    override fun onClick(v: View?) {
+                        // For dialogs, just close the fragment
+                        // For full screen, close activity on phone, leave it for tablet
+                        if (mIsDialog) {
+                            this@EventInfoFragment.dismiss()
+                        } else if (!mIsTabletConfig) {
+                            getActivity().finish()
+                        }
+                    }
+                })
+            }
+            var button: View
+            if ((!mIsDialog && !mIsTabletConfig ||
+                mWindowStyle == FULL_WINDOW_STYLE) && mMenu != null
+            ) {
+                mActivity?.invalidateOptionsMenu()
+            }
+        } else {
+            setVisibilityCommon(view, R.id.calendar, View.GONE)
+            sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS)
+        }
+    }
+
+    private fun setTextCommon(view: View, id: Int, text: CharSequence) {
+        val textView: TextView = view.findViewById(id) as TextView ?: return
+        textView.setText(text)
+    }
+
+    private fun setVisibilityCommon(view: View?, id: Int, visibility: Int) {
+        val v: View? = view?.findViewById(id)
+        if (v != null) {
+            v.setVisibility(visibility)
+        }
+        return
+    }
+
+    @Override
+    override fun onPause() {
+        mIsPaused = true
+        super.onPause()
+    }
+
+    @Override
+    override fun onResume() {
+        super.onResume()
+        if (mIsDialog) {
+            setDialogSize(getActivity().getResources())
+            applyDialogParams()
+        }
+        mIsPaused = false
+        if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
+            val buttonId = findButtonIdForResponse(mTentativeUserSetResponse)
+            mResponseRadioGroup?.check(buttonId)
+        }
+    }
+
+    @Override
+    override fun eventsChanged() {
+    }
+
+    @get:Override override val supportedEventTypes: Long
+        get() = EventType.EVENTS_CHANGED
+
+    @Override
+    override fun handleEvent(event: EventInfo?) {
+        reloadEvents()
+    }
+
+    fun reloadEvents() {}
+    @Override
+    override fun onClick(view: View?) {
+    }
+
+    private fun setDialogSize(r: Resources) {
+        mDialogWidth = r.getDimension(R.dimen.event_info_dialog_width).toInt()
+        mDialogHeight = r.getDimension(R.dimen.event_info_dialog_height).toInt()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/EventLoader.java b/src/com/android/calendar/EventLoader.java
deleted file mode 100644
index d34b1c7..0000000
--- a/src/com/android/calendar/EventLoader.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Handler;
-import android.os.Process;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.EventDays;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class EventLoader {
-
-    private Context mContext;
-    private Handler mHandler = new Handler();
-    private AtomicInteger mSequenceNumber = new AtomicInteger();
-
-    private LinkedBlockingQueue<LoadRequest> mLoaderQueue;
-    private LoaderThread mLoaderThread;
-    private ContentResolver mResolver;
-
-    private static interface LoadRequest {
-        public void processRequest(EventLoader eventLoader);
-        public void skipRequest(EventLoader eventLoader);
-    }
-
-    private static class ShutdownRequest implements LoadRequest {
-        public void processRequest(EventLoader eventLoader) {
-        }
-
-        public void skipRequest(EventLoader eventLoader) {
-        }
-    }
-
-    /**
-     *
-     * Code for handling requests to get whether days have an event or not
-     * and filling in the eventDays array.
-     *
-     */
-    private static class LoadEventDaysRequest implements LoadRequest {
-        public int startDay;
-        public int numDays;
-        public boolean[] eventDays;
-        public Runnable uiCallback;
-
-        /**
-         * The projection used by the EventDays query.
-         */
-        private static final String[] PROJECTION = {
-                CalendarContract.EventDays.STARTDAY, CalendarContract.EventDays.ENDDAY
-        };
-
-        public LoadEventDaysRequest(int startDay, int numDays, boolean[] eventDays,
-                final Runnable uiCallback)
-        {
-            this.startDay = startDay;
-            this.numDays = numDays;
-            this.eventDays = eventDays;
-            this.uiCallback = uiCallback;
-        }
-
-        @Override
-        public void processRequest(EventLoader eventLoader)
-        {
-            final Handler handler = eventLoader.mHandler;
-            ContentResolver cr = eventLoader.mResolver;
-
-            // Clear the event days
-            Arrays.fill(eventDays, false);
-
-            //query which days have events
-            Cursor cursor = EventDays.query(cr, startDay, numDays, PROJECTION);
-            try {
-                int startDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.STARTDAY);
-                int endDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.ENDDAY);
-
-                //Set all the days with events to true
-                while (cursor.moveToNext()) {
-                    int firstDay = cursor.getInt(startDayColumnIndex);
-                    int lastDay = cursor.getInt(endDayColumnIndex);
-                    //we want the entire range the event occurs, but only within the month
-                    int firstIndex = Math.max(firstDay - startDay, 0);
-                    int lastIndex = Math.min(lastDay - startDay, 30);
-
-                    for(int i = firstIndex; i <= lastIndex; i++) {
-                        eventDays[i] = true;
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-            handler.post(uiCallback);
-        }
-
-        @Override
-        public void skipRequest(EventLoader eventLoader) {
-        }
-    }
-
-    private static class LoadEventsRequest implements LoadRequest {
-
-        public int id;
-        public int startDay;
-        public int numDays;
-        public ArrayList<Event> events;
-        public Runnable successCallback;
-        public Runnable cancelCallback;
-
-        public LoadEventsRequest(int id, int startDay, int numDays, ArrayList<Event> events,
-                final Runnable successCallback, final Runnable cancelCallback) {
-            this.id = id;
-            this.startDay = startDay;
-            this.numDays = numDays;
-            this.events = events;
-            this.successCallback = successCallback;
-            this.cancelCallback = cancelCallback;
-        }
-
-        public void processRequest(EventLoader eventLoader) {
-            Event.loadEvents(eventLoader.mContext, events, startDay,
-                    numDays, id, eventLoader.mSequenceNumber);
-
-            // Check if we are still the most recent request.
-            if (id == eventLoader.mSequenceNumber.get()) {
-                eventLoader.mHandler.post(successCallback);
-            } else {
-                eventLoader.mHandler.post(cancelCallback);
-            }
-        }
-
-        public void skipRequest(EventLoader eventLoader) {
-            eventLoader.mHandler.post(cancelCallback);
-        }
-    }
-
-    private static class LoaderThread extends Thread {
-        LinkedBlockingQueue<LoadRequest> mQueue;
-        EventLoader mEventLoader;
-
-        public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) {
-            mQueue = queue;
-            mEventLoader = eventLoader;
-        }
-
-        public void shutdown() {
-            try {
-                mQueue.put(new ShutdownRequest());
-            } catch (InterruptedException ex) {
-                // The put() method fails with InterruptedException if the
-                // queue is full. This should never happen because the queue
-                // has no limit.
-                Log.e("Cal", "LoaderThread.shutdown() interrupted!");
-            }
-        }
-
-        @Override
-        public void run() {
-            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            while (true) {
-                try {
-                    // Wait for the next request
-                    LoadRequest request = mQueue.take();
-
-                    // If there are a bunch of requests already waiting, then
-                    // skip all but the most recent request.
-                    while (!mQueue.isEmpty()) {
-                        // Let the request know that it was skipped
-                        request.skipRequest(mEventLoader);
-
-                        // Skip to the next request
-                        request = mQueue.take();
-                    }
-
-                    if (request instanceof ShutdownRequest) {
-                        return;
-                    }
-                    request.processRequest(mEventLoader);
-                } catch (InterruptedException ex) {
-                    Log.e("Cal", "background LoaderThread interrupted!");
-                }
-            }
-        }
-    }
-
-    public EventLoader(Context context) {
-        mContext = context;
-        mLoaderQueue = new LinkedBlockingQueue<LoadRequest>();
-        mResolver = context.getContentResolver();
-    }
-
-    /**
-     * Call this from the activity's onResume()
-     */
-    public void startBackgroundThread() {
-        mLoaderThread = new LoaderThread(mLoaderQueue, this);
-        mLoaderThread.start();
-    }
-
-    /**
-     * Call this from the activity's onPause()
-     */
-    public void stopBackgroundThread() {
-        mLoaderThread.shutdown();
-    }
-
-    /**
-     * Loads "numDays" days worth of events, starting at start, into events.
-     * Posts uiCallback to the {@link Handler} for this view, which will run in the UI thread.
-     * Reuses an existing background thread, if events were already being loaded in the background.
-     * NOTE: events and uiCallback are not used if an existing background thread gets reused --
-     * the ones that were passed in on the call that results in the background thread getting
-     * created are used, and the most recent call's worth of data is loaded into events and posted
-     * via the uiCallback.
-     */
-    public void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
-            int startDay, final Runnable successCallback, final Runnable cancelCallback) {
-
-        // Increment the sequence number for requests.  We don't care if the
-        // sequence numbers wrap around because we test for equality with the
-        // latest one.
-        int id = mSequenceNumber.incrementAndGet();
-
-        // Send the load request to the background thread
-        LoadEventsRequest request = new LoadEventsRequest(id, startDay, numDays,
-                events, successCallback, cancelCallback);
-
-        try {
-            mLoaderQueue.put(request);
-        } catch (InterruptedException ex) {
-            // The put() method fails with InterruptedException if the
-            // queue is full. This should never happen because the queue
-            // has no limit.
-            Log.e("Cal", "loadEventsInBackground() interrupted!");
-        }
-    }
-
-    /**
-     * Sends a request for the days with events to be marked. Loads "numDays"
-     * worth of days, starting at start, and fills in eventDays to express which
-     * days have events.
-     *
-     * @param startDay First day to check for events
-     * @param numDays Days following the start day to check
-     * @param eventDay Whether or not an event exists on that day
-     * @param uiCallback What to do when done (log data, redraw screen)
-     */
-    void loadEventDaysInBackground(int startDay, int numDays, boolean[] eventDays,
-        final Runnable uiCallback)
-    {
-        // Send load request to the background thread
-        LoadEventDaysRequest request = new LoadEventDaysRequest(startDay, numDays,
-                eventDays, uiCallback);
-        try {
-            mLoaderQueue.put(request);
-        } catch (InterruptedException ex) {
-            // The put() method fails with InterruptedException if the
-            // queue is full. This should never happen because the queue
-            // has no limit.
-            Log.e("Cal", "loadEventDaysInBackground() interrupted!");
-        }
-    }
-}
diff --git a/src/com/android/calendar/EventLoader.kt b/src/com/android/calendar/EventLoader.kt
new file mode 100644
index 0000000..a05e8a2
--- /dev/null
+++ b/src/com/android/calendar/EventLoader.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.os.Handler
+import android.os.Process
+import android.provider.CalendarContract
+import android.provider.CalendarContract.EventDays
+import android.util.Log
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.atomic.AtomicInteger
+
+class EventLoader(context: Context) {
+    private val mContext: Context
+    private val mHandler: Handler = Handler()
+    private val mSequenceNumber: AtomicInteger? = AtomicInteger()
+    private val mLoaderQueue: LinkedBlockingQueue<LoadRequest>
+    private var mLoaderThread: LoaderThread? = null
+    private val mResolver: ContentResolver
+
+    private interface LoadRequest {
+        fun processRequest(eventLoader: EventLoader?)
+        fun skipRequest(eventLoader: EventLoader?)
+    }
+
+    private class ShutdownRequest : LoadRequest {
+        override fun processRequest(eventLoader: EventLoader?) {}
+        override fun skipRequest(eventLoader: EventLoader?) {}
+    }
+
+    /**
+     *
+     * Code for handling requests to get whether days have an event or not
+     * and filling in the eventDays array.
+     *
+     */
+    private class LoadEventDaysRequest(
+        var startDay: Int,
+        var numDays: Int,
+        var eventDays: BooleanArray,
+        uiCallback: Runnable
+    ) : LoadRequest {
+        var uiCallback: Runnable
+        @Override
+        override fun processRequest(eventLoader: EventLoader?) {
+            val handler: Handler? = eventLoader?.mHandler
+            val cr: ContentResolver? = eventLoader?.mResolver
+
+            // Clear the event days
+            Arrays.fill(eventDays, false)
+
+            // query which days have events
+            val cursor: Cursor = EventDays.query(cr, startDay, numDays, PROJECTION)
+            try {
+                val startDayColumnIndex: Int = cursor.getColumnIndexOrThrow(EventDays.STARTDAY)
+                val endDayColumnIndex: Int = cursor.getColumnIndexOrThrow(EventDays.ENDDAY)
+
+                // Set all the days with events to true
+                while (cursor.moveToNext()) {
+                    val firstDay: Int = cursor.getInt(startDayColumnIndex)
+                    val lastDay: Int = cursor.getInt(endDayColumnIndex)
+                    // we want the entire range the event occurs, but only within the month
+                    val firstIndex: Int = Math.max(firstDay - startDay, 0)
+                    val lastIndex: Int = Math.min(lastDay - startDay, 30)
+                    for (i in firstIndex..lastIndex) {
+                        eventDays[i] = true
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close()
+                }
+            }
+            handler?.post(uiCallback)
+        }
+
+        @Override
+        override fun skipRequest(eventLoader: EventLoader?) {
+        }
+
+        companion object {
+            /**
+             * The projection used by the EventDays query.
+             */
+            private val PROJECTION = arrayOf<String>(
+                    CalendarContract.EventDays.STARTDAY, CalendarContract.EventDays.ENDDAY
+            )
+        }
+
+        init {
+            this.uiCallback = uiCallback
+        }
+    }
+
+    private class LoadEventsRequest(
+        var id: Int,
+        var startDay: Int,
+        var numDays: Int,
+        events: ArrayList<Event?>,
+        successCallback: Runnable,
+        cancelCallback: Runnable
+    ) : LoadRequest {
+        var events: ArrayList<Event?>
+        var successCallback: Runnable
+        var cancelCallback: Runnable
+        @Override
+        override fun processRequest(eventLoader: EventLoader?) {
+            Event.loadEvents(eventLoader?.mContext, events, startDay,
+                    numDays, id, eventLoader?.mSequenceNumber)
+
+            // Check if we are still the most recent request.
+            if (id == eventLoader?.mSequenceNumber?.get()) {
+                eventLoader?.mHandler?.post(successCallback)
+            } else {
+                eventLoader?.mHandler?.post(cancelCallback)
+            }
+        }
+
+        @Override
+        override fun skipRequest(eventLoader: EventLoader?) {
+            eventLoader?.mHandler?.post(cancelCallback)
+        }
+
+        init {
+            this.events = events
+            this.successCallback = successCallback
+            this.cancelCallback = cancelCallback
+        }
+    }
+
+    private class LoaderThread(
+        queue: LinkedBlockingQueue<LoadRequest>,
+        eventLoader: EventLoader
+    ) : Thread() {
+        var mQueue: LinkedBlockingQueue<LoadRequest>
+        var mEventLoader: EventLoader
+        fun shutdown() {
+            try {
+                mQueue.put(ShutdownRequest())
+            } catch (ex: InterruptedException) {
+                // The put() method fails with InterruptedException if the
+                // queue is full. This should never happen because the queue
+                // has no limit.
+                Log.e("Cal", "LoaderThread.shutdown() interrupted!")
+            }
+        }
+
+        @Override
+        override fun run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
+            while (true) {
+                try {
+                    // Wait for the next request
+                    var request: LoadRequest = mQueue.take()
+
+                    // If there are a bunch of requests already waiting, then
+                    // skip all but the most recent request.
+                    while (!mQueue.isEmpty()) {
+                        // Let the request know that it was skipped
+                        request.skipRequest(mEventLoader)
+
+                        // Skip to the next request
+                        request = mQueue.take()
+                    }
+                    if (request is ShutdownRequest) {
+                        return
+                    }
+                    request.processRequest(mEventLoader)
+                } catch (ex: InterruptedException) {
+                    Log.e("Cal", "background LoaderThread interrupted!")
+                }
+            }
+        }
+
+        init {
+            mQueue = queue
+            mEventLoader = eventLoader
+        }
+    }
+
+    /**
+     * Call this from the activity's onResume()
+     */
+    fun startBackgroundThread() {
+        mLoaderThread = LoaderThread(mLoaderQueue, this)
+        mLoaderThread?.start()
+    }
+
+    /**
+     * Call this from the activity's onPause()
+     */
+    fun stopBackgroundThread() {
+        mLoaderThread!!.shutdown()
+    }
+
+    /**
+     * Loads "numDays" days worth of events, starting at start, into events.
+     * Posts uiCallback to the [Handler] for this view, which will run in the UI thread.
+     * Reuses an existing background thread, if events were already being loaded in the background.
+     * NOTE: events and uiCallback are not used if an existing background thread gets reused --
+     * the ones that were passed in on the call that results in the background thread getting
+     * created are used, and the most recent call's worth of data is loaded into events and posted
+     * via the uiCallback.
+     */
+    fun loadEventsInBackground(
+        numDays: Int,
+        events: ArrayList<Event?>,
+        startDay: Int,
+        successCallback: Runnable,
+        cancelCallback: Runnable
+    ) {
+
+        // Increment the sequence number for requests.  We don't care if the
+        // sequence numbers wrap around because we test for equality with the
+        // latest one.
+        val id: Int = mSequenceNumber?.incrementAndGet() as Int
+
+        // Send the load request to the background thread
+        val request = LoadEventsRequest(id, startDay, numDays,
+                events, successCallback, cancelCallback)
+        try {
+            mLoaderQueue.put(request)
+        } catch (ex: InterruptedException) {
+            // The put() method fails with InterruptedException if the
+            // queue is full. This should never happen because the queue
+            // has no limit.
+            Log.e("Cal", "loadEventsInBackground() interrupted!")
+        }
+    }
+
+    /**
+     * Sends a request for the days with events to be marked. Loads "numDays"
+     * worth of days, starting at start, and fills in eventDays to express which
+     * days have events.
+     *
+     * @param startDay First day to check for events
+     * @param numDays Days following the start day to check
+     * @param eventDay Whether or not an event exists on that day
+     * @param uiCallback What to do when done (log data, redraw screen)
+     */
+    fun loadEventDaysInBackground(
+        startDay: Int,
+        numDays: Int,
+        eventDays: BooleanArray,
+        uiCallback: Runnable
+    ) {
+        // Send load request to the background thread
+        val request = LoadEventDaysRequest(startDay, numDays,
+                eventDays, uiCallback)
+        try {
+            mLoaderQueue.put(request)
+        } catch (ex: InterruptedException) {
+            // The put() method fails with InterruptedException if the
+            // queue is full. This should never happen because the queue
+            // has no limit.
+            Log.e("Cal", "loadEventDaysInBackground() interrupted!")
+        }
+    }
+
+    init {
+        mContext = context
+        mLoaderQueue = LinkedBlockingQueue<LoadRequest>()
+        mResolver = context.getContentResolver()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/GeneralPreferences.java b/src/com/android/calendar/GeneralPreferences.java
deleted file mode 100644
index a42f07e..0000000
--- a/src/com/android/calendar/GeneralPreferences.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.backup.BackupManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Vibrator;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.preference.RingtonePreference;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.CalendarCache;
-import android.provider.SearchRecentSuggestions;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.widget.Toast;
-
-import com.android.calendar.alerts.AlertReceiver;
-import com.android.timezonepicker.TimeZoneInfo;
-import com.android.timezonepicker.TimeZonePickerDialog;
-import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener;
-import com.android.timezonepicker.TimeZonePickerUtils;
-
-public class GeneralPreferences extends PreferenceFragment implements
-        OnSharedPreferenceChangeListener, OnPreferenceChangeListener, OnTimeZoneSetListener {
-    // The name of the shared preferences file. This name must be maintained for historical
-    // reasons, as it's what PreferenceManager assigned the first time the file was created.
-    static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-    static final String SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup";
-
-    private static final String FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker";
-
-    // Preference keys
-    public static final String KEY_HIDE_DECLINED = "preferences_hide_declined";
-    public static final String KEY_WEEK_START_DAY = "preferences_week_start_day";
-    public static final String KEY_SHOW_WEEK_NUM = "preferences_show_week_num";
-    public static final String KEY_DAYS_PER_WEEK = "preferences_days_per_week";
-    public static final String KEY_SKIP_SETUP = "preferences_skip_setup";
-
-    public static final String KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history";
-
-    public static final String KEY_ALERTS_CATEGORY = "preferences_alerts_category";
-    public static final String KEY_ALERTS = "preferences_alerts";
-    public static final String KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate";
-    public static final String KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone";
-    public static final String KEY_ALERTS_POPUP = "preferences_alerts_popup";
-
-    public static final String KEY_SHOW_CONTROLS = "preferences_show_controls";
-
-    public static final String KEY_DEFAULT_REMINDER = "preferences_default_reminder";
-    public static final int NO_REMINDER = -1;
-    public static final String NO_REMINDER_STRING = "-1";
-    public static final int REMINDER_DEFAULT_TIME = 10; // in minutes
-
-    public static final String KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height";
-    public static final String KEY_VERSION = "preferences_version";
-
-    /** Key to SharePreference for default view (CalendarController.ViewType) */
-    public static final String KEY_START_VIEW = "preferred_startView";
-    /**
-     *  Key to SharePreference for default detail view (CalendarController.ViewType)
-     *  Typically used by widget
-     */
-    public static final String KEY_DETAILED_VIEW = "preferred_detailedView";
-    public static final String KEY_DEFAULT_CALENDAR = "preference_defaultCalendar";
-
-    // These must be in sync with the array preferences_week_start_day_values
-    public static final String WEEK_START_DEFAULT = "-1";
-    public static final String WEEK_START_SATURDAY = "7";
-    public static final String WEEK_START_SUNDAY = "1";
-    public static final String WEEK_START_MONDAY = "2";
-
-    // These keys are kept to enable migrating users from previous versions
-    private static final String KEY_ALERTS_TYPE = "preferences_alerts_type";
-    private static final String ALERT_TYPE_ALERTS = "0";
-    private static final String ALERT_TYPE_STATUS_BAR = "1";
-    private static final String ALERT_TYPE_OFF = "2";
-    static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
-    static final String KEY_HOME_TZ = "preferences_home_tz";
-
-    // Default preference values
-    public static final int DEFAULT_START_VIEW = CalendarController.ViewType.WEEK;
-    public static final int DEFAULT_DETAILED_VIEW = CalendarController.ViewType.DAY;
-    public static final boolean DEFAULT_SHOW_WEEK_NUM = false;
-    // This should match the XML file.
-    public static final String DEFAULT_RINGTONE = "content://settings/system/notification_sound";
-
-    CheckBoxPreference mAlert;
-    CheckBoxPreference mVibrate;
-    CheckBoxPreference mPopup;
-    CheckBoxPreference mUseHomeTZ;
-    CheckBoxPreference mHideDeclined;
-    Preference mHomeTZ;
-    TimeZonePickerUtils mTzPickerUtils;
-    ListPreference mWeekStart;
-    ListPreference mDefaultReminder;
-
-    private String mTimeZoneId;
-
-    /** Return a properly configured SharedPreferences instance */
-    public static SharedPreferences getSharedPreferences(Context context) {
-        return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
-    }
-
-    /** Set the default shared preferences in the proper context */
-    public static void setDefaultValues(Context context) {
-        PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
-                R.xml.general_preferences, false);
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final Activity activity = getActivity();
-
-        // Make sure to always use the same preferences file regardless of the package name
-        // we're running under
-        final PreferenceManager preferenceManager = getPreferenceManager();
-        final SharedPreferences sharedPreferences = getSharedPreferences(activity);
-        preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME);
-
-        // Load the preferences from an XML resource
-        addPreferencesFromResource(R.xml.general_preferences);
-
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        mAlert = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS);
-        mVibrate = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE);
-        Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
-        if (vibrator == null || !vibrator.hasVibrator()) {
-            PreferenceCategory mAlertGroup = (PreferenceCategory) preferenceScreen
-                    .findPreference(KEY_ALERTS_CATEGORY);
-            mAlertGroup.removePreference(mVibrate);
-        }
-
-        mPopup = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_POPUP);
-        mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED);
-        mHideDeclined = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HIDE_DECLINED);
-        mWeekStart = (ListPreference) preferenceScreen.findPreference(KEY_WEEK_START_DAY);
-        mDefaultReminder = (ListPreference) preferenceScreen.findPreference(KEY_DEFAULT_REMINDER);
-        mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ);
-        mWeekStart.setSummary(mWeekStart.getEntry());
-        mDefaultReminder.setSummary(mDefaultReminder.getEntry());
-
-        // This triggers an asynchronous call to the provider to refresh the data in shared pref
-        mTimeZoneId = Utils.getTimeZone(activity, null);
-
-        SharedPreferences prefs = CalendarUtils.getSharedPreferences(activity,
-                Utils.SHARED_PREFS_NAME);
-
-        // Utils.getTimeZone will return the currentTimeZone instead of the one
-        // in the shared_pref if home time zone is disabled. So if home tz is
-        // off, we will explicitly read it.
-        if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) {
-            mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
-        }
-
-        mHomeTZ.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                showTimezoneDialog();
-                return true;
-            }
-        });
-
-        if (mTzPickerUtils == null) {
-            mTzPickerUtils = new TimeZonePickerUtils(getActivity());
-        }
-        CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(getActivity(), mTimeZoneId,
-                System.currentTimeMillis(), false);
-        mHomeTZ.setSummary(timezoneName != null ? timezoneName : mTimeZoneId);
-
-        TimeZonePickerDialog tzpd = (TimeZonePickerDialog) activity.getFragmentManager()
-                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
-        if (tzpd != null) {
-            tzpd.setOnTimeZoneSetListener(this);
-        }
-
-        migrateOldPreferences(sharedPreferences);
-
-        updateChildPreferences();
-    }
-
-    private void showTimezoneDialog() {
-        final Activity activity = getActivity();
-        if (activity == null) {
-            return;
-        }
-
-        Bundle b = new Bundle();
-        b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis());
-        b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null));
-
-        FragmentManager fm = getActivity().getFragmentManager();
-        TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm
-                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
-        if (tzpd != null) {
-            tzpd.dismiss();
-        }
-        tzpd = new TimeZonePickerDialog();
-        tzpd.setArguments(b);
-        tzpd.setOnTimeZoneSetListener(this);
-        tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        getPreferenceScreen().getSharedPreferences()
-                .registerOnSharedPreferenceChangeListener(this);
-        setPreferenceListeners(this);
-    }
-
-    /**
-     * Sets up all the preference change listeners to use the specified
-     * listener.
-     */
-    private void setPreferenceListeners(OnPreferenceChangeListener listener) {
-        mUseHomeTZ.setOnPreferenceChangeListener(listener);
-        mHomeTZ.setOnPreferenceChangeListener(listener);
-        mWeekStart.setOnPreferenceChangeListener(listener);
-        mDefaultReminder.setOnPreferenceChangeListener(listener);
-        mHideDeclined.setOnPreferenceChangeListener(listener);
-        mVibrate.setOnPreferenceChangeListener(listener);
-    }
-
-    @Override
-    public void onStop() {
-        getPreferenceScreen().getSharedPreferences()
-                .unregisterOnSharedPreferenceChangeListener(this);
-        setPreferenceListeners(null);
-        super.onStop();
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        Activity a = getActivity();
-        if (key.equals(KEY_ALERTS)) {
-            updateChildPreferences();
-            if (a != null) {
-                Intent intent = new Intent();
-                intent.setClass(a, AlertReceiver.class);
-                if (mAlert.isChecked()) {
-                    intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS);
-                } else {
-                    intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION);
-                }
-                a.sendBroadcast(intent);
-            }
-        }
-        if (a != null) {
-            BackupManager.dataChanged(a.getPackageName());
-        }
-    }
-
-    /**
-     * Handles time zone preference changes
-     */
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        String tz;
-        final Activity activity = getActivity();
-        if (preference == mUseHomeTZ) {
-            if ((Boolean)newValue) {
-                tz = mTimeZoneId;
-            } else {
-                tz = CalendarCache.TIMEZONE_TYPE_AUTO;
-            }
-            Utils.setTimeZone(activity, tz);
-            return true;
-        } else if (preference == mHideDeclined) {
-            mHideDeclined.setChecked((Boolean) newValue);
-            Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(activity));
-            intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE);
-            activity.sendBroadcast(intent);
-            return true;
-        } else if (preference == mWeekStart) {
-            mWeekStart.setValue((String) newValue);
-            mWeekStart.setSummary(mWeekStart.getEntry());
-        } else if (preference == mDefaultReminder) {
-            mDefaultReminder.setValue((String) newValue);
-            mDefaultReminder.setSummary(mDefaultReminder.getEntry());
-        } else if (preference == mVibrate) {
-            mVibrate.setChecked((Boolean) newValue);
-            return true;
-        } else {
-            return true;
-        }
-        return false;
-    }
-
-    public String getRingtoneTitleFromUri(Context context, String uri) {
-        if (TextUtils.isEmpty(uri)) {
-            return null;
-        }
-
-        Ringtone ring = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri));
-        if (ring != null) {
-            return ring.getTitle(context);
-        }
-        return null;
-    }
-
-    /**
-     * If necessary, upgrades previous versions of preferences to the current
-     * set of keys and values.
-     * @param prefs the preferences to upgrade
-     */
-    private void migrateOldPreferences(SharedPreferences prefs) {
-        // If needed, migrate vibration setting from a previous version
-
-        mVibrate.setChecked(Utils.getDefaultVibrate(getActivity(), prefs));
-
-        // If needed, migrate the old alerts type settin
-        if (!prefs.contains(KEY_ALERTS) && prefs.contains(KEY_ALERTS_TYPE)) {
-            String type = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR);
-            if (type.equals(ALERT_TYPE_OFF)) {
-                mAlert.setChecked(false);
-                mPopup.setChecked(false);
-                mPopup.setEnabled(false);
-            } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
-                mAlert.setChecked(true);
-                mPopup.setChecked(false);
-                mPopup.setEnabled(true);
-            } else if (type.equals(ALERT_TYPE_ALERTS)) {
-                mAlert.setChecked(true);
-                mPopup.setChecked(true);
-                mPopup.setEnabled(true);
-            }
-            // clear out the old setting
-            prefs.edit().remove(KEY_ALERTS_TYPE).commit();
-        }
-    }
-
-    /**
-     * Keeps the dependent settings in sync with the parent preference, so for
-     * example, when notifications are turned off, we disable the preferences
-     * for configuring the exact notification behavior.
-     */
-    private void updateChildPreferences() {
-        if (mAlert.isChecked()) {
-            mVibrate.setEnabled(true);
-            mPopup.setEnabled(true);
-        } else {
-            mVibrate.setEnabled(false);
-            mPopup.setEnabled(false);
-        }
-    }
-
-
-    @Override
-    public boolean onPreferenceTreeClick(
-            PreferenceScreen preferenceScreen, Preference preference) {
-        final String key = preference.getKey();
-        return super.onPreferenceTreeClick(preferenceScreen, preference);
-    }
-
-    @Override
-    public void onTimeZoneSet(TimeZoneInfo tzi) {
-        if (mTzPickerUtils == null) {
-            mTzPickerUtils = new TimeZonePickerUtils(getActivity());
-        }
-
-        final CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(
-                getActivity(), tzi.mTzId, System.currentTimeMillis(), false);
-        mHomeTZ.setSummary(timezoneName);
-        Utils.setTimeZone(getActivity(), tzi.mTzId);
-    }
-}
diff --git a/src/com/android/calendar/GeneralPreferences.kt b/src/com/android/calendar/GeneralPreferences.kt
new file mode 100644
index 0000000..dd4c955
--- /dev/null
+++ b/src/com/android/calendar/GeneralPreferences.kt
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.app.Activity
+import android.app.FragmentManager
+import android.app.backup.BackupManager
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.media.Ringtone
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.Vibrator
+import android.preference.CheckBoxPreference
+import android.preference.ListPreference
+import android.preference.Preference
+import android.preference.Preference.OnPreferenceChangeListener
+import android.preference.Preference.OnPreferenceClickListener
+import android.preference.PreferenceCategory
+import android.preference.PreferenceFragment
+import android.preference.PreferenceManager
+import android.preference.PreferenceScreen
+import android.provider.CalendarContract
+import android.provider.CalendarContract.CalendarCache
+import android.text.TextUtils
+import android.text.format.Time
+import com.android.calendar.alerts.AlertReceiver
+import com.android.timezonepicker.TimeZoneInfo
+import com.android.timezonepicker.TimeZonePickerDialog
+import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener
+import com.android.timezonepicker.TimeZonePickerUtils
+
+class GeneralPreferences : PreferenceFragment(), OnSharedPreferenceChangeListener,
+        OnPreferenceChangeListener, OnTimeZoneSetListener {
+    var mAlert: CheckBoxPreference? = null
+    var mVibrate: CheckBoxPreference? = null
+    var mPopup: CheckBoxPreference? = null
+    var mUseHomeTZ: CheckBoxPreference? = null
+    var mHideDeclined: CheckBoxPreference? = null
+    var mHomeTZ: Preference? = null
+    var mTzPickerUtils: TimeZonePickerUtils? = null
+    var mWeekStart: ListPreference? = null
+    var mDefaultReminder: ListPreference? = null
+    private var mTimeZoneId: String? = null
+
+    @Override
+    override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        val activity: Activity = getActivity()
+
+        // Make sure to always use the same preferences file regardless of the package name
+        // we're running under
+        val preferenceManager: PreferenceManager = getPreferenceManager()
+        val sharedPreferences: SharedPreferences? = getSharedPreferences(activity)
+        preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME)
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.general_preferences)
+        val preferenceScreen: PreferenceScreen = getPreferenceScreen()
+        mAlert = preferenceScreen.findPreference(KEY_ALERTS) as CheckBoxPreference
+        mVibrate = preferenceScreen.findPreference(KEY_ALERTS_VIBRATE) as CheckBoxPreference
+        val vibrator: Vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+        if (vibrator == null || !vibrator.hasVibrator()) {
+            val mAlertGroup: PreferenceCategory = preferenceScreen
+                    .findPreference(KEY_ALERTS_CATEGORY) as PreferenceCategory
+            mAlertGroup.removePreference(mVibrate)
+        }
+        mPopup = preferenceScreen.findPreference(KEY_ALERTS_POPUP) as CheckBoxPreference
+        mUseHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED) as CheckBoxPreference
+        mHideDeclined = preferenceScreen.findPreference(KEY_HIDE_DECLINED) as CheckBoxPreference
+        mWeekStart = preferenceScreen.findPreference(KEY_WEEK_START_DAY) as ListPreference
+        mDefaultReminder = preferenceScreen.findPreference(KEY_DEFAULT_REMINDER) as ListPreference
+        mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ)
+        mWeekStart?.setSummary(mWeekStart?.getEntry())
+        mDefaultReminder?.setSummary(mDefaultReminder?.getEntry())
+
+        // This triggers an asynchronous call to the provider to refresh the data in shared pref
+        mTimeZoneId = Utils.getTimeZone(activity, null)
+        val prefs: SharedPreferences = CalendarUtils.getSharedPreferences(activity,
+                Utils.SHARED_PREFS_NAME)
+
+        // Utils.getTimeZone will return the currentTimeZone instead of the one
+        // in the shared_pref if home time zone is disabled. So if home tz is
+        // off, we will explicitly read it.
+        if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) {
+            mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone())
+        }
+
+        mHomeTZ?.setOnPreferenceClickListener(object : Preference.OnPreferenceClickListener {
+            @Override
+            override fun onPreferenceClick(preference: Preference?): Boolean {
+                showTimezoneDialog()
+                return true
+            }
+        })
+
+        if (mTzPickerUtils == null) {
+            mTzPickerUtils = TimeZonePickerUtils(getActivity())
+        }
+        val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(getActivity(),
+                mTimeZoneId, System.currentTimeMillis(), false)
+        mHomeTZ?.setSummary(timezoneName ?: mTimeZoneId)
+        val tzpd: TimeZonePickerDialog = activity.getFragmentManager()
+                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog
+        if (tzpd != null) {
+            tzpd.setOnTimeZoneSetListener(this)
+        }
+        migrateOldPreferences(sharedPreferences)
+        updateChildPreferences()
+    }
+
+    private fun showTimezoneDialog() {
+        val activity: Activity = getActivity() ?: return
+        val b = Bundle()
+        b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis())
+        b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null))
+        val fm: FragmentManager = getActivity().getFragmentManager()
+        var tzpd: TimeZonePickerDialog? = fm
+                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog
+        if (tzpd != null) {
+            tzpd.dismiss()
+        }
+        tzpd = TimeZonePickerDialog()
+        tzpd.setArguments(b)
+        tzpd.setOnTimeZoneSetListener(this)
+        tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER)
+    }
+
+    @Override
+    override fun onStart() {
+        super.onStart()
+        getPreferenceScreen().getSharedPreferences()
+                .registerOnSharedPreferenceChangeListener(this)
+        setPreferenceListeners(this)
+    }
+
+    /**
+     * Sets up all the preference change listeners to use the specified
+     * listener.
+     */
+    private fun setPreferenceListeners(listener: OnPreferenceChangeListener?) {
+        mUseHomeTZ?.setOnPreferenceChangeListener(listener)
+        mHomeTZ?.setOnPreferenceChangeListener(listener)
+        mWeekStart?.setOnPreferenceChangeListener(listener)
+        mDefaultReminder?.setOnPreferenceChangeListener(listener)
+        mHideDeclined?.setOnPreferenceChangeListener(listener)
+        mVibrate?.setOnPreferenceChangeListener(listener)
+    }
+
+    @Override
+    override fun onStop() {
+        getPreferenceScreen().getSharedPreferences()
+                .unregisterOnSharedPreferenceChangeListener(this)
+        setPreferenceListeners(null)
+        super.onStop()
+    }
+
+    @Override
+    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String) {
+        val a: Activity = getActivity()
+        if (key.equals(KEY_ALERTS)) {
+            updateChildPreferences()
+            if (a != null) {
+                val intent = Intent()
+                intent.setClass(a, AlertReceiver::class.java)
+                if (mAlert?.isChecked() ?: false) {
+                    intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)
+                } else {
+                    intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION)
+                }
+                a.sendBroadcast(intent)
+            }
+        }
+        if (a != null) {
+            BackupManager.dataChanged(a.getPackageName())
+        }
+    }
+
+    /**
+     * Handles time zone preference changes
+     */
+    @Override
+    override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
+        val tz: String?
+        val activity: Activity = getActivity()
+        if (preference === mUseHomeTZ) {
+            tz = if (newValue != null) {
+                mTimeZoneId
+            } else {
+                CalendarCache.TIMEZONE_TYPE_AUTO
+            }
+            Utils.setTimeZone(activity, tz)
+            return true
+        } else if (preference === mHideDeclined) {
+            mHideDeclined?.setChecked(newValue as Boolean)
+            val intent = Intent(Utils.getWidgetScheduledUpdateAction(activity))
+            intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
+            activity.sendBroadcast(intent)
+            return true
+        } else if (preference === mWeekStart) {
+            mWeekStart?.setValue(newValue as String)
+            mWeekStart?.setSummary(mWeekStart?.getEntry())
+        } else if (preference === mDefaultReminder) {
+            mDefaultReminder?.setValue(newValue as String)
+            mDefaultReminder?.setSummary(mDefaultReminder?.getEntry())
+        } else if (preference === mVibrate) {
+            mVibrate?.setChecked(newValue as Boolean)
+            return true
+        } else {
+            return true
+        }
+        return false
+    }
+
+    fun getRingtoneTitleFromUri(context: Context?, uri: String?): String? {
+        if (TextUtils.isEmpty(uri)) {
+            return null
+        }
+        val ring: Ringtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri))
+        return if (ring != null) {
+            ring.getTitle(context)
+        } else null
+    }
+
+    /**
+     * If necessary, upgrades previous versions of preferences to the current
+     * set of keys and values.
+     * @param prefs the preferences to upgrade
+     */
+    private fun migrateOldPreferences(prefs: SharedPreferences?) {
+        // If needed, migrate vibration setting from a previous version
+        mVibrate?.setChecked(Utils.getDefaultVibrate(getActivity(), prefs))
+
+        // If needed, migrate the old alerts type settin
+        if (prefs?.contains(KEY_ALERTS) == false && prefs?.contains(KEY_ALERTS_TYPE) == true) {
+            val type: String? = prefs?.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR)
+            if (type.equals(ALERT_TYPE_OFF)) {
+                mAlert?.setChecked(false)
+                mPopup?.setChecked(false)
+                mPopup?.setEnabled(false)
+            } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
+                mAlert?.setChecked(true)
+                mPopup?.setChecked(false)
+                mPopup?.setEnabled(true)
+            } else if (type.equals(ALERT_TYPE_ALERTS)) {
+                mAlert?.setChecked(true)
+                mPopup?.setChecked(true)
+                mPopup?.setEnabled(true)
+            }
+            // clear out the old setting
+            prefs?.edit().remove(KEY_ALERTS_TYPE).commit()
+        }
+    }
+
+    /**
+     * Keeps the dependent settings in sync with the parent preference, so for
+     * example, when notifications are turned off, we disable the preferences
+     * for configuring the exact notification behavior.
+     */
+    private fun updateChildPreferences() {
+        if (mAlert?.isChecked() ?: false) {
+            mVibrate?.setEnabled(true)
+            mPopup?.setEnabled(true)
+        } else {
+            mVibrate?.setEnabled(false)
+            mPopup?.setEnabled(false)
+        }
+    }
+
+    @Override
+    override fun onPreferenceTreeClick(
+        preferenceScreen: PreferenceScreen?,
+        preference: Preference
+    ): Boolean {
+        val key: String = preference.getKey()
+        return super.onPreferenceTreeClick(preferenceScreen, preference)
+    }
+
+    @Override
+    override fun onTimeZoneSet(tzi: TimeZoneInfo) {
+        if (mTzPickerUtils == null) {
+            mTzPickerUtils = TimeZonePickerUtils(getActivity())
+        }
+        val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(
+                getActivity(), tzi.mTzId, System.currentTimeMillis(), false)
+        mHomeTZ?.setSummary(timezoneName)
+        Utils.setTimeZone(getActivity(), tzi.mTzId)
+    }
+
+    companion object {
+        // The name of the shared preferences file. This name must be maintained for historical
+        // reasons, as it's what PreferenceManager assigned the first time the file was created.
+        const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+        const val SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup"
+        private const val FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker"
+
+        // Preference keys
+        const val KEY_HIDE_DECLINED = "preferences_hide_declined"
+        const val KEY_WEEK_START_DAY = "preferences_week_start_day"
+        const val KEY_SHOW_WEEK_NUM = "preferences_show_week_num"
+        const val KEY_DAYS_PER_WEEK = "preferences_days_per_week"
+        const val KEY_SKIP_SETUP = "preferences_skip_setup"
+        const val KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history"
+        const val KEY_ALERTS_CATEGORY = "preferences_alerts_category"
+        const val KEY_ALERTS = "preferences_alerts"
+        const val KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate"
+        const val KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone"
+        const val KEY_ALERTS_POPUP = "preferences_alerts_popup"
+        const val KEY_SHOW_CONTROLS = "preferences_show_controls"
+        const val KEY_DEFAULT_REMINDER = "preferences_default_reminder"
+        const val NO_REMINDER = -1
+        const val NO_REMINDER_STRING = "-1"
+        const val REMINDER_DEFAULT_TIME = 10 // in minutes
+        const val KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height"
+        const val KEY_VERSION = "preferences_version"
+
+        /** Key to SharePreference for default view (CalendarController.ViewType)  */
+        const val KEY_START_VIEW = "preferred_startView"
+
+        /**
+         * Key to SharePreference for default detail view (CalendarController.ViewType)
+         * Typically used by widget
+         */
+        const val KEY_DETAILED_VIEW = "preferred_detailedView"
+        const val KEY_DEFAULT_CALENDAR = "preference_defaultCalendar"
+
+        // These must be in sync with the array preferences_week_start_day_values
+        const val WEEK_START_DEFAULT = "-1"
+        const val WEEK_START_SATURDAY = "7"
+        const val WEEK_START_SUNDAY = "1"
+        const val WEEK_START_MONDAY = "2"
+
+        // These keys are kept to enable migrating users from previous versions
+        private const val KEY_ALERTS_TYPE = "preferences_alerts_type"
+        private const val ALERT_TYPE_ALERTS = "0"
+        private const val ALERT_TYPE_STATUS_BAR = "1"
+        private const val ALERT_TYPE_OFF = "2"
+        const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled"
+        const val KEY_HOME_TZ = "preferences_home_tz"
+
+        // Default preference values
+        const val DEFAULT_START_VIEW: Int = CalendarController.ViewType.WEEK
+        const val DEFAULT_DETAILED_VIEW: Int = CalendarController.ViewType.DAY
+        const val DEFAULT_SHOW_WEEK_NUM = false
+
+        // This should match the XML file.
+        const val DEFAULT_RINGTONE = "content://settings/system/notification_sound"
+
+        /** Return a properly configured SharedPreferences instance  */
+        @JvmStatic
+        fun getSharedPreferences(context: Context?): SharedPreferences? {
+            return context?.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
+        }
+
+        /** Set the default shared preferences in the proper context  */
+        @JvmStatic
+        fun setDefaultValues(context: Context?) {
+            PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
+                    R.xml.general_preferences, false)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
deleted file mode 100644
index 3970115..0000000
--- a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
-**
-** 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
new file mode 100644
index 0000000..d2fe77f
--- /dev/null
+++ b/src/com/android/calendar/GoogleCalendarUriIntentFilter.kt
@@ -0,0 +1,37 @@
+/*
+** 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
deleted file mode 100644
index 8034b28..0000000
--- a/src/com/android/calendar/MultiStateButton.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.calendar;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.widget.Button;
-
-/**
- * <p>
- * A button with more than two states. When the button is pressed
- * or clicked, the state transitions automatically.
- * </p>
- *
- * <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link R.styleable#MultiStateButton
- * MultiStateButton Attributes}, {@link android.R.styleable#Button Button
- * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
- * android.R.styleable#View View Attributes}
- * </p>
- */
-
-public class MultiStateButton extends Button {
-    //The current state for this button, ranging from 0 to maxState-1
-    private int mState;
-    //The maximum number of states allowed for this button.
-    private int mMaxStates;
-    //The currently displaying resource ID. This gets set to a default on creation and remains
-    //on the last set if the resources get set to null.
-    private int mButtonResource;
-    //A list of all drawable resources used by this button in the order it uses them.
-    private int[] mButtonResources;
-    private Drawable mButtonDrawable;
-
-    public MultiStateButton(Context context) {
-        this(context, null);
-    }
-
-    public MultiStateButton(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public MultiStateButton(Context context, AttributeSet attrs, int defStyle) {
-        //Currently using the standard buttonStyle, will update when new resources are added.
-        super(context, attrs, defStyle);
-        mMaxStates = 1;
-        mState = 0;
-        //TODO add a more generic default button
-        mButtonResources = new int[] { R.drawable.widget_show };
-        setButtonDrawable(mButtonResources[mState]);
-    }
-
-    @Override
-    public boolean performClick() {
-        /* When clicked, toggle the state */
-        transitionState();
-        return super.performClick();
-    }
-
-    public void transitionState() {
-        mState = (mState + 1) % mMaxStates;
-        setButtonDrawable(mButtonResources[mState]);
-    }
-
-    /**
-     * Allows for a new set of drawable resource ids to be set.
-     *
-     * This sets the maximum states allowed to the length of the resources array. It will also
-     * set the current state to the maximum allowed if it's greater than the new max.
-     */
-    public void setButtonResources(int[] resources) throws IllegalArgumentException {
-        if(resources == null) {
-            throw new IllegalArgumentException("Button resources cannot be null");
-        }
-        mMaxStates = resources.length;
-        if(mState >= mMaxStates) {
-            mState = mMaxStates - 1;
-        }
-        mButtonResources = resources;
-    }
-
-    /**
-     * Attempts to set the state. Returns true if successful, false otherwise.
-     */
-    public boolean setState(int state){
-        if(state >= mMaxStates || state < 0) {
-            //When moved out of Calendar the tag should be changed.
-            Log.w("Cal", "MultiStateButton state set to value greater than maxState or < 0");
-            return false;
-        }
-        mState = state;
-        setButtonDrawable(mButtonResources[mState]);
-        return true;
-    }
-
-    public int getState() {
-        return mState;
-    }
-
-    /**
-     * Set the background to a given Drawable, identified by its resource id.
-     *
-     * @param resid the resource id of the drawable to use as the background
-     */
-    public void setButtonDrawable(int resid) {
-        if (resid != 0 && resid == mButtonResource) {
-            return;
-        }
-
-        mButtonResource = resid;
-
-        Drawable d = null;
-        if (mButtonResource != 0) {
-            d = getResources().getDrawable(mButtonResource);
-        }
-        setButtonDrawable(d);
-    }
-
-    /**
-     * Set the background to a given Drawable
-     *
-     * @param d The Drawable to use as the background
-     */
-    public void setButtonDrawable(Drawable d) {
-        if (d != null) {
-            if (mButtonDrawable != null) {
-                mButtonDrawable.setCallback(null);
-                unscheduleDrawable(mButtonDrawable);
-            }
-            d.setCallback(this);
-            d.setState(getDrawableState());
-            d.setVisible(getVisibility() == VISIBLE, false);
-            mButtonDrawable = d;
-            mButtonDrawable.setState(null);
-            setMinHeight(mButtonDrawable.getIntrinsicHeight());
-            setWidth(mButtonDrawable.getIntrinsicWidth());
-        }
-        refreshDrawableState();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (mButtonDrawable != null) {
-            final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
-            final int horizontalGravity = getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
-            final int height = mButtonDrawable.getIntrinsicHeight();
-            final int width = mButtonDrawable.getIntrinsicWidth();
-
-            int y = 0;
-            int x = 0;
-
-            switch (verticalGravity) {
-                case Gravity.BOTTOM:
-                    y = getHeight() - height;
-                    break;
-                case Gravity.CENTER_VERTICAL:
-                    y = (getHeight() - height) / 2;
-                    break;
-            }
-            switch (horizontalGravity) {
-                case Gravity.RIGHT:
-                    x = getWidth() - width;
-                    break;
-                case Gravity.CENTER_HORIZONTAL:
-                    x = (getWidth() - width) / 2;
-                    break;
-            }
-
-            mButtonDrawable.setBounds(x, y, x + width, y + height);
-            mButtonDrawable.draw(canvas);
-        }
-    }
-}
diff --git a/src/com/android/calendar/MultiStateButton.kt b/src/com/android/calendar/MultiStateButton.kt
new file mode 100644
index 0000000..f86ee6b
--- /dev/null
+++ b/src/com/android/calendar/MultiStateButton.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.widget.Button
+
+/**
+ * A button with more than two states. When the button is pressed
+ * or clicked, the state transitions automatically.
+ *
+ * **XML attributes**
+ * See [ MultiStateButton Attributes][R.styleable.MultiStateButton],
+ * [Button][android.R.styleable.Button], [TextView Attributes][android.R.styleable.TextView],
+ * [ ][android.R.styleable.View]
+ *
+ */
+class MultiStateButton(context: Context?, attrs: AttributeSet?, defStyle: Int) :
+                       Button(context, attrs, defStyle) {
+    //The current state for this button, ranging from 0 to maxState-1
+    var mState = 0
+        private set
+
+    //The maximum number of states allowed for this button.
+    private var mMaxStates = 1
+
+    //The currently displaying resource ID. This gets set to a default on creation and remains
+    //on the last set if the resources get set to null.
+    private var mButtonResource = 0
+
+    //A list of all drawable resources used by this button in the order it uses them.
+    private var mButtonResources: IntArray
+    private var mButtonDrawable: Drawable? = null
+
+    constructor(context: Context?) : this(context, null) {}
+    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) {}
+
+    override fun performClick(): Boolean {
+        /* When clicked, toggle the state */
+        transitionState()
+        return super.performClick()
+    }
+
+    fun transitionState() {
+        mState = (mState + 1) % mMaxStates
+        setButtonDrawable(mButtonResources[mState])
+    }
+
+    /**
+     * Allows for a new set of drawable resource ids to be set.
+     *
+     * This sets the maximum states allowed to the length of the resources array. It will also
+     * set the current state to the maximum allowed if it's greater than the new max.
+     */
+    //@Throws(IllegalArgumentException::class)
+    fun setButtonResources(resources: IntArray?) {
+        if (resources == null) {
+            throw IllegalArgumentException("Button resources cannot be null")
+        }
+        mMaxStates = resources.size
+        if (mState >= mMaxStates) {
+            mState = mMaxStates - 1
+        }
+        mButtonResources = resources
+    }
+
+    /**
+     * Attempts to set the state. Returns true if successful, false otherwise.
+     */
+    fun setState(state: Int): Boolean {
+        if (state >= mMaxStates || state < 0) {
+            //When moved out of Calendar the tag should be changed.
+            Log.w("Cal", "MultiStateButton state set to value greater than maxState or < 0")
+            return false
+        }
+        mState = state
+        setButtonDrawable(mButtonResources[mState])
+        return true
+    }
+
+    /**
+     * Set the background to a given Drawable, identified by its resource id.
+     *
+     * @param resid the resource id of the drawable to use as the background
+     */
+    fun setButtonDrawable(resid: Int) {
+        if (resid != 0 && resid == mButtonResource) {
+            return
+        }
+        mButtonResource = resid
+        var d: Drawable? = null
+        if (mButtonResource != 0) {
+            d = getResources().getDrawable(mButtonResource)
+        }
+        setButtonDrawable(d)
+    }
+
+    /**
+     * Set the background to a given Drawable
+     *
+     * @param d The Drawable to use as the background
+     */
+    fun setButtonDrawable(d: Drawable?) {
+        if (d != null) {
+            if (mButtonDrawable != null) {
+                mButtonDrawable?.setCallback(null)
+                unscheduleDrawable(mButtonDrawable)
+            }
+            d.setCallback(this)
+            d.setState(getDrawableState())
+            d.setVisible(getVisibility() === VISIBLE, false)
+            mButtonDrawable = d
+            mButtonDrawable?.setState(getDrawableState())
+            setMinHeight(mButtonDrawable?.getIntrinsicHeight() ?: 0)
+            setWidth(mButtonDrawable?.getIntrinsicWidth() ?: 0)
+        }
+        refreshDrawableState()
+    }
+
+    protected override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        if (mButtonDrawable != null) {
+            val verticalGravity: Int = getGravity() and Gravity.VERTICAL_GRAVITY_MASK
+            val horizontalGravity: Int = getGravity() and Gravity.HORIZONTAL_GRAVITY_MASK
+            val height: Int = mButtonDrawable?.getIntrinsicHeight() ?: 0
+            val width: Int = mButtonDrawable?.getIntrinsicWidth() ?: 0
+            var y = 0
+            var x = 0
+            when (verticalGravity) {
+                Gravity.BOTTOM -> y = getHeight() - height
+                Gravity.CENTER_VERTICAL -> y = (getHeight() - height) / 2
+            }
+            when (horizontalGravity) {
+                Gravity.RIGHT -> x = getWidth() - width
+                Gravity.CENTER_HORIZONTAL -> x = (getWidth() - width) / 2
+            }
+            mButtonDrawable?.setBounds(x, y, x + width, y + height)
+            mButtonDrawable?.draw(canvas)
+        }
+    }
+
+    init {
+        //Currently using the standard buttonStyle, will update when new resources are added.
+        //TODO add a more generic default button
+        mButtonResources = intArrayOf(R.drawable.widget_show)
+        setButtonDrawable(mButtonResources[mState])
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/OtherPreferences.java b/src/com/android/calendar/OtherPreferences.java
deleted file mode 100644
index a59d3f4..0000000
--- a/src/com/android/calendar/OtherPreferences.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.TimePickerDialog;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.text.format.DateFormat;
-import android.text.format.Time;
-import android.util.Log;
-import android.widget.TimePicker;
-
-public class OtherPreferences extends PreferenceFragment  implements OnPreferenceChangeListener{
-    private static final String TAG = "CalendarOtherPreferences";
-
-    // The name of the shared preferences file. This name must be maintained for
-    // historical reasons, as it's what PreferenceManager assigned the first
-    // time the file was created.
-    static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-
-    // Must be the same keys that are used in the other_preferences.xml file.
-    public static final String KEY_OTHER_COPY_DB = "preferences_copy_db";
-    public static final String KEY_OTHER_QUIET_HOURS = "preferences_reminders_quiet_hours";
-    public static final String KEY_OTHER_REMINDERS_RESPONDED = "preferences_reminders_responded";
-    public static final String KEY_OTHER_QUIET_HOURS_START =
-            "preferences_reminders_quiet_hours_start";
-    public static final String KEY_OTHER_QUIET_HOURS_START_HOUR =
-            "preferences_reminders_quiet_hours_start_hour";
-    public static final String KEY_OTHER_QUIET_HOURS_START_MINUTE =
-            "preferences_reminders_quiet_hours_start_minute";
-    public static final String KEY_OTHER_QUIET_HOURS_END =
-            "preferences_reminders_quiet_hours_end";
-    public static final String KEY_OTHER_QUIET_HOURS_END_HOUR =
-            "preferences_reminders_quiet_hours_end_hour";
-    public static final String KEY_OTHER_QUIET_HOURS_END_MINUTE =
-            "preferences_reminders_quiet_hours_end_minute";
-    public static final String KEY_OTHER_1 = "preferences_tardis_1";
-
-    public static final int QUIET_HOURS_DEFAULT_START_HOUR = 22;
-    public static final int QUIET_HOURS_DEFAULT_START_MINUTE = 0;
-    public static final int QUIET_HOURS_DEFAULT_END_HOUR = 8;
-    public static final int QUIET_HOURS_DEFAULT_END_MINUTE = 0;
-
-    private static final int START_LISTENER = 1;
-    private static final int END_LISTENER = 2;
-    private static final String format24Hour = "%H:%M";
-    private static final String format12Hour = "%I:%M%P";
-
-    private Preference mCopyDb;
-    private CheckBoxPreference mQuietHours;
-    private Preference mQuietHoursStart;
-    private Preference mQuietHoursEnd;
-
-    private TimePickerDialog mTimePickerDialog;
-    private TimeSetListener mQuietHoursStartListener;
-    private TimePickerDialog mQuietHoursStartDialog;
-    private TimeSetListener mQuietHoursEndListener;
-    private TimePickerDialog mQuietHoursEndDialog;
-    private boolean mIs24HourMode;
-
-    public OtherPreferences() {
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        PreferenceManager manager = getPreferenceManager();
-        manager.setSharedPreferencesName(SHARED_PREFS_NAME);
-        SharedPreferences prefs = manager.getSharedPreferences();
-
-        addPreferencesFromResource(R.xml.other_preferences);
-        mCopyDb = findPreference(KEY_OTHER_COPY_DB);
-
-        Activity activity = getActivity();
-        if (activity == null) {
-            Log.d(TAG, "Activity was null");
-        }
-        mIs24HourMode = DateFormat.is24HourFormat(activity);
-
-        mQuietHours =
-                (CheckBoxPreference) findPreference(KEY_OTHER_QUIET_HOURS);
-
-        int startHour = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_HOUR,
-                QUIET_HOURS_DEFAULT_START_HOUR);
-        int startMinute = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_MINUTE,
-                QUIET_HOURS_DEFAULT_START_MINUTE);
-        mQuietHoursStart = findPreference(KEY_OTHER_QUIET_HOURS_START);
-        mQuietHoursStartListener = new TimeSetListener(START_LISTENER);
-        mQuietHoursStartDialog = new TimePickerDialog(
-                activity, mQuietHoursStartListener,
-                startHour, startMinute, mIs24HourMode);
-        mQuietHoursStart.setSummary(formatTime(startHour, startMinute));
-
-        int endHour = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_HOUR,
-                QUIET_HOURS_DEFAULT_END_HOUR);
-        int endMinute = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_MINUTE,
-                QUIET_HOURS_DEFAULT_END_MINUTE);
-        mQuietHoursEnd = findPreference(KEY_OTHER_QUIET_HOURS_END);
-        mQuietHoursEndListener = new TimeSetListener(END_LISTENER);
-        mQuietHoursEndDialog = new TimePickerDialog(
-                activity, mQuietHoursEndListener,
-                endHour, endMinute, mIs24HourMode);
-        mQuietHoursEnd.setSummary(formatTime(endHour, endMinute));
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object objValue) {
-        return true;
-    }
-
-    @Override
-    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
-        if (preference == mCopyDb) {
-            Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.setComponent(new ComponentName("com.android.providers.calendar",
-                    "com.android.providers.calendar.CalendarDebugActivity"));
-            startActivity(intent);
-        } else if (preference == mQuietHoursStart) {
-            if (mTimePickerDialog == null) {
-                mTimePickerDialog = mQuietHoursStartDialog;
-                mTimePickerDialog.show();
-            } else {
-                Log.v(TAG, "not null");
-            }
-        } else if (preference == mQuietHoursEnd) {
-            if (mTimePickerDialog == null) {
-                mTimePickerDialog = mQuietHoursEndDialog;
-                mTimePickerDialog.show();
-            } else {
-                Log.v(TAG, "not null");
-            }
-        } else {
-            return super.onPreferenceTreeClick(screen, preference);
-        }
-        return true;
-    }
-
-    private class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
-        private int mListenerId;
-
-        public TimeSetListener(int listenerId) {
-            mListenerId = listenerId;
-        }
-
-        @Override
-        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
-            mTimePickerDialog = null;
-
-            SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
-            SharedPreferences.Editor editor = prefs.edit();
-
-            String summary = formatTime(hourOfDay, minute);
-            switch (mListenerId) {
-                case (START_LISTENER):
-                    mQuietHoursStart.setSummary(summary);
-                    editor.putInt(KEY_OTHER_QUIET_HOURS_START_HOUR, hourOfDay);
-                    editor.putInt(KEY_OTHER_QUIET_HOURS_START_MINUTE, minute);
-                    break;
-                case (END_LISTENER):
-                    mQuietHoursEnd.setSummary(summary);
-                    editor.putInt(KEY_OTHER_QUIET_HOURS_END_HOUR, hourOfDay);
-                    editor.putInt(KEY_OTHER_QUIET_HOURS_END_MINUTE, minute);
-                    break;
-                default:
-                    Log.d(TAG, "Set time for unknown listener: "+mListenerId);
-            }
-
-            editor.commit();
-        }
-    }
-
-    /**
-     * @param hourOfDay the hour of the day (0-24)
-     * @param minute
-     * @return human-readable string formatted based on 24-hour mode.
-     */
-    private String formatTime(int hourOfDay, int minute) {
-        Time time = new Time();
-        time.hour = hourOfDay;
-        time.minute = minute;
-
-        String format = mIs24HourMode? format24Hour : format12Hour;
-        return time.format(format);
-    }
-}
diff --git a/src/com/android/calendar/OtherPreferences.kt b/src/com/android/calendar/OtherPreferences.kt
new file mode 100644
index 0000000..f1507cc
--- /dev/null
+++ b/src/com/android/calendar/OtherPreferences.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.app.Activity
+import android.app.Dialog
+import android.app.TimePickerDialog
+import android.content.ComponentName
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.preference.CheckBoxPreference
+import android.preference.ListPreference
+import android.preference.Preference
+import android.preference.Preference.OnPreferenceChangeListener
+import android.preference.PreferenceFragment
+import android.preference.PreferenceManager
+import android.preference.PreferenceScreen
+import android.text.format.DateFormat
+import android.text.format.Time
+import android.util.Log
+import android.widget.TimePicker
+
+class OtherPreferences : PreferenceFragment(), OnPreferenceChangeListener {
+    private var mCopyDb: Preference? = null
+    private var mQuietHours: CheckBoxPreference? = null
+    private var mQuietHoursStart: Preference? = null
+    private var mQuietHoursEnd: Preference? = null
+    private var mTimePickerDialog: TimePickerDialog? = null
+    private var mQuietHoursStartListener: TimeSetListener? = null
+    private var mQuietHoursStartDialog: TimePickerDialog? = null
+    private var mQuietHoursEndListener: TimeSetListener? = null
+    private var mQuietHoursEndDialog: TimePickerDialog? = null
+    private var mIs24HourMode = false
+
+    @Override
+    override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        val manager: PreferenceManager = getPreferenceManager()
+        manager.setSharedPreferencesName(SHARED_PREFS_NAME)
+        val prefs: SharedPreferences = manager.getSharedPreferences()
+        addPreferencesFromResource(R.xml.other_preferences)
+        mCopyDb = findPreference(KEY_OTHER_COPY_DB)
+        val activity: Activity = getActivity()
+        if (activity == null) {
+            Log.d(TAG, "Activity was null")
+        }
+        mIs24HourMode = DateFormat.is24HourFormat(activity)
+        mQuietHours = findPreference(KEY_OTHER_QUIET_HOURS) as CheckBoxPreference?
+        val startHour: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_HOUR,
+                QUIET_HOURS_DEFAULT_START_HOUR)
+        val startMinute: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_START_MINUTE,
+                QUIET_HOURS_DEFAULT_START_MINUTE)
+        mQuietHoursStart = findPreference(KEY_OTHER_QUIET_HOURS_START)
+        mQuietHoursStartListener = TimeSetListener(START_LISTENER)
+        mQuietHoursStartDialog = TimePickerDialog(
+                activity, mQuietHoursStartListener,
+                startHour, startMinute, mIs24HourMode)
+        mQuietHoursStart?.setSummary(formatTime(startHour, startMinute))
+        val endHour: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_HOUR,
+                QUIET_HOURS_DEFAULT_END_HOUR)
+        val endMinute: Int = prefs.getInt(KEY_OTHER_QUIET_HOURS_END_MINUTE,
+                QUIET_HOURS_DEFAULT_END_MINUTE)
+        mQuietHoursEnd = findPreference(KEY_OTHER_QUIET_HOURS_END)
+        mQuietHoursEndListener = TimeSetListener(END_LISTENER)
+        mQuietHoursEndDialog = TimePickerDialog(
+                activity, mQuietHoursEndListener,
+                endHour, endMinute, mIs24HourMode)
+        mQuietHoursEnd?.setSummary(formatTime(endHour, endMinute))
+    }
+
+    @Override
+    override fun onPreferenceChange(preference: Preference?, objValue: Any?): Boolean {
+        return true
+    }
+
+    @Override
+    override fun onPreferenceTreeClick(screen: PreferenceScreen?, preference: Preference): Boolean {
+        if (preference === mCopyDb) {
+            val intent = Intent(Intent.ACTION_MAIN)
+            intent.setComponent(ComponentName("com.android.providers.calendar",
+                    "com.android.providers.calendar.CalendarDebugActivity"))
+            startActivity(intent)
+        } else if (preference === mQuietHoursStart) {
+            if (mTimePickerDialog == null) {
+                mTimePickerDialog = mQuietHoursStartDialog
+                mTimePickerDialog?.show()
+            } else {
+                Log.v(TAG, "not null")
+            }
+        } else if (preference === mQuietHoursEnd) {
+            if (mTimePickerDialog == null) {
+                mTimePickerDialog = mQuietHoursEndDialog
+                mTimePickerDialog?.show()
+            } else {
+                Log.v(TAG, "not null")
+            }
+        } else {
+            return super.onPreferenceTreeClick(screen, preference)
+        }
+        return true
+    }
+
+    private inner class TimeSetListener(private val mListenerId: Int) :
+            TimePickerDialog.OnTimeSetListener {
+        @Override
+        override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
+            mTimePickerDialog = null
+            val prefs: SharedPreferences = getPreferenceManager().getSharedPreferences()
+            val editor: SharedPreferences.Editor = prefs.edit()
+            val summary = formatTime(hourOfDay, minute)
+            when (mListenerId) {
+                START_LISTENER -> {
+                    mQuietHoursStart?.setSummary(summary)
+                    editor.putInt(KEY_OTHER_QUIET_HOURS_START_HOUR, hourOfDay)
+                    editor.putInt(KEY_OTHER_QUIET_HOURS_START_MINUTE, minute)
+                }
+                END_LISTENER -> {
+                    mQuietHoursEnd?.setSummary(summary)
+                    editor.putInt(KEY_OTHER_QUIET_HOURS_END_HOUR, hourOfDay)
+                    editor.putInt(KEY_OTHER_QUIET_HOURS_END_MINUTE, minute)
+                }
+                else -> Log.d(TAG, "Set time for unknown listener: $mListenerId")
+            }
+            editor.commit()
+        }
+    }
+
+    /**
+     * @param hourOfDay the hour of the day (0-24)
+     * @param minute
+     * @return human-readable string formatted based on 24-hour mode.
+     */
+    private fun formatTime(hourOfDay: Int, minute: Int): String {
+        val time = Time()
+        time.hour = hourOfDay
+        time.minute = minute
+        val format = if (mIs24HourMode) format24Hour else format12Hour
+        return time.format(format)
+    }
+
+    companion object {
+        private const val TAG = "CalendarOtherPreferences"
+
+        // The name of the shared preferences file. This name must be maintained for
+        // historical reasons, as it's what PreferenceManager assigned the first
+        // time the file was created.
+        const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+
+        // Must be the same keys that are used in the other_preferences.xml file.
+        const val KEY_OTHER_COPY_DB = "preferences_copy_db"
+        const val KEY_OTHER_QUIET_HOURS = "preferences_reminders_quiet_hours"
+        const val KEY_OTHER_REMINDERS_RESPONDED = "preferences_reminders_responded"
+        const val KEY_OTHER_QUIET_HOURS_START = "preferences_reminders_quiet_hours_start"
+        const val KEY_OTHER_QUIET_HOURS_START_HOUR = "preferences_reminders_quiet_hours_start_hour"
+        const val KEY_OTHER_QUIET_HOURS_START_MINUTE =
+                "preferences_reminders_quiet_hours_start_minute"
+        const val KEY_OTHER_QUIET_HOURS_END = "preferences_reminders_quiet_hours_end"
+        const val KEY_OTHER_QUIET_HOURS_END_HOUR = "preferences_reminders_quiet_hours_end_hour"
+        const val KEY_OTHER_QUIET_HOURS_END_MINUTE = "preferences_reminders_quiet_hours_end_minute"
+        const val KEY_OTHER_1 = "preferences_tardis_1"
+        const val QUIET_HOURS_DEFAULT_START_HOUR = 22
+        const val QUIET_HOURS_DEFAULT_START_MINUTE = 0
+        const val QUIET_HOURS_DEFAULT_END_HOUR = 8
+        const val QUIET_HOURS_DEFAULT_END_MINUTE = 0
+        private const val START_LISTENER = 1
+        private const val END_LISTENER = 2
+        private const val format24Hour = "%H:%M"
+        private const val format12Hour = "%I:%M%P"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/StickyHeaderListView.java b/src/com/android/calendar/StickyHeaderListView.java
deleted file mode 100644
index 981e7af..0000000
--- a/src/com/android/calendar/StickyHeaderListView.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.Adapter;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-
-/**
- * Implements a ListView class with a sticky header at the top. The header is
- * per section and it is pinned to the top as long as its section is at the top
- * of the view. If it is not, the header slides up or down (depending on the
- * scroll movement) and the header of the current section slides to the top.
- * Notes:
- * 1. The class uses the first available child ListView as the working
- *    ListView. If no ListView child exists, the class will create a default one.
- * 2. The ListView's adapter must be passed to this class using the 'setAdapter'
- *    method. The adapter must implement the HeaderIndexer interface. If no adapter
- *    is specified, the class will try to extract it from the ListView
- * 3. The class registers itself as a listener to scroll events (OnScrollListener), if the
- *    ListView needs to receive scroll events, it must register its listener using
- *    this class' setOnScrollListener method.
- * 4. Headers for the list view must be added before using the StickyHeaderListView
- * 5. The implementation should register to listen to dataset changes. Right now this is not done
- *    since a change the dataset in a listview forces a call to OnScroll. The needed code is
- *    commented out.
- */
-public class StickyHeaderListView extends FrameLayout implements OnScrollListener {
-
-    private static final String TAG = "StickyHeaderListView";
-    protected boolean mChildViewsCreated = false;
-    protected boolean mDoHeaderReset = false;
-
-    protected Context mContext = null;
-    protected Adapter mAdapter = null;
-    protected HeaderIndexer mIndexer = null;
-    protected HeaderHeightListener mHeaderHeightListener = null;
-    protected View mStickyHeader = null;
-    protected View mNonessentialHeader = null; // A invisible header used when a section has no header
-    protected ListView mListView = null;
-    protected ListView.OnScrollListener mListener = null;
-
-    private int mSeparatorWidth;
-    private View mSeparatorView;
-    private int mLastStickyHeaderHeight = 0;
-
-    // This code is needed only if dataset changes do not force a call to OnScroll
-    // protected DataSetObserver mListDataObserver = null;
-
-
-    protected int mCurrentSectionPos = -1; // Position of section that has its header on the
-                                           // top of the view
-    protected int mNextSectionPosition = -1; // Position of next section's header
-    protected int mListViewHeadersCount = 0;
-
-    /**
-     * Interface that must be implemented by the ListView adapter to provide headers locations
-     * and number of items under each header.
-     *
-     */
-    public interface HeaderIndexer {
-        /**
-         * Calculates the position of the header of a specific item in the adapter's data set.
-         * For example: Assuming you have a list with albums and songs names:
-         * Album A, song 1, song 2, ...., song 10, Album B, song 1, ..., song 7. A call to
-         * this method with the position of song 5 in Album B, should return  the position
-         * of Album B.
-         * @param position - Position of the item in the ListView dataset
-         * @return Position of header. -1 if the is no header
-         */
-
-        int getHeaderPositionFromItemPosition(int position);
-
-        /**
-         * Calculates the number of items in the section defined by the header (not including
-         * the header).
-         * For example: A list with albums and songs, the method should return
-         * the number of songs names (without the album name).
-         *
-         * @param headerPosition - the value returned by 'getHeaderPositionFromItemPosition'
-         * @return Number of items. -1 on error.
-         */
-        int getHeaderItemsNumber(int headerPosition);
-    }
-
-    /***
-    *
-    * Interface that is used to update the sticky header's height
-    *
-    */
-   public interface HeaderHeightListener {
-
-       /***
-        * Updated a change in the sticky header's size
-        *
-        * @param height - new height of sticky header
-        */
-       void OnHeaderHeightChanged(int height);
-   }
-
-    /**
-     * Sets the adapter to be used by the class to get views of headers
-     *
-     * @param adapter - The adapter.
-     */
-
-    public void setAdapter(Adapter adapter) {
-
-        // This code is needed only if dataset changes do not force a call to
-        // OnScroll
-        // if (mAdapter != null && mListDataObserver != null) {
-        // mAdapter.unregisterDataSetObserver(mListDataObserver);
-        // }
-
-        if (adapter != null) {
-            mAdapter = adapter;
-            // This code is needed only if dataset changes do not force a call
-            // to OnScroll
-            // mAdapter.registerDataSetObserver(mListDataObserver);
-        }
-    }
-
-    /**
-     * Sets the indexer object (that implements the HeaderIndexer interface).
-     *
-     * @param indexer - The indexer.
-     */
-
-    public void setIndexer(HeaderIndexer indexer) {
-        mIndexer = indexer;
-    }
-
-    /**
-     * Sets the list view that is displayed
-     * @param lv - The list view.
-     */
-
-    public void setListView(ListView lv) {
-        mListView = lv;
-        mListView.setOnScrollListener(this);
-        mListViewHeadersCount = mListView.getHeaderViewsCount();
-    }
-
-    /**
-     * Sets an external OnScroll listener. Since the StickyHeaderListView sets
-     * itself as the scroll events listener of the listview, this method allows
-     * the user to register another listener that will be called after this
-     * class listener is called.
-     *
-     * @param listener - The external listener.
-     */
-    public void setOnScrollListener(ListView.OnScrollListener listener) {
-        mListener = listener;
-    }
-
-    public void setHeaderHeightListener(HeaderHeightListener listener) {
-        mHeaderHeightListener = listener;
-    }
-
-    // This code is needed only if dataset changes do not force a call to OnScroll
-    // protected void createDataListener() {
-    //    mListDataObserver = new DataSetObserver() {
-    //        @Override
-    //        public void onChanged() {
-    //            onDataChanged();
-    //        }
-    //    };
-    // }
-
-    /**
-     * Constructor
-     *
-     * @param context - application context.
-     * @param attrs - layout attributes.
-     */
-    public StickyHeaderListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        // This code is needed only if dataset changes do not force a call to OnScroll
-        // createDataListener();
-     }
-
-    /**
-     * Scroll status changes listener
-     *
-     * @param view - the scrolled view
-     * @param scrollState - new scroll state.
-     */
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-        if (mListener != null) {
-            mListener.onScrollStateChanged(view, scrollState);
-        }
-    }
-
-    /**
-     * Scroll events listener
-     *
-     * @param view - the scrolled view
-     * @param firstVisibleItem - the index (in the list's adapter) of the top
-     *            visible item.
-     * @param visibleItemCount - the number of visible items in the list
-     * @param totalItemCount - the total number items in the list
-     */
-    @Override
-    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-            int totalItemCount) {
-
-        updateStickyHeader(firstVisibleItem);
-
-        if (mListener != null) {
-            mListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
-        }
-    }
-
-    /**
-     * Sets a separator below the sticky header, which will be visible while the sticky header
-     * is not scrolling up.
-     * @param color - color of separator
-     * @param width - width in pixels of separator
-     */
-    public void setHeaderSeparator(int color, int width) {
-        mSeparatorView = new View(mContext);
-        ViewGroup.LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
-                width, Gravity.TOP);
-        mSeparatorView.setLayoutParams(params);
-        mSeparatorView.setBackgroundColor(color);
-        mSeparatorWidth = width;
-        this.addView(mSeparatorView);
-    }
-
-    protected void updateStickyHeader(int firstVisibleItem) {
-
-        // Try to make sure we have an adapter to work with (may not succeed).
-        if (mAdapter == null && mListView != null) {
-            setAdapter(mListView.getAdapter());
-        }
-
-        firstVisibleItem -= mListViewHeadersCount;
-        if (mAdapter != null && mIndexer != null && mDoHeaderReset) {
-
-            // Get the section header position
-            int sectionSize = 0;
-            int sectionPos = mIndexer.getHeaderPositionFromItemPosition(firstVisibleItem);
-
-            // New section - set it in the header view
-            boolean newView = false;
-            if (sectionPos != mCurrentSectionPos) {
-
-                // No header for current position , use the nonessential invisible one, hide the separator
-                if (sectionPos == -1) {
-                    sectionSize = 0;
-                    this.removeView(mStickyHeader);
-                    mStickyHeader = mNonessentialHeader;
-                    if (mSeparatorView != null) {
-                        mSeparatorView.setVisibility(View.GONE);
-                    }
-                    newView = true;
-                } else {
-                    // Create a copy of the header view to show on top
-                    sectionSize = mIndexer.getHeaderItemsNumber(sectionPos);
-                    View v = mAdapter.getView(sectionPos + mListViewHeadersCount, null, mListView);
-                    v.measure(MeasureSpec.makeMeasureSpec(mListView.getWidth(),
-                            MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mListView.getHeight(),
-                                    MeasureSpec.AT_MOST));
-                    this.removeView(mStickyHeader);
-                    mStickyHeader = v;
-                    newView = true;
-                }
-                mCurrentSectionPos = sectionPos;
-                mNextSectionPosition = sectionSize + sectionPos + 1;
-            }
-
-
-            // Do transitions
-            // If position of bottom of last item in a section is smaller than the height of the
-            // sticky header - shift drawable of header.
-            if (mStickyHeader != null) {
-                int sectionLastItemPosition =  mNextSectionPosition - firstVisibleItem - 1;
-                int stickyHeaderHeight = mStickyHeader.getHeight();
-                if (stickyHeaderHeight == 0) {
-                    stickyHeaderHeight = mStickyHeader.getMeasuredHeight();
-                }
-
-                // Update new header height
-                if (mHeaderHeightListener != null &&
-                        mLastStickyHeaderHeight != stickyHeaderHeight) {
-                    mLastStickyHeaderHeight = stickyHeaderHeight;
-                    mHeaderHeightListener.OnHeaderHeightChanged(stickyHeaderHeight);
-                }
-
-                View SectionLastView = mListView.getChildAt(sectionLastItemPosition);
-                if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
-                    int lastViewBottom = SectionLastView.getBottom();
-                    mStickyHeader.setTranslationY(lastViewBottom - stickyHeaderHeight);
-                    if (mSeparatorView != null) {
-                        mSeparatorView.setVisibility(View.GONE);
-                    }
-                } else if (stickyHeaderHeight != 0) {
-                    mStickyHeader.setTranslationY(0);
-                    if (mSeparatorView != null && !mStickyHeader.equals(mNonessentialHeader)) {
-                        mSeparatorView.setVisibility(View.VISIBLE);
-                    }
-                }
-                if (newView) {
-                    mStickyHeader.setVisibility(View.INVISIBLE);
-                    this.addView(mStickyHeader);
-                    if (mSeparatorView != null && !mStickyHeader.equals(mNonessentialHeader)){
-                        FrameLayout.LayoutParams params =
-                                new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                                        mSeparatorWidth);
-                        params.setMargins(0, mStickyHeader.getMeasuredHeight(), 0, 0);
-                        mSeparatorView.setLayoutParams(params);
-                        mSeparatorView.setVisibility(View.VISIBLE);
-                    }
-                    mStickyHeader.setVisibility(View.VISIBLE);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        if (!mChildViewsCreated) {
-            setChildViews();
-        }
-        mDoHeaderReset = true;
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (!mChildViewsCreated) {
-            setChildViews();
-        }
-        mDoHeaderReset = true;
-    }
-
-
-    // Resets the sticky header when the adapter data set was changed
-    // This code is needed only if dataset changes do not force a call to OnScroll
-    // protected void onDataChanged() {
-    // Should do a call to updateStickyHeader if needed
-    // }
-
-    private void setChildViews() {
-
-        // Find a child ListView (if any)
-        int iChildNum = getChildCount();
-        for (int i = 0; i < iChildNum; i++) {
-            Object v = getChildAt(i);
-            if (v instanceof ListView) {
-                setListView((ListView) v);
-            }
-        }
-
-        // No child ListView - add one
-        if (mListView == null) {
-            setListView(new ListView(mContext));
-        }
-
-        // Create a nonessential view , it will be used in case a section has no header
-        mNonessentialHeader = new View (mContext);
-        ViewGroup.LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
-                1, Gravity.TOP);
-        mNonessentialHeader.setLayoutParams(params);
-        mNonessentialHeader.setBackgroundColor(Color.TRANSPARENT);
-
-        mChildViewsCreated = true;
-    }
-
-}
diff --git a/src/com/android/calendar/StickyHeaderListView.kt b/src/com/android/calendar/StickyHeaderListView.kt
new file mode 100644
index 0000000..37733b7
--- /dev/null
+++ b/src/com/android/calendar/StickyHeaderListView.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.content.Context
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+import android.widget.Adapter
+import android.widget.FrameLayout
+import android.widget.ListView
+
+/**
+ * Implements a ListView class with a sticky header at the top. The header is
+ * per section and it is pinned to the top as long as its section is at the top
+ * of the view. If it is not, the header slides up or down (depending on the
+ * scroll movement) and the header of the current section slides to the top.
+ * Notes:
+ * 1. The class uses the first available child ListView as the working
+ * ListView. If no ListView child exists, the class will create a default one.
+ * 2. The ListView's adapter must be passed to this class using the 'setAdapter'
+ * method. The adapter must implement the HeaderIndexer interface. If no adapter
+ * is specified, the class will try to extract it from the ListView
+ * 3. The class registers itself as a listener to scroll events (OnScrollListener), if the
+ * ListView needs to receive scroll events, it must register its listener using
+ * this class' setOnScrollListener method.
+ * 4. Headers for the list view must be added before using the StickyHeaderListView
+ * 5. The implementation should register to listen to dataset changes. Right now this is not done
+ * since a change the dataset in a listview forces a call to OnScroll. The needed code is
+ * commented out.
+ */
+class StickyHeaderListView(context: Context, attrs: AttributeSet?) :
+    FrameLayout(context, attrs), OnScrollListener {
+    protected var mChildViewsCreated = false
+    protected var mDoHeaderReset = false
+    protected var mContext: Context? = null
+    protected var mAdapter: Adapter? = null
+    protected var mIndexer: HeaderIndexer? = null
+    protected var mHeaderHeightListener: HeaderHeightListener? = null
+    protected var mStickyHeader: View? = null
+    // A invisible header used when a section has no header
+    protected var mNonessentialHeader: View? = null
+    protected var mListView: ListView? = null
+    protected var mListener: AbsListView.OnScrollListener? = null
+    private var mSeparatorWidth = 0
+    private var mSeparatorView: View? = null
+    private var mLastStickyHeaderHeight = 0
+
+    // This code is needed only if dataset changes do not force a call to OnScroll
+    // protected DataSetObserver mListDataObserver = null;
+    protected var mCurrentSectionPos = -1 // Position of section that has its header on the
+
+    // top of the view
+    protected var mNextSectionPosition = -1 // Position of next section's header
+    protected var mListViewHeadersCount = 0
+
+    /**
+     * Interface that must be implemented by the ListView adapter to provide headers locations
+     * and number of items under each header.
+     *
+     */
+    interface HeaderIndexer {
+        /**
+         * Calculates the position of the header of a specific item in the adapter's data set.
+         * For example: Assuming you have a list with albums and songs names:
+         * Album A, song 1, song 2, ...., song 10, Album B, song 1, ..., song 7. A call to
+         * this method with the position of song 5 in Album B, should return  the position
+         * of Album B.
+         * @param position - Position of the item in the ListView dataset
+         * @return Position of header. -1 if the is no header
+         */
+        fun getHeaderPositionFromItemPosition(position: Int): Int
+
+        /**
+         * Calculates the number of items in the section defined by the header (not including
+         * the header).
+         * For example: A list with albums and songs, the method should return
+         * the number of songs names (without the album name).
+         *
+         * @param headerPosition - the value returned by 'getHeaderPositionFromItemPosition'
+         * @return Number of items. -1 on error.
+         */
+        fun getHeaderItemsNumber(headerPosition: Int): Int
+    }
+
+    /***
+     *
+     * Interface that is used to update the sticky header's height
+     *
+     */
+    interface HeaderHeightListener {
+        /***
+         * Updated a change in the sticky header's size
+         *
+         * @param height - new height of sticky header
+         */
+        fun OnHeaderHeightChanged(height: Int)
+    }
+
+    /**
+     * Sets the adapter to be used by the class to get views of headers
+     *
+     * @param adapter - The adapter.
+     */
+    fun setAdapter(adapter: Adapter?) {
+        // This code is needed only if dataset changes do not force a call to
+        // OnScroll
+        // if (mAdapter != null && mListDataObserver != null) {
+        // mAdapter.unregisterDataSetObserver(mListDataObserver);
+        // }
+        if (adapter != null) {
+            mAdapter = adapter
+            // This code is needed only if dataset changes do not force a call
+            // to OnScroll
+            // mAdapter.registerDataSetObserver(mListDataObserver);
+        }
+    }
+
+    /**
+     * Sets the indexer object (that implements the HeaderIndexer interface).
+     *
+     * @param indexer - The indexer.
+     */
+    fun setIndexer(indexer: HeaderIndexer?) {
+        mIndexer = indexer
+    }
+
+    /**
+     * Sets the list view that is displayed
+     * @param lv - The list view.
+     */
+    fun setListView(lv: ListView?) {
+        mListView = lv
+        mListView?.setOnScrollListener(this)
+        mListViewHeadersCount = mListView?.getHeaderViewsCount() as Int
+    }
+
+    /**
+     * Sets an external OnScroll listener. Since the StickyHeaderListView sets
+     * itself as the scroll events listener of the listview, this method allows
+     * the user to register another listener that will be called after this
+     * class listener is called.
+     *
+     * @param listener - The external listener.
+     */
+    fun setOnScrollListener(listener: AbsListView.OnScrollListener?) {
+        mListener = listener
+    }
+
+    fun setHeaderHeightListener(listener: HeaderHeightListener?) {
+        mHeaderHeightListener = listener
+    }
+
+    /**
+     * Scroll status changes listener
+     *
+     * @param view - the scrolled view
+     * @param scrollState - new scroll state.
+     */
+    @Override
+    override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+        if (mListener != null) {
+            mListener?.onScrollStateChanged(view, scrollState)
+        }
+    }
+
+    /**
+     * Scroll events listener
+     *
+     * @param view - the scrolled view
+     * @param firstVisibleItem - the index (in the list's adapter) of the top
+     * visible item.
+     * @param visibleItemCount - the number of visible items in the list
+     * @param totalItemCount - the total number items in the list
+     */
+    @Override
+    override fun onScroll(
+        view: AbsListView?,
+        firstVisibleItem: Int,
+        visibleItemCount: Int,
+        totalItemCount: Int
+    ) {
+        updateStickyHeader(firstVisibleItem)
+        if (mListener != null) {
+            mListener?.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount)
+        }
+    }
+
+    /**
+     * Sets a separator below the sticky header, which will be visible while the sticky header
+     * is not scrolling up.
+     * @param color - color of separator
+     * @param width - width in pixels of separator
+     */
+    fun setHeaderSeparator(color: Int, width: Int) {
+        mSeparatorView = View(mContext)
+        val params: ViewGroup.LayoutParams = LayoutParams(
+            LayoutParams.MATCH_PARENT,
+            width, Gravity.TOP
+        )
+        mSeparatorView?.setLayoutParams(params)
+        mSeparatorView?.setBackgroundColor(color)
+        mSeparatorWidth = width
+        this.addView(mSeparatorView)
+    }
+
+    protected fun updateStickyHeader(firstVisibleItemInput: Int) {
+        // Try to make sure we have an adapter to work with (may not succeed).
+        var firstVisibleItem = firstVisibleItemInput
+        if (mAdapter == null && mListView != null) {
+            setAdapter(mListView?.getAdapter())
+        }
+        firstVisibleItem -= mListViewHeadersCount
+        if (mAdapter != null && mIndexer != null && mDoHeaderReset) {
+
+            // Get the section header position
+            var sectionSize = 0
+            val sectionPos = mIndexer!!.getHeaderPositionFromItemPosition(firstVisibleItem)
+
+            // New section - set it in the header view
+            var newView = false
+            if (sectionPos != mCurrentSectionPos) {
+
+                // No header for current position , use the nonessential invisible one,
+                // hide the separator
+                if (sectionPos == -1) {
+                    sectionSize = 0
+                    this.removeView(mStickyHeader)
+                    mStickyHeader = mNonessentialHeader
+                    if (mSeparatorView != null) {
+                        mSeparatorView?.setVisibility(View.GONE)
+                    }
+                    newView = true
+                } else {
+                    // Create a copy of the header view to show on top
+                    sectionSize = mIndexer!!.getHeaderItemsNumber(sectionPos)
+                    val v: View? =
+                        mAdapter?.getView(sectionPos + mListViewHeadersCount, null, mListView)
+                    v?.measure(
+                        MeasureSpec.makeMeasureSpec(
+                            mListView?.getWidth() as Int,
+                            MeasureSpec.EXACTLY
+                        ), MeasureSpec.makeMeasureSpec(
+                            mListView?.getHeight() as Int,
+                            MeasureSpec.AT_MOST
+                        )
+                    )
+                    this.removeView(mStickyHeader)
+                    mStickyHeader = v
+                    newView = true
+                }
+                mCurrentSectionPos = sectionPos
+                mNextSectionPosition = sectionSize + sectionPos + 1
+            }
+
+            // Do transitions
+            // If position of bottom of last item in a section is smaller than the height of the
+            // sticky header - shift drawable of header.
+            if (mStickyHeader != null) {
+                val sectionLastItemPosition = mNextSectionPosition - firstVisibleItem - 1
+                var stickyHeaderHeight: Int = mStickyHeader?.getHeight() as Int
+                if (stickyHeaderHeight == 0) {
+                    stickyHeaderHeight = mStickyHeader?.getMeasuredHeight() as Int
+                }
+
+                // Update new header height
+                if (mHeaderHeightListener != null &&
+                    mLastStickyHeaderHeight != stickyHeaderHeight
+                ) {
+                    mLastStickyHeaderHeight = stickyHeaderHeight
+                    mHeaderHeightListener!!.OnHeaderHeightChanged(stickyHeaderHeight)
+                }
+                val SectionLastView: View? = mListView?.getChildAt(sectionLastItemPosition)
+                if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
+                    val lastViewBottom: Int = SectionLastView.getBottom()
+                    mStickyHeader?.setTranslationY(lastViewBottom.toFloat() -
+                        stickyHeaderHeight.toFloat())
+                    if (mSeparatorView != null) {
+                        mSeparatorView?.setVisibility(View.GONE)
+                    }
+                } else if (stickyHeaderHeight != 0) {
+                    mStickyHeader?.setTranslationY(0f)
+                    if (mSeparatorView != null &&
+                        mStickyHeader?.equals(mNonessentialHeader) == false) {
+                        mSeparatorView?.setVisibility(View.VISIBLE)
+                    }
+                }
+                if (newView) {
+                    mStickyHeader?.setVisibility(View.INVISIBLE)
+                    this.addView(mStickyHeader)
+                    if (mSeparatorView != null &&
+                        mStickyHeader?.equals(mNonessentialHeader) == false) {
+                        val params: FrameLayout.LayoutParams = LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            mSeparatorWidth
+                        )
+                        params.setMargins(0, mStickyHeader?.getMeasuredHeight() as Int, 0, 0)
+                        mSeparatorView?.setLayoutParams(params)
+                        mSeparatorView?.setVisibility(View.VISIBLE)
+                    }
+                    mStickyHeader?.setVisibility(View.VISIBLE)
+                }
+            }
+        }
+    }
+
+    @Override
+    protected override fun onFinishInflate() {
+        super.onFinishInflate()
+        if (!mChildViewsCreated) {
+            setChildViews()
+        }
+        mDoHeaderReset = true
+    }
+
+    @Override
+    protected override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        if (!mChildViewsCreated) {
+            setChildViews()
+        }
+        mDoHeaderReset = true
+    }
+
+    // Resets the sticky header when the adapter data set was changed
+    // This code is needed only if dataset changes do not force a call to OnScroll
+    // protected void onDataChanged() {
+    // Should do a call to updateStickyHeader if needed
+    // }
+    private fun setChildViews() {
+        // Find a child ListView (if any)
+        val iChildNum: Int = getChildCount()
+        for (i in 0 until iChildNum) {
+            val v: Object = getChildAt(i) as Object
+            if (v is ListView) {
+                setListView(v as ListView)
+            }
+        }
+
+        // No child ListView - add one
+        if (mListView == null) {
+            setListView(ListView(mContext))
+        }
+
+        // Create a nonessential view , it will be used in case a section has no header
+        mNonessentialHeader = View(mContext)
+        val params: ViewGroup.LayoutParams = LayoutParams(
+            LayoutParams.MATCH_PARENT,
+            1, Gravity.TOP
+        )
+        mNonessentialHeader?.setLayoutParams(params)
+        mNonessentialHeader?.setBackgroundColor(Color.TRANSPARENT)
+        mChildViewsCreated = true
+    }
+
+    companion object {
+        private const val TAG = "StickyHeaderListView"
+    }
+
+    /**
+     * Constructor
+     *
+     * @param context - application context.
+     * @param attrs - layout attributes.
+     */
+    init {
+        mContext = context
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/UpgradeReceiver.java b/src/com/android/calendar/UpgradeReceiver.java
deleted file mode 100644
index 0e89286..0000000
--- a/src/com/android/calendar/UpgradeReceiver.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import android.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/alerts/AlarmManagerInterface.java b/src/com/android/calendar/UpgradeReceiver.kt
similarity index 61%
copy from src/com/android/calendar/alerts/AlarmManagerInterface.java
copy to src/com/android/calendar/UpgradeReceiver.kt
index 3c66434..ab2de1d 100644
--- a/src/com/android/calendar/alerts/AlarmManagerInterface.java
+++ b/src/com/android/calendar/UpgradeReceiver.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.calendar
 
-package com.android.calendar.alerts;
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
 
-import android.app.PendingIntent;
-
-/**
- * AlarmManager abstracted to an interface for testability.
- */
-public interface AlarmManagerInterface {
-    public void set(int type, long triggerAtMillis, PendingIntent operation);
-}
+class UpgradeReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context?, intent: Intent?) {
+        Utils.trySyncAndDisableUpgradeReceiver(context)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
deleted file mode 100644
index cc55c99..0000000
--- a/src/com/android/calendar/Utils.java
+++ /dev/null
@@ -1,1499 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.CalendarContract.Calendars;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.Log;
-
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.CalendarUtils.TimeZoneUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Utils {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "CalUtils";
-
-    // Set to 0 until we have UI to perform undo
-    public static final long UNDO_DELAY = 0;
-
-    // For recurring events which instances of the series are being modified
-    public static final int MODIFY_UNINITIALIZED = 0;
-    public static final int MODIFY_SELECTED = 1;
-    public static final int MODIFY_ALL_FOLLOWING = 2;
-    public static final int MODIFY_ALL = 3;
-
-    // When the edit event view finishes it passes back the appropriate exit
-    // code.
-    public static final int DONE_REVERT = 1 << 0;
-    public static final int DONE_SAVE = 1 << 1;
-    public static final int DONE_DELETE = 1 << 2;
-    // And should re run with DONE_EXIT if it should also leave the view, just
-    // exiting is identical to reverting
-    public static final int DONE_EXIT = 1 << 0;
-
-    public static final String OPEN_EMAIL_MARKER = " <";
-    public static final String CLOSE_EMAIL_MARKER = ">";
-
-    public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW";
-    public static final String INTENT_KEY_VIEW_TYPE = "VIEW";
-    public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY";
-    public static final String INTENT_KEY_HOME = "KEY_HOME";
-
-    public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
-    public static final int DECLINED_EVENT_ALPHA = 0x66;
-    public static final int DECLINED_EVENT_TEXT_ALPHA = 0xC0;
-
-    private static final float SATURATION_ADJUST = 1.3f;
-    private static final float INTENSITY_ADJUST = 0.8f;
-
-    // Defines used by the DNA generation code
-    static final int DAY_IN_MINUTES = 60 * 24;
-    static final int WEEK_IN_MINUTES = DAY_IN_MINUTES * 7;
-    // The work day is being counted as 6am to 8pm
-    static int WORK_DAY_MINUTES = 14 * 60;
-    static int WORK_DAY_START_MINUTES = 6 * 60;
-    static int WORK_DAY_END_MINUTES = 20 * 60;
-    static int WORK_DAY_END_LENGTH = (24 * 60) - WORK_DAY_END_MINUTES;
-    static int CONFLICT_COLOR = 0xFF000000;
-    static boolean mMinutesLoaded = false;
-
-    public static final int YEAR_MIN = 1970;
-    public static final int YEAR_MAX = 2036;
-
-    // The name of the shared preferences file. This name must be maintained for
-    // historical
-    // reasons, as it's what PreferenceManager assigned the first time the file
-    // was created.
-    static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-
-    public static final String KEY_QUICK_RESPONSES = "preferences_quick_responses";
-
-    public static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen";
-
-    public static final String APPWIDGET_DATA_TYPE = "vnd.android.data/update";
-
-    static final String MACHINE_GENERATED_ADDRESS = "calendar.google.com";
-
-    private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME);
-    private static boolean mAllowWeekForDetailView = false;
-    private static long mTardis = 0;
-    private static String sVersion = null;
-
-    private static final Pattern mWildcardPattern = Pattern.compile("^.*$");
-
-    /**
-    * A coordinate must be of the following form for Google Maps to correctly use it:
-    * Latitude, Longitude
-    *
-    * This may be in decimal form:
-    * Latitude: {-90 to 90}
-    * Longitude: {-180 to 180}
-    *
-    * Or, in degrees, minutes, and seconds:
-    * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
-    * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
-    * + or - degrees may also be represented with N or n, S or s for latitude, and with
-    * E or e, W or w for longitude, where the direction may either precede or follow the value.
-    *
-    * Some examples of coordinates that will be accepted by the regex:
-    * 37.422081°, -122.084576°
-    * 37.422081,-122.084576
-    * +37°25'19.49", -122°5'4.47"
-    * 37°25'19.49"N, 122°5'4.47"W
-    * N 37° 25' 19.49",  W 122° 5' 4.47"
-    **/
-    private static final String COORD_DEGREES_LATITUDE =
-            "([-+NnSs]" + "(\\s)*)?"
-            + "[1-9]?[0-9](\u00B0)" + "(\\s)*"
-            + "([1-5]?[0-9]\')?" + "(\\s)*"
-            + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
-            + "((\\s)*" + "[NnSs])?";
-    private static final String COORD_DEGREES_LONGITUDE =
-            "([-+EeWw]" + "(\\s)*)?"
-            + "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*"
-            + "([1-5]?[0-9]\')?" + "(\\s)*"
-            + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
-            + "((\\s)*" + "[EeWw])?";
-    private static final String COORD_DEGREES_PATTERN =
-            COORD_DEGREES_LATITUDE
-            + "(\\s)*" + "," + "(\\s)*"
-            + COORD_DEGREES_LONGITUDE;
-    private static final String COORD_DECIMAL_LATITUDE =
-            "[+-]?"
-            + "[1-9]?[0-9]" + "(\\.[0-9]+)"
-            + "(\u00B0)?";
-    private static final String COORD_DECIMAL_LONGITUDE =
-            "[+-]?"
-            + "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)"
-            + "(\u00B0)?";
-    private static final String COORD_DECIMAL_PATTERN =
-            COORD_DECIMAL_LATITUDE
-            + "(\\s)*" + "," + "(\\s)*"
-            + COORD_DECIMAL_LONGITUDE;
-    private static final Pattern COORD_PATTERN =
-            Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN);
-
-    private static final String NANP_ALLOWED_SYMBOLS = "()+-*#.";
-    private static final int NANP_MIN_DIGITS = 7;
-    private static final int NANP_MAX_DIGITS = 11;
-
-
-    /**
-     * Returns whether the SDK is the Jellybean release or later.
-     */
-    public static boolean isJellybeanOrLater() {
-      return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-    }
-
-    /**
-     * Returns whether the SDK is the KeyLimePie release or later.
-     */
-    public static boolean isKeyLimePieOrLater() {
-      return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-    }
-
-    public static int getViewTypeFromIntentAndSharedPref(Activity activity) {
-        Intent intent = activity.getIntent();
-        Bundle extras = intent.getExtras();
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(activity);
-
-        if (TextUtils.equals(intent.getAction(), Intent.ACTION_EDIT)) {
-            return ViewType.EDIT;
-        }
-        if (extras != null) {
-            if (extras.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
-                // This is the "detail" view which is either agenda or day view
-                return prefs.getInt(GeneralPreferences.KEY_DETAILED_VIEW,
-                        GeneralPreferences.DEFAULT_DETAILED_VIEW);
-            } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras.getString(INTENT_KEY_VIEW_TYPE))) {
-                // Not sure who uses this. This logic came from LaunchActivity
-                return ViewType.DAY;
-            }
-        }
-
-        // Default to the last view
-        return prefs.getInt(
-                GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
-    }
-
-    /**
-     * Gets the intent action for telling the widget to update.
-     */
-    public static String getWidgetUpdateAction(Context context) {
-        return context.getPackageName() + ".APPWIDGET_UPDATE";
-    }
-
-    /**
-     * Gets the intent action for telling the widget to update.
-     */
-    public static String getWidgetScheduledUpdateAction(Context context) {
-        return context.getPackageName() + ".APPWIDGET_SCHEDULED_UPDATE";
-    }
-
-    /**
-     * Writes a new home time zone to the db. Updates the home time zone in the
-     * db asynchronously and updates the local cache. Sending a time zone of
-     * **tbd** will cause it to be set to the device's time zone. null or empty
-     * tz will be ignored.
-     *
-     * @param context The calling activity
-     * @param timeZone The time zone to set Calendar to, or **tbd**
-     */
-    public static void setTimeZone(Context context, String timeZone) {
-        mTZUtils.setTimeZone(context, timeZone);
-    }
-
-    /**
-     * Gets the time zone that Calendar should be displayed in This is a helper
-     * method to get the appropriate time zone for Calendar. If this is the
-     * first time this method has been called it will initiate an asynchronous
-     * query to verify that the data in preferences is correct. The callback
-     * supplied will only be called if this query returns a value other than
-     * what is stored in preferences and should cause the calling activity to
-     * refresh anything that depends on calling this method.
-     *
-     * @param context The calling activity
-     * @param callback The runnable that should execute if a query returns new
-     *            values
-     * @return The string value representing the time zone Calendar should
-     *         display
-     */
-    public static String getTimeZone(Context context, Runnable callback) {
-        return mTZUtils.getTimeZone(context, callback);
-    }
-
-    /**
-     * Formats a date or a time range according to the local conventions.
-     *
-     * @param context the context is required only if the time is shown
-     * @param startMillis the start time in UTC milliseconds
-     * @param endMillis the end time in UTC milliseconds
-     * @param flags a bit mask of options See {@link DateUtils#formatDateRange(Context, Formatter,
-     * long, long, int, String) formatDateRange}
-     * @return a string containing the formatted date/time range.
-     */
-    public static String formatDateRange(
-            Context context, long startMillis, long endMillis, int flags) {
-        return mTZUtils.formatDateRange(context, startMillis, endMillis, flags);
-    }
-
-    public static boolean getDefaultVibrate(Context context, SharedPreferences prefs) {
-        boolean vibrate;
-        if (prefs.contains(KEY_ALERTS_VIBRATE_WHEN)) {
-            // Migrate setting to new 4.2 behavior
-            //
-            // silent and never -> off
-            // always -> on
-            String vibrateWhen = prefs.getString(KEY_ALERTS_VIBRATE_WHEN, null);
-            vibrate = vibrateWhen != null && vibrateWhen.equals(context
-                    .getString(R.string.prefDefault_alerts_vibrate_true));
-            prefs.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit();
-            Log.d(TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" + vibrateWhen
-                    + ") to KEY_ALERTS_VIBRATE = " + vibrate);
-        } else {
-            vibrate = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE,
-                    false);
-        }
-        return vibrate;
-    }
-
-    public static String[] getSharedPreference(Context context, String key, String[] defaultValue) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        Set<String> ss = prefs.getStringSet(key, null);
-        if (ss != null) {
-            String strings[] = new String[ss.size()];
-            return ss.toArray(strings);
-        }
-        return defaultValue;
-    }
-
-    public static String getSharedPreference(Context context, String key, String defaultValue) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getString(key, defaultValue);
-    }
-
-    public static int getSharedPreference(Context context, String key, int defaultValue) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getInt(key, defaultValue);
-    }
-
-    public static boolean getSharedPreference(Context context, String key, boolean defaultValue) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getBoolean(key, defaultValue);
-    }
-
-    /**
-     * Asynchronously sets the preference with the given key to the given value
-     *
-     * @param context the context to use to get preferences from
-     * @param key the key of the preference to set
-     * @param value the value to set
-     */
-    public static void setSharedPreference(Context context, String key, String value) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        prefs.edit().putString(key, value).apply();
-    }
-
-    public static void setSharedPreference(Context context, String key, String[] values) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        LinkedHashSet<String> set = new LinkedHashSet<String>();
-        for (String value : values) {
-            set.add(value);
-        }
-        prefs.edit().putStringSet(key, set).apply();
-    }
-
-    protected static void tardis() {
-        mTardis = System.currentTimeMillis();
-    }
-
-    protected static long getTardis() {
-        return mTardis;
-    }
-
-    public static void setSharedPreference(Context context, String key, boolean value) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        SharedPreferences.Editor editor = prefs.edit();
-        editor.putBoolean(key, value);
-        editor.apply();
-    }
-
-    static void setSharedPreference(Context context, String key, int value) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        SharedPreferences.Editor editor = prefs.edit();
-        editor.putInt(key, value);
-        editor.apply();
-    }
-
-    public static void removeSharedPreference(Context context, String key) {
-        SharedPreferences prefs = context.getSharedPreferences(
-                GeneralPreferences.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
-        prefs.edit().remove(key).apply();
-    }
-
-    /**
-     * Save default agenda/day/week/month view for next time
-     *
-     * @param context
-     * @param viewId {@link CalendarController.ViewType}
-     */
-    static void setDefaultView(Context context, int viewId) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        SharedPreferences.Editor editor = prefs.edit();
-
-        boolean validDetailView = false;
-        if (mAllowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
-            validDetailView = true;
-        } else {
-            validDetailView = viewId == CalendarController.ViewType.AGENDA
-                    || viewId == CalendarController.ViewType.DAY;
-        }
-
-        if (validDetailView) {
-            // Record the detail start view
-            editor.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId);
-        }
-
-        // Record the (new) start view
-        editor.putInt(GeneralPreferences.KEY_START_VIEW, viewId);
-        editor.apply();
-    }
-
-    public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
-        if (cursor == null) {
-            return null;
-        }
-
-        String[] columnNames = cursor.getColumnNames();
-        if (columnNames == null) {
-            columnNames = new String[] {};
-        }
-        MatrixCursor newCursor = new MatrixCursor(columnNames);
-        int numColumns = cursor.getColumnCount();
-        String data[] = new String[numColumns];
-        cursor.moveToPosition(-1);
-        while (cursor.moveToNext()) {
-            for (int i = 0; i < numColumns; i++) {
-                data[i] = cursor.getString(i);
-            }
-            newCursor.addRow(data);
-        }
-        return newCursor;
-    }
-
-    /**
-     * Compares two cursors to see if they contain the same data.
-     *
-     * @return Returns true of the cursors contain the same data and are not
-     *         null, false otherwise
-     */
-    public static boolean compareCursors(Cursor c1, Cursor c2) {
-        if (c1 == null || c2 == null) {
-            return false;
-        }
-
-        int numColumns = c1.getColumnCount();
-        if (numColumns != c2.getColumnCount()) {
-            return false;
-        }
-
-        if (c1.getCount() != c2.getCount()) {
-            return false;
-        }
-
-        c1.moveToPosition(-1);
-        c2.moveToPosition(-1);
-        while (c1.moveToNext() && c2.moveToNext()) {
-            for (int i = 0; i < numColumns; i++) {
-                if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * If the given intent specifies a time (in milliseconds since the epoch),
-     * then that time is returned. Otherwise, the current time is returned.
-     */
-    public static final long timeFromIntentInMillis(Intent intent) {
-        // If the time was specified, then use that. Otherwise, use the current
-        // time.
-        Uri data = intent.getData();
-        long millis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1);
-        if (millis == -1 && data != null && data.isHierarchical()) {
-            List<String> path = data.getPathSegments();
-            if (path.size() == 2 && path.get(0).equals("time")) {
-                try {
-                    millis = Long.valueOf(data.getLastPathSegment());
-                } catch (NumberFormatException e) {
-                    Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time "
-                            + "found. Using current time.");
-                }
-            }
-        }
-        if (millis <= 0) {
-            millis = System.currentTimeMillis();
-        }
-        return millis;
-    }
-
-    /**
-     * Formats the given Time object so that it gives the month and year (for
-     * example, "September 2007").
-     *
-     * @param time the time to format
-     * @return the string containing the weekday and the date
-     */
-    public static String formatMonthYear(Context context, Time time) {
-        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
-                | DateUtils.FORMAT_SHOW_YEAR;
-        long millis = time.toMillis(true);
-        return formatDateRange(context, millis, millis, flags);
-    }
-
-    /**
-     * Returns a list joined together by the provided delimiter, for example,
-     * ["a", "b", "c"] could be joined into "a,b,c"
-     *
-     * @param things the things to join together
-     * @param delim the delimiter to use
-     * @return a string contained the things joined together
-     */
-    public static String join(List<?> things, String delim) {
-        StringBuilder builder = new StringBuilder();
-        boolean first = true;
-        for (Object thing : things) {
-            if (first) {
-                first = false;
-            } else {
-                builder.append(delim);
-            }
-            builder.append(thing.toString());
-        }
-        return builder.toString();
-    }
-
-    /**
-     * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
-     * adjusted for first day of week.
-     *
-     * This takes a julian day and the week start day and calculates which
-     * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting
-     * at 0. *Do not* use this to compute the ISO week number for the year.
-     *
-     * @param julianDay The julian day to calculate the week number for
-     * @param firstDayOfWeek Which week day is the first day of the week,
-     *          see {@link Time#SUNDAY}
-     * @return Weeks since the epoch
-     */
-    public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
-        int diff = Time.THURSDAY - firstDayOfWeek;
-        if (diff < 0) {
-            diff += 7;
-        }
-        int refDay = Time.EPOCH_JULIAN_DAY - diff;
-        return (julianDay - refDay) / 7;
-    }
-
-    /**
-     * Takes a number of weeks since the epoch and calculates the Julian day of
-     * the Monday for that week.
-     *
-     * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY}
-     * is considered week 0. It returns the Julian day for the Monday
-     * {@code week} weeks after the Monday of the week containing the epoch.
-     *
-     * @param week Number of weeks since the epoch
-     * @return The julian day for the Monday of the given week since the epoch
-     */
-    public static int getJulianMondayFromWeeksSinceEpoch(int week) {
-        return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
-    }
-
-    /**
-     * Get first day of week as android.text.format.Time constant.
-     *
-     * @return the first day of week in android.text.format.Time
-     */
-    public static int getFirstDayOfWeek(Context context) {
-        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        String pref = prefs.getString(
-                GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT);
-
-        int startDay;
-        if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
-            startDay = Calendar.getInstance().getFirstDayOfWeek();
-        } else {
-            startDay = Integer.parseInt(pref);
-        }
-
-        if (startDay == Calendar.SATURDAY) {
-            return Time.SATURDAY;
-        } else if (startDay == Calendar.MONDAY) {
-            return Time.MONDAY;
-        } else {
-            return Time.SUNDAY;
-        }
-    }
-
-    /**
-     * Get first day of week as java.util.Calendar constant.
-     *
-     * @return the first day of week as a java.util.Calendar constant
-     */
-    public static int getFirstDayOfWeekAsCalendar(Context context) {
-        return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context));
-    }
-
-    /**
-     * Converts the day of the week from android.text.format.Time to java.util.Calendar
-     */
-    public static int convertDayOfWeekFromTimeToCalendar(int timeDayOfWeek) {
-        switch (timeDayOfWeek) {
-            case Time.MONDAY:
-                return Calendar.MONDAY;
-            case Time.TUESDAY:
-                return Calendar.TUESDAY;
-            case Time.WEDNESDAY:
-                return Calendar.WEDNESDAY;
-            case Time.THURSDAY:
-                return Calendar.THURSDAY;
-            case Time.FRIDAY:
-                return Calendar.FRIDAY;
-            case Time.SATURDAY:
-                return Calendar.SATURDAY;
-            case Time.SUNDAY:
-                return Calendar.SUNDAY;
-            default:
-                throw new IllegalArgumentException("Argument must be between Time.SUNDAY and " +
-                        "Time.SATURDAY");
-        }
-    }
-
-    /**
-     * @return true when week number should be shown.
-     */
-    public static boolean getShowWeekNumber(Context context) {
-        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getBoolean(
-                GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM);
-    }
-
-    /**
-     * @return true when declined events should be hidden.
-     */
-    public static boolean getHideDeclinedEvents(Context context) {
-        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false);
-    }
-
-    public static int getDaysPerWeek(Context context) {
-        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
-        return prefs.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7);
-    }
-
-    /**
-     * Determine whether the column position is Saturday or not.
-     *
-     * @param column the column position
-     * @param firstDayOfWeek the first day of week in android.text.format.Time
-     * @return true if the column is Saturday position
-     */
-    public static boolean isSaturday(int column, int firstDayOfWeek) {
-        return (firstDayOfWeek == Time.SUNDAY && column == 6)
-                || (firstDayOfWeek == Time.MONDAY && column == 5)
-                || (firstDayOfWeek == Time.SATURDAY && column == 0);
-    }
-
-    /**
-     * Determine whether the column position is Sunday or not.
-     *
-     * @param column the column position
-     * @param firstDayOfWeek the first day of week in android.text.format.Time
-     * @return true if the column is Sunday position
-     */
-    public static boolean isSunday(int column, int firstDayOfWeek) {
-        return (firstDayOfWeek == Time.SUNDAY && column == 0)
-                || (firstDayOfWeek == Time.MONDAY && column == 6)
-                || (firstDayOfWeek == Time.SATURDAY && column == 1);
-    }
-
-    /**
-     * Convert given UTC time into current local time. This assumes it is for an
-     * allday event and will adjust the time to be on a midnight boundary.
-     *
-     * @param recycle Time object to recycle, otherwise null.
-     * @param utcTime Time to convert, in UTC.
-     * @param tz The time zone to convert this time to.
-     */
-    public static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz) {
-        if (recycle == null) {
-            recycle = new Time();
-        }
-        recycle.timezone = Time.TIMEZONE_UTC;
-        recycle.set(utcTime);
-        recycle.timezone = tz;
-        return recycle.normalize(true);
-    }
-
-    public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) {
-        if (recycle == null) {
-            recycle = new Time();
-        }
-        recycle.timezone = tz;
-        recycle.set(localTime);
-        recycle.timezone = Time.TIMEZONE_UTC;
-        return recycle.normalize(true);
-    }
-
-    /**
-     * Finds and returns the next midnight after "theTime" in milliseconds UTC
-     *
-     * @param recycle - Time object to recycle, otherwise null.
-     * @param theTime - Time used for calculations (in UTC)
-     * @param tz The time zone to convert this time to.
-     */
-    public static long getNextMidnight(Time recycle, long theTime, String tz) {
-        if (recycle == null) {
-            recycle = new Time();
-        }
-        recycle.timezone = tz;
-        recycle.set(theTime);
-        recycle.monthDay ++;
-        recycle.hour = 0;
-        recycle.minute = 0;
-        recycle.second = 0;
-        return recycle.normalize(true);
-    }
-
-    public static void setAllowWeekForDetailView(boolean allowWeekView) {
-        mAllowWeekForDetailView  = allowWeekView;
-    }
-
-    public static boolean getAllowWeekForDetailView() {
-        return mAllowWeekForDetailView;
-    }
-
-    public static boolean getConfigBool(Context c, int key) {
-        return c.getResources().getBoolean(key);
-    }
-
-    /**
-     * For devices with Jellybean or later, darkens the given color to ensure that white text is
-     * clearly visible on top of it.  For devices prior to Jellybean, does nothing, as the
-     * sync adapter handles the color change.
-     *
-     * @param color
-     */
-    public static int getDisplayColorFromColor(int color) {
-        if (!isJellybeanOrLater()) {
-            return color;
-        }
-
-        float[] hsv = new float[3];
-        Color.colorToHSV(color, hsv);
-        hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f);
-        hsv[2] = hsv[2] * INTENSITY_ADJUST;
-        return Color.HSVToColor(hsv);
-    }
-
-    // This takes a color and computes what it would look like blended with
-    // white. The result is the color that should be used for declined events.
-    public static int getDeclinedColorFromColor(int color) {
-        int bg = 0xffffffff;
-        int a = DECLINED_EVENT_ALPHA;
-        int r = (((color & 0x00ff0000) * a) + ((bg & 0x00ff0000) * (0xff - a))) & 0xff000000;
-        int g = (((color & 0x0000ff00) * a) + ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000;
-        int b = (((color & 0x000000ff) * a) + ((bg & 0x000000ff) * (0xff - a))) & 0x0000ff00;
-        return (0xff000000) | ((r | g | b) >> 8);
-    }
-
-    public static void trySyncAndDisableUpgradeReceiver(Context context) {
-        final PackageManager pm = context.getPackageManager();
-        ComponentName upgradeComponent = new ComponentName(context, UpgradeReceiver.class);
-        if (pm.getComponentEnabledSetting(upgradeComponent) ==
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
-            // The upgrade receiver has been disabled, which means this code has been run before,
-            // so no need to sync.
-            return;
-        }
-
-        Bundle extras = new Bundle();
-        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
-        ContentResolver.requestSync(
-                null /* no account */,
-                Calendars.CONTENT_URI.getAuthority(),
-                extras);
-
-        // Now unregister the receiver so that we won't continue to sync every time.
-        pm.setComponentEnabledSetting(upgradeComponent,
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
-    }
-
-    // A single strand represents one color of events. Events are divided up by
-    // color to make them convenient to draw. The black strand is special in
-    // that it holds conflicting events as well as color settings for allday on
-    // each day.
-    public static class DNAStrand {
-        public float[] points;
-        public int[] allDays; // color for the allday, 0 means no event
-        int position;
-        public int color;
-        int count;
-    }
-
-    // A segment is a single continuous length of time occupied by a single
-    // color. Segments should never span multiple days.
-    private static class DNASegment {
-        int startMinute; // in minutes since the start of the week
-        int endMinute;
-        int color; // Calendar color or black for conflicts
-        int day; // quick reference to the day this segment is on
-    }
-
-    /**
-     * Converts a list of events to a list of segments to draw. Assumes list is
-     * ordered by start time of the events. The function processes events for a
-     * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1.
-     * The algorithm goes over all the events and creates a set of segments
-     * ordered by start time. This list of segments is then converted into a
-     * HashMap of strands which contain the draw points and are organized by
-     * color. The strands can then be drawn by setting the paint color to each
-     * strand's color and calling drawLines on its set of points. The points are
-     * set up using the following parameters.
-     * <ul>
-     * <li>Events between midnight and WORK_DAY_START_MINUTES are compressed
-     * into the first 1/8th of the space between top and bottom.</li>
-     * <li>Events between WORK_DAY_END_MINUTES and the following midnight are
-     * compressed into the last 1/8th of the space between top and bottom</li>
-     * <li>Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use
-     * the remaining 3/4ths of the space</li>
-     * <li>All segments drawn will maintain at least minPixels height, except
-     * for conflicts in the first or last 1/8th, which may be smaller</li>
-     * </ul>
-     *
-     * @param firstJulianDay The julian day of the first day of events
-     * @param events A list of events sorted by start time
-     * @param top The lowest y value the dna should be drawn at
-     * @param bottom The highest y value the dna should be drawn at
-     * @param dayXs An array of x values to draw the dna at, one for each day
-     * @param conflictColor the color to use for conflicts
-     * @return
-     */
-    public static HashMap<Integer, DNAStrand> createDNAStrands(int firstJulianDay,
-            ArrayList<Event> events, int top, int bottom, int minPixels, int[] dayXs,
-            Context context) {
-
-        if (!mMinutesLoaded) {
-            if (context == null) {
-                Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA.");
-            }
-            Resources res = context.getResources();
-            CONFLICT_COLOR = res.getColor(R.color.month_dna_conflict_time_color);
-            WORK_DAY_START_MINUTES = res.getInteger(R.integer.work_start_minutes);
-            WORK_DAY_END_MINUTES = res.getInteger(R.integer.work_end_minutes);
-            WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES;
-            WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES;
-            mMinutesLoaded = true;
-        }
-
-        if (events == null || events.isEmpty() || dayXs == null || dayXs.length < 1
-                || bottom - top < 8 || minPixels < 0) {
-            Log.e(TAG,
-                    "Bad values for createDNAStrands! events:" + events + " dayXs:"
-                            + Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:"
-                            + minPixels);
-            return null;
-        }
-
-        LinkedList<DNASegment> segments = new LinkedList<DNASegment>();
-        HashMap<Integer, DNAStrand> strands = new HashMap<Integer, DNAStrand>();
-        // add a black strand by default, other colors will get added in
-        // the loop
-        DNAStrand blackStrand = new DNAStrand();
-        blackStrand.color = CONFLICT_COLOR;
-        strands.put(CONFLICT_COLOR, blackStrand);
-        // the min length is the number of minutes that will occupy
-        // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the
-        // minutes/pixel * minpx where the number of pixels are 3/4 the total
-        // dna height: 4*(mins/(px * 3/4))
-        int minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top));
-
-        // There are slightly fewer than half as many pixels in 1/6 the space,
-        // so round to 2.5x for the min minutes in the non-work area
-        int minOtherMinutes = minMinutes * 5 / 2;
-        int lastJulianDay = firstJulianDay + dayXs.length - 1;
-
-        Event event = new Event();
-        // Go through all the events for the week
-        for (Event currEvent : events) {
-            // if this event is outside the weeks range skip it
-            if (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay) {
-                continue;
-            }
-            if (currEvent.drawAsAllday()) {
-                addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.length);
-                continue;
-            }
-            // Copy the event over so we can clip its start and end to our range
-            currEvent.copyTo(event);
-            if (event.startDay < firstJulianDay) {
-                event.startDay = firstJulianDay;
-                event.startTime = 0;
-            }
-            // If it starts after the work day make sure the start is at least
-            // minPixels from midnight
-            if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) {
-                event.startTime = DAY_IN_MINUTES - minOtherMinutes;
-            }
-            if (event.endDay > lastJulianDay) {
-                event.endDay = lastJulianDay;
-                event.endTime = DAY_IN_MINUTES - 1;
-            }
-            // If the end time is before the work day make sure it ends at least
-            // minPixels after midnight
-            if (event.endTime < minOtherMinutes) {
-                event.endTime = minOtherMinutes;
-            }
-            // If the start and end are on the same day make sure they are at
-            // least minPixels apart. This only needs to be done for times
-            // outside the work day as the min distance for within the work day
-            // is enforced in the segment code.
-            if (event.startDay == event.endDay &&
-                    event.endTime - event.startTime < minOtherMinutes) {
-                // If it's less than minPixels in an area before the work
-                // day
-                if (event.startTime < WORK_DAY_START_MINUTES) {
-                    // extend the end to the first easy guarantee that it's
-                    // minPixels
-                    event.endTime = Math.min(event.startTime + minOtherMinutes,
-                            WORK_DAY_START_MINUTES + minMinutes);
-                    // if it's in the area after the work day
-                } else if (event.endTime > WORK_DAY_END_MINUTES) {
-                    // First try shifting the end but not past midnight
-                    event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1);
-                    // if it's still too small move the start back
-                    if (event.endTime - event.startTime < minOtherMinutes) {
-                        event.startTime = event.endTime - minOtherMinutes;
-                    }
-                }
-            }
-
-            // This handles adding the first segment
-            if (segments.size() == 0) {
-                addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes);
-                continue;
-            }
-            // Now compare our current start time to the end time of the last
-            // segment in the list
-            DNASegment lastSegment = segments.getLast();
-            int startMinute = (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime;
-            int endMinute = Math.max((event.endDay - firstJulianDay) * DAY_IN_MINUTES
-                    + event.endTime, startMinute + minMinutes);
-
-            if (startMinute < 0) {
-                startMinute = 0;
-            }
-            if (endMinute >= WEEK_IN_MINUTES) {
-                endMinute = WEEK_IN_MINUTES - 1;
-            }
-            // If we start before the last segment in the list ends we need to
-            // start going through the list as this may conflict with other
-            // events
-            if (startMinute < lastSegment.endMinute) {
-                int i = segments.size();
-                // find the last segment this event intersects with
-                while (--i >= 0 && endMinute < segments.get(i).startMinute);
-
-                DNASegment currSegment;
-                // for each segment this event intersects with
-                for (; i >= 0 && startMinute <= (currSegment = segments.get(i)).endMinute; i--) {
-                    // if the segment is already a conflict ignore it
-                    if (currSegment.color == CONFLICT_COLOR) {
-                        continue;
-                    }
-                    // if the event ends before the segment and wouldn't create
-                    // a segment that is too small split off the right side
-                    if (endMinute < currSegment.endMinute - minMinutes) {
-                        DNASegment rhs = new DNASegment();
-                        rhs.endMinute = currSegment.endMinute;
-                        rhs.color = currSegment.color;
-                        rhs.startMinute = endMinute + 1;
-                        rhs.day = currSegment.day;
-                        currSegment.endMinute = endMinute;
-                        segments.add(i + 1, rhs);
-                        strands.get(rhs.color).count++;
-                        if (DEBUG) {
-                            Log.d(TAG, "Added rhs, curr:" + currSegment.toString() + " i:"
-                                    + segments.get(i).toString());
-                        }
-                    }
-                    // if the event starts after the segment and wouldn't create
-                    // a segment that is too small split off the left side
-                    if (startMinute > currSegment.startMinute + minMinutes) {
-                        DNASegment lhs = new DNASegment();
-                        lhs.startMinute = currSegment.startMinute;
-                        lhs.color = currSegment.color;
-                        lhs.endMinute = startMinute - 1;
-                        lhs.day = currSegment.day;
-                        currSegment.startMinute = startMinute;
-                        // increment i so that we are at the right position when
-                        // referencing the segments to the right and left of the
-                        // current segment.
-                        segments.add(i++, lhs);
-                        strands.get(lhs.color).count++;
-                        if (DEBUG) {
-                            Log.d(TAG, "Added lhs, curr:" + currSegment.toString() + " i:"
-                                    + segments.get(i).toString());
-                        }
-                    }
-                    // if the right side is black merge this with the segment to
-                    // the right if they're on the same day and overlap
-                    if (i + 1 < segments.size()) {
-                        DNASegment rhs = segments.get(i + 1);
-                        if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day
-                                && rhs.startMinute <= currSegment.endMinute + 1) {
-                            rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute);
-                            segments.remove(currSegment);
-                            strands.get(currSegment.color).count--;
-                            // point at the new current segment
-                            currSegment = rhs;
-                        }
-                    }
-                    // if the left side is black merge this with the segment to
-                    // the left if they're on the same day and overlap
-                    if (i - 1 >= 0) {
-                        DNASegment lhs = segments.get(i - 1);
-                        if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day
-                                && lhs.endMinute >= currSegment.startMinute - 1) {
-                            lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute);
-                            segments.remove(currSegment);
-                            strands.get(currSegment.color).count--;
-                            // point at the new current segment
-                            currSegment = lhs;
-                            // point i at the new current segment in case new
-                            // code is added
-                            i--;
-                        }
-                    }
-                    // if we're still not black, decrement the count for the
-                    // color being removed, change this to black, and increment
-                    // the black count
-                    if (currSegment.color != CONFLICT_COLOR) {
-                        strands.get(currSegment.color).count--;
-                        currSegment.color = CONFLICT_COLOR;
-                        strands.get(CONFLICT_COLOR).count++;
-                    }
-                }
-
-            }
-            // If this event extends beyond the last segment add a new segment
-            if (endMinute > lastSegment.endMinute) {
-                addNewSegment(segments, event, strands, firstJulianDay, lastSegment.endMinute,
-                        minMinutes);
-            }
-        }
-        weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs);
-        return strands;
-    }
-
-    // This figures out allDay colors as allDay events are found
-    private static void addAllDayToStrands(Event event, HashMap<Integer, DNAStrand> strands,
-            int firstJulianDay, int numDays) {
-        DNAStrand strand = getOrCreateStrand(strands, CONFLICT_COLOR);
-        // if we haven't initialized the allDay portion create it now
-        if (strand.allDays == null) {
-            strand.allDays = new int[numDays];
-        }
-
-        // For each day this event is on update the color
-        int end = Math.min(event.endDay - firstJulianDay, numDays - 1);
-        for (int i = Math.max(event.startDay - firstJulianDay, 0); i <= end; i++) {
-            if (strand.allDays[i] != 0) {
-                // if this day already had a color, it is now a conflict
-                strand.allDays[i] = CONFLICT_COLOR;
-            } else {
-                // else it's just the color of the event
-                strand.allDays[i] = event.color;
-            }
-        }
-    }
-
-    // This processes all the segments, sorts them by color, and generates a
-    // list of points to draw
-    private static void weaveDNAStrands(LinkedList<DNASegment> segments, int firstJulianDay,
-            HashMap<Integer, DNAStrand> strands, int top, int bottom, int[] dayXs) {
-        // First, get rid of any colors that ended up with no segments
-        Iterator<DNAStrand> strandIterator = strands.values().iterator();
-        while (strandIterator.hasNext()) {
-            DNAStrand strand = strandIterator.next();
-            if (strand.count < 1 && strand.allDays == null) {
-                strandIterator.remove();
-                continue;
-            }
-            strand.points = new float[strand.count * 4];
-            strand.position = 0;
-        }
-        // Go through each segment and compute its points
-        for (DNASegment segment : segments) {
-            // Add the points to the strand of that color
-            DNAStrand strand = strands.get(segment.color);
-            int dayIndex = segment.day - firstJulianDay;
-            int dayStartMinute = segment.startMinute % DAY_IN_MINUTES;
-            int dayEndMinute = segment.endMinute % DAY_IN_MINUTES;
-            int height = bottom - top;
-            int workDayHeight = height * 3 / 4;
-            int remainderHeight = (height - workDayHeight) / 2;
-
-            int x = dayXs[dayIndex];
-            int y0 = 0;
-            int y1 = 0;
-
-            y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight);
-            y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight);
-            if (DEBUG) {
-                Log.d(TAG, "Adding " + Integer.toHexString(segment.color) + " at x,y0,y1: " + x
-                        + " " + y0 + " " + y1 + " for " + dayStartMinute + " " + dayEndMinute);
-            }
-            strand.points[strand.position++] = x;
-            strand.points[strand.position++] = y0;
-            strand.points[strand.position++] = x;
-            strand.points[strand.position++] = y1;
-        }
-    }
-
-    /**
-     * Compute a pixel offset from the top for a given minute from the work day
-     * height and the height of the top area.
-     */
-    private static int getPixelOffsetFromMinutes(int minute, int workDayHeight,
-            int remainderHeight) {
-        int y;
-        if (minute < WORK_DAY_START_MINUTES) {
-            y = minute * remainderHeight / WORK_DAY_START_MINUTES;
-        } else if (minute < WORK_DAY_END_MINUTES) {
-            y = remainderHeight + (minute - WORK_DAY_START_MINUTES) * workDayHeight
-                    / WORK_DAY_MINUTES;
-        } else {
-            y = remainderHeight + workDayHeight + (minute - WORK_DAY_END_MINUTES) * remainderHeight
-                    / WORK_DAY_END_LENGTH;
-        }
-        return y;
-    }
-
-    /**
-     * Add a new segment based on the event provided. This will handle splitting
-     * segments across day boundaries and ensures a minimum size for segments.
-     */
-    private static void addNewSegment(LinkedList<DNASegment> segments, Event event,
-            HashMap<Integer, DNAStrand> strands, int firstJulianDay, int minStart, int minMinutes) {
-        if (event.startDay > event.endDay) {
-            Log.wtf(TAG, "Event starts after it ends: " + event.toString());
-        }
-        // If this is a multiday event split it up by day
-        if (event.startDay != event.endDay) {
-            Event lhs = new Event();
-            lhs.color = event.color;
-            lhs.startDay = event.startDay;
-            // the first day we want the start time to be the actual start time
-            lhs.startTime = event.startTime;
-            lhs.endDay = lhs.startDay;
-            lhs.endTime = DAY_IN_MINUTES - 1;
-            // Nearly recursive iteration!
-            while (lhs.startDay != event.endDay) {
-                addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes);
-                // The days in between are all day, even though that shouldn't
-                // actually happen due to the allday filtering
-                lhs.startDay++;
-                lhs.endDay = lhs.startDay;
-                lhs.startTime = 0;
-                minStart = 0;
-            }
-            // The last day we want the end time to be the actual end time
-            lhs.endTime = event.endTime;
-            event = lhs;
-        }
-        // Create the new segment and compute its fields
-        DNASegment segment = new DNASegment();
-        int dayOffset = (event.startDay - firstJulianDay) * DAY_IN_MINUTES;
-        int endOfDay = dayOffset + DAY_IN_MINUTES - 1;
-        // clip the start if needed
-        segment.startMinute = Math.max(dayOffset + event.startTime, minStart);
-        // and extend the end if it's too small, but not beyond the end of the
-        // day
-        int minEnd = Math.min(segment.startMinute + minMinutes, endOfDay);
-        segment.endMinute = Math.max(dayOffset + event.endTime, minEnd);
-        if (segment.endMinute > endOfDay) {
-            segment.endMinute = endOfDay;
-        }
-
-        segment.color = event.color;
-        segment.day = event.startDay;
-        segments.add(segment);
-        // increment the count for the correct color or add a new strand if we
-        // don't have that color yet
-        DNAStrand strand = getOrCreateStrand(strands, segment.color);
-        strand.count++;
-    }
-
-    /**
-     * Try to get a strand of the given color. Create it if it doesn't exist.
-     */
-    private static DNAStrand getOrCreateStrand(HashMap<Integer, DNAStrand> strands, int color) {
-        DNAStrand strand = strands.get(color);
-        if (strand == null) {
-            strand = new DNAStrand();
-            strand.color = color;
-            strand.count = 0;
-            strands.put(strand.color, strand);
-        }
-        return strand;
-    }
-
-    /**
-     * Sends an intent to launch the top level Calendar view.
-     *
-     * @param context
-     */
-    public static void returnToCalendarHome(Context context) {
-        Intent launchIntent = new Intent(context, AllInOneActivity.class);
-        launchIntent.setAction(Intent.ACTION_DEFAULT);
-        launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        launchIntent.putExtra(INTENT_KEY_HOME, true);
-        context.startActivity(launchIntent);
-    }
-
-    /**
-     * Given a context and a time in millis since unix epoch figures out the
-     * correct week of the year for that time.
-     *
-     * @param millisSinceEpoch
-     * @return
-     */
-    public static int getWeekNumberFromTime(long millisSinceEpoch, Context context) {
-        Time weekTime = new Time(getTimeZone(context, null));
-        weekTime.set(millisSinceEpoch);
-        weekTime.normalize(true);
-        int firstDayOfWeek = getFirstDayOfWeek(context);
-        // if the date is on Saturday or Sunday and the start of the week
-        // isn't Monday we may need to shift the date to be in the correct
-        // week
-        if (weekTime.weekDay == Time.SUNDAY
-                && (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)) {
-            weekTime.monthDay++;
-            weekTime.normalize(true);
-        } else if (weekTime.weekDay == Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) {
-            weekTime.monthDay += 2;
-            weekTime.normalize(true);
-        }
-        return weekTime.getWeekNumber();
-    }
-
-    /**
-     * Formats a day of the week string. This is either just the name of the day
-     * or a combination of yesterday/today/tomorrow and the day of the week.
-     *
-     * @param julianDay The julian day to get the string for
-     * @param todayJulianDay The julian day for today's date
-     * @param millis A utc millis since epoch time that falls on julian day
-     * @param context The calling context, used to get the timezone and do the
-     *            formatting
-     * @return
-     */
-    public static String getDayOfWeekString(int julianDay, int todayJulianDay, long millis,
-            Context context) {
-        getTimeZone(context, null);
-        int flags = DateUtils.FORMAT_SHOW_WEEKDAY;
-        String dayViewText;
-        if (julianDay == todayJulianDay) {
-            dayViewText = context.getString(R.string.agenda_today,
-                    mTZUtils.formatDateRange(context, millis, millis, flags).toString());
-        } else if (julianDay == todayJulianDay - 1) {
-            dayViewText = context.getString(R.string.agenda_yesterday,
-                    mTZUtils.formatDateRange(context, millis, millis, flags).toString());
-        } else if (julianDay == todayJulianDay + 1) {
-            dayViewText = context.getString(R.string.agenda_tomorrow,
-                    mTZUtils.formatDateRange(context, millis, millis, flags).toString());
-        } else {
-            dayViewText = mTZUtils.formatDateRange(context, millis, millis, flags).toString();
-        }
-        dayViewText = dayViewText.toUpperCase();
-        return dayViewText;
-    }
-
-    // Calculate the time until midnight + 1 second and set the handler to
-    // do run the runnable
-    public static void setMidnightUpdater(Handler h, Runnable r, String timezone) {
-        if (h == null || r == null || timezone == null) {
-            return;
-        }
-        long now = System.currentTimeMillis();
-        Time time = new Time(timezone);
-        time.set(now);
-        long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 -
-                time.second + 1) * 1000;
-        h.removeCallbacks(r);
-        h.postDelayed(r, runInMillis);
-    }
-
-    // Stop the midnight update thread
-    public static void resetMidnightUpdater(Handler h, Runnable r) {
-        if (h == null || r == null) {
-            return;
-        }
-        h.removeCallbacks(r);
-    }
-
-    /**
-     * Returns a string description of the specified time interval.
-     */
-    public static String getDisplayedDatetime(long startMillis, long endMillis, long currentMillis,
-            String localTimezone, boolean allDay, Context context) {
-        // Configure date/time formatting.
-        int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
-        int flagsTime = DateUtils.FORMAT_SHOW_TIME;
-        if (DateFormat.is24HourFormat(context)) {
-            flagsTime |= DateUtils.FORMAT_24HOUR;
-        }
-
-        Time currentTime = new Time(localTimezone);
-        currentTime.set(currentMillis);
-        Resources resources = context.getResources();
-        String datetimeString = null;
-        if (allDay) {
-            // All day events require special timezone adjustment.
-            long localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone);
-            long localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone);
-            if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
-                // If possible, use "Today" or "Tomorrow" instead of a full date string.
-                int todayOrTomorrow = isTodayOrTomorrow(context.getResources(),
-                        localStartMillis, currentMillis, currentTime.gmtoff);
-                if (TODAY == todayOrTomorrow) {
-                    datetimeString = resources.getString(R.string.today);
-                } else if (TOMORROW == todayOrTomorrow) {
-                    datetimeString = resources.getString(R.string.tomorrow);
-                }
-            }
-            if (datetimeString == null) {
-                // For multi-day allday events or single-day all-day events that are not
-                // today or tomorrow, use framework formatter.
-                Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
-                datetimeString = DateUtils.formatDateRange(context, f, startMillis,
-                        endMillis, flagsDate, Time.TIMEZONE_UTC).toString();
-            }
-        } else {
-            if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
-                // Format the time.
-                String timeString = Utils.formatDateRange(context, startMillis, endMillis,
-                        flagsTime);
-
-                // If possible, use "Today" or "Tomorrow" instead of a full date string.
-                int todayOrTomorrow = isTodayOrTomorrow(context.getResources(), startMillis,
-                        currentMillis, currentTime.gmtoff);
-                if (TODAY == todayOrTomorrow) {
-                    // Example: "Today at 1:00pm - 2:00 pm"
-                    datetimeString = resources.getString(R.string.today_at_time_fmt,
-                            timeString);
-                } else if (TOMORROW == todayOrTomorrow) {
-                    // Example: "Tomorrow at 1:00pm - 2:00 pm"
-                    datetimeString = resources.getString(R.string.tomorrow_at_time_fmt,
-                            timeString);
-                } else {
-                    // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
-                    String dateString = Utils.formatDateRange(context, startMillis, endMillis,
-                            flagsDate);
-                    datetimeString = resources.getString(R.string.date_time_fmt, dateString,
-                            timeString);
-                }
-            } else {
-                // For multiday events, shorten day/month names.
-                // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
-                int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH |
-                        DateUtils.FORMAT_ABBREV_WEEKDAY;
-                datetimeString = Utils.formatDateRange(context, startMillis, endMillis,
-                        flagsDatetime);
-            }
-        }
-        return datetimeString;
-    }
-
-    /**
-     * Returns the timezone to display in the event info, if the local timezone is different
-     * from the event timezone.  Otherwise returns null.
-     */
-    public static String getDisplayedTimezone(long startMillis, String localTimezone,
-            String eventTimezone) {
-        String tzDisplay = null;
-        if (!TextUtils.equals(localTimezone, eventTimezone)) {
-            // Figure out if this is in DST
-            TimeZone tz = TimeZone.getTimeZone(localTimezone);
-            if (tz == null || tz.getID().equals("GMT")) {
-                tzDisplay = localTimezone;
-            } else {
-                Time startTime = new Time(localTimezone);
-                startTime.set(startMillis);
-                tzDisplay = tz.getDisplayName(startTime.isDst != 0, TimeZone.SHORT);
-            }
-        }
-        return tzDisplay;
-    }
-
-    /**
-     * Returns whether the specified time interval is in a single day.
-     */
-    private static boolean singleDayEvent(long startMillis, long endMillis, long localGmtOffset) {
-        if (startMillis == endMillis) {
-            return true;
-        }
-
-        // An event ending at midnight should still be a single-day event, so check
-        // time end-1.
-        int startDay = Time.getJulianDay(startMillis, localGmtOffset);
-        int endDay = Time.getJulianDay(endMillis - 1, localGmtOffset);
-        return startDay == endDay;
-    }
-
-    // Using int constants as a return value instead of an enum to minimize resources.
-    private static final int TODAY = 1;
-    private static final int TOMORROW = 2;
-    private static final int NONE = 0;
-
-    /**
-     * Returns TODAY or TOMORROW if applicable.  Otherwise returns NONE.
-     */
-    private static int isTodayOrTomorrow(Resources r, long dayMillis,
-            long currentMillis, long localGmtOffset) {
-        int startDay = Time.getJulianDay(dayMillis, localGmtOffset);
-        int currentDay = Time.getJulianDay(currentMillis, localGmtOffset);
-
-        int days = startDay - currentDay;
-        if (days == 1) {
-            return TOMORROW;
-        } else if (days == 0) {
-            return TODAY;
-        } else {
-            return NONE;
-        }
-    }
-
-    /**
-     * Inserts a drawable with today's day into the today's icon in the option menu
-     * @param icon - today's icon from the options menu
-     */
-    public static void setTodayIcon(LayerDrawable icon, Context c, String timezone) {
-        DayOfMonthDrawable today;
-
-        // Reuse current drawable if possible
-        Drawable currentDrawable = icon.findDrawableByLayerId(R.id.today_icon_day);
-        if (currentDrawable != null && currentDrawable instanceof DayOfMonthDrawable) {
-            today = (DayOfMonthDrawable)currentDrawable;
-        } else {
-            today = new DayOfMonthDrawable(c);
-        }
-        // Set the day and update the icon
-        Time now =  new Time(timezone);
-        now.setToNow();
-        now.normalize(false);
-        today.setDayOfMonth(now.monthDay);
-        icon.mutate();
-        icon.setDrawableByLayerId(R.id.today_icon_day, today);
-    }
-
-    /**
-     * Get a list of quick responses used for emailing guests from the
-     * SharedPreferences. If not are found, get the hard coded ones that shipped
-     * with the app
-     *
-     * @param context
-     * @return a list of quick responses.
-     */
-    public static String[] getQuickResponses(Context context) {
-        String[] s = Utils.getSharedPreference(context, KEY_QUICK_RESPONSES, (String[]) null);
-
-        if (s == null) {
-            s = context.getResources().getStringArray(R.array.quick_response_defaults);
-        }
-
-        return s;
-    }
-
-    /**
-     * Return the app version code.
-     */
-    public static String getVersionCode(Context context) {
-        if (sVersion == null) {
-            try {
-                sVersion = context.getPackageManager().getPackageInfo(
-                        context.getPackageName(), 0).versionName;
-            } catch (PackageManager.NameNotFoundException e) {
-                // Can't find version; just leave it blank.
-                Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName);
-            }
-        }
-        return sVersion;
-    }
-}
diff --git a/src/com/android/calendar/Utils.kt b/src/com/android/calendar/Utils.kt
new file mode 100644
index 0000000..ef78048
--- /dev/null
+++ b/src/com/android/calendar/Utils.kt
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.CalendarUtils.TimeZoneUtils
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Calendar
+import java.util.Formatter
+import java.util.HashMap
+import java.util.LinkedHashSet
+import java.util.LinkedList
+import java.util.List
+import java.util.Locale
+import java.util.TimeZone
+import java.util.regex.Pattern
+
+object Utils {
+    private const val DEBUG = false
+    private const val TAG = "CalUtils"
+
+    // Set to 0 until we have UI to perform undo
+    const val UNDO_DELAY: Long = 0
+
+    // For recurring events which instances of the series are being modified
+    const val MODIFY_UNINITIALIZED = 0
+    const val MODIFY_SELECTED = 1
+    const val MODIFY_ALL_FOLLOWING = 2
+    const val MODIFY_ALL = 3
+
+    // When the edit event view finishes it passes back the appropriate exit
+    // code.
+    const val DONE_REVERT = 1 shl 0
+    const val DONE_SAVE = 1 shl 1
+    const val DONE_DELETE = 1 shl 2
+
+    // And should re run with DONE_EXIT if it should also leave the view, just
+    // exiting is identical to reverting
+    const val DONE_EXIT = 1 shl 0
+    const val OPEN_EMAIL_MARKER = " <"
+    const val CLOSE_EMAIL_MARKER = ">"
+    const val INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW"
+    const val INTENT_KEY_VIEW_TYPE = "VIEW"
+    const val INTENT_VALUE_VIEW_TYPE_DAY = "DAY"
+    const val INTENT_KEY_HOME = "KEY_HOME"
+    val MONDAY_BEFORE_JULIAN_EPOCH: Int = Time.EPOCH_JULIAN_DAY - 3
+    const val DECLINED_EVENT_ALPHA = 0x66
+    const val DECLINED_EVENT_TEXT_ALPHA = 0xC0
+    private const val SATURATION_ADJUST = 1.3f
+    private const val INTENSITY_ADJUST = 0.8f
+
+    // Defines used by the DNA generation code
+    const val DAY_IN_MINUTES = 60 * 24
+    const val WEEK_IN_MINUTES = DAY_IN_MINUTES * 7
+
+    // The work day is being counted as 6am to 8pm
+    var WORK_DAY_MINUTES = 14 * 60
+    var WORK_DAY_START_MINUTES = 6 * 60
+    var WORK_DAY_END_MINUTES = 20 * 60
+    var WORK_DAY_END_LENGTH = 24 * 60 - WORK_DAY_END_MINUTES
+    var CONFLICT_COLOR = -0x1000000
+    var mMinutesLoaded = false
+    const val YEAR_MIN = 1970
+    const val YEAR_MAX = 2036
+
+    // The name of the shared preferences file. This name must be maintained for
+    // historical
+    // reasons, as it's what PreferenceManager assigned the first time the file
+    // was created.
+    const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
+    const val KEY_QUICK_RESPONSES = "preferences_quick_responses"
+    const val KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen"
+    const val APPWIDGET_DATA_TYPE = "vnd.android.data/update"
+    const val MACHINE_GENERATED_ADDRESS = "calendar.google.com"
+    private val mTZUtils: TimeZoneUtils? = TimeZoneUtils(SHARED_PREFS_NAME)
+    @JvmField var allowWeekForDetailView = false
+    internal var tardis: Long = 0
+        private set
+    private var sVersion: String? = null
+    private val mWildcardPattern: Pattern = Pattern.compile("^.*$")
+
+    /**
+     * A coordinate must be of the following form for Google Maps to correctly use it:
+     * Latitude, Longitude
+     *
+     * This may be in decimal form:
+     * Latitude: {-90 to 90}
+     * Longitude: {-180 to 180}
+     *
+     * Or, in degrees, minutes, and seconds:
+     * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
+     * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
+     * + or - degrees may also be represented with N or n, S or s for latitude, and with
+     * E or e, W or w for longitude, where the direction may either precede or follow the value.
+     *
+     * Some examples of coordinates that will be accepted by the regex:
+     * 37.422081°, -122.084576°
+     * 37.422081,-122.084576
+     * +37°25'19.49", -122°5'4.47"
+     * 37°25'19.49"N, 122°5'4.47"W
+     * N 37° 25' 19.49",  W 122° 5' 4.47"
+     */
+    private const val COORD_DEGREES_LATITUDE = ("([-+NnSs]" + "(\\s)*)?" +
+        "[1-9]?[0-9](\u00B0)" + "(\\s)*" +
+        "([1-5]?[0-9]\')?" + "(\\s)*" +
+        "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
+        "((\\s)*" + "[NnSs])?")
+    private const val COORD_DEGREES_LONGITUDE = ("([-+EeWw]" + "(\\s)*)?" +
+        "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*" +
+        "([1-5]?[0-9]\')?" + "(\\s)*" +
+        "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
+        "((\\s)*" + "[EeWw])?")
+    private const val COORD_DEGREES_PATTERN = (COORD_DEGREES_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
+        COORD_DEGREES_LONGITUDE)
+    private const val COORD_DECIMAL_LATITUDE = ("[+-]?" +
+        "[1-9]?[0-9]" + "(\\.[0-9]+)" +
+        "(\u00B0)?")
+    private const val COORD_DECIMAL_LONGITUDE = ("[+-]?" +
+        "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)" +
+        "(\u00B0)?")
+    private const val COORD_DECIMAL_PATTERN = (COORD_DECIMAL_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
+        COORD_DECIMAL_LONGITUDE)
+    private val COORD_PATTERN: Pattern =
+        Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN)
+    private const val NANP_ALLOWED_SYMBOLS = "()+-*#."
+    private const val NANP_MIN_DIGITS = 7
+    private const val NANP_MAX_DIGITS = 11
+
+    /**
+     * Returns whether the SDK is the KeyLimePie release or later.
+     */
+    @JvmStatic fun isKeyLimePieOrLater(): Boolean {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+    }
+
+    /**
+     * Returns whether the SDK is the Jellybean release or later.
+     */
+    @JvmStatic fun isJellybeanOrLater(): Boolean {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+    }
+
+    @JvmStatic fun getViewTypeFromIntentAndSharedPref(activity: Activity): Int {
+        val intent: Intent? = activity.getIntent()
+        val extras: Bundle? = intent?.getExtras()
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(activity)
+        if (TextUtils.equals(intent?.getAction(), Intent.ACTION_EDIT)) {
+            return ViewType.EDIT
+        }
+        if (extras != null) {
+            if (extras?.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
+                // This is the "detail" view which is either agenda or day view
+                return prefs?.getInt(
+                    GeneralPreferences.KEY_DETAILED_VIEW,
+                    GeneralPreferences.DEFAULT_DETAILED_VIEW
+                ) as Int
+            } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras?.getString(INTENT_KEY_VIEW_TYPE))) {
+                // Not sure who uses this. This logic came from LaunchActivity
+                return ViewType.DAY
+            }
+        }
+
+        // Default to the last view
+        return prefs?.getInt(
+            GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW
+        ) as Int
+    }
+
+    /**
+     * Gets the intent action for telling the widget to update.
+     */
+    @JvmStatic fun getWidgetUpdateAction(context: Context): String {
+        return context.getPackageName().toString() + ".APPWIDGET_UPDATE"
+    }
+
+    /**
+     * Gets the intent action for telling the widget to update.
+     */
+    @JvmStatic fun getWidgetScheduledUpdateAction(context: Context): String {
+        return context.getPackageName().toString() + ".APPWIDGET_SCHEDULED_UPDATE"
+    }
+
+    /**
+     * Writes a new home time zone to the db. Updates the home time zone in the
+     * db asynchronously and updates the local cache. Sending a time zone of
+     * **tbd** will cause it to be set to the device's time zone. null or empty
+     * tz will be ignored.
+     *
+     * @param context The calling activity
+     * @param timeZone The time zone to set Calendar to, or **tbd**
+     */
+    @JvmStatic fun setTimeZone(context: Context?, timeZone: String?) {
+        mTZUtils?.setTimeZone(context as Context, timeZone as String)
+    }
+
+    /**
+     * Gets the time zone that Calendar should be displayed in This is a helper
+     * method to get the appropriate time zone for Calendar. If this is the
+     * first time this method has been called it will initiate an asynchronous
+     * query to verify that the data in preferences is correct. The callback
+     * supplied will only be called if this query returns a value other than
+     * what is stored in preferences and should cause the calling activity to
+     * refresh anything that depends on calling this method.
+     *
+     * @param context The calling activity
+     * @param callback The runnable that should execute if a query returns new
+     * values
+     * @return The string value representing the time zone Calendar should
+     * display
+     */
+    @JvmStatic fun getTimeZone(context: Context?, callback: Runnable?): String? {
+        return mTZUtils?.getTimeZone(context as Context, callback)
+    }
+
+    /**
+     * Formats a date or a time range according to the local conventions.
+     *
+     * @param context the context is required only if the time is shown
+     * @param startMillis the start time in UTC milliseconds
+     * @param endMillis the end time in UTC milliseconds
+     * @param flags a bit mask of options See [formatDateRange][DateUtils.formatDateRange]
+     * @return a string containing the formatted date/time range.
+     */
+    @JvmStatic fun formatDateRange(
+        context: Context?,
+        startMillis: Long,
+        endMillis: Long,
+        flags: Int
+    ): String? {
+        return mTZUtils?.formatDateRange(context as Context, startMillis, endMillis, flags)
+    }
+
+    @JvmStatic fun getDefaultVibrate(context: Context, prefs: SharedPreferences?): Boolean {
+        val vibrate: Boolean
+        if (prefs?.contains(KEY_ALERTS_VIBRATE_WHEN) == true) {
+            // Migrate setting to new 4.2 behavior
+            //
+            // silent and never -> off
+            // always -> on
+            val vibrateWhen: String? = prefs?.getString(KEY_ALERTS_VIBRATE_WHEN, null)
+            vibrate = vibrateWhen != null && vibrateWhen.equals(
+                context
+                    .getString(R.string.prefDefault_alerts_vibrate_true)
+            )
+            prefs?.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit()
+            Log.d(
+                TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" +
+                    vibrateWhen + ") to KEY_ALERTS_VIBRATE = " + vibrate
+            )
+        } else {
+            vibrate = prefs?.getBoolean(
+                GeneralPreferences.KEY_ALERTS_VIBRATE,
+                false
+            ) as Boolean
+        }
+        return vibrate
+    }
+
+    @JvmStatic fun getSharedPreference(
+        context: Context?,
+        key: String?,
+        defaultValue: Array<String>?
+    ): Array<String>? {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val ss = prefs?.getStringSet(key, null)
+        if (ss != null) {
+            val strings = arrayOfNulls<String>(ss?.size)
+            return ss?.toTypedArray()
+        }
+        return defaultValue
+    }
+
+    @JvmStatic fun getSharedPreference(
+        context: Context?,
+        key: String?,
+        defaultValue: String?
+    ): String? {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getString(key, defaultValue)
+    }
+
+    @JvmStatic fun getSharedPreference(context: Context?, key: String?, defaultValue: Int): Int {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getInt(key, defaultValue) as Int
+    }
+
+    @JvmStatic fun getSharedPreference(
+        context: Context?,
+        key: String?,
+        defaultValue: Boolean
+    ): Boolean {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getBoolean(key, defaultValue) as Boolean
+    }
+
+    /**
+     * Asynchronously sets the preference with the given key to the given value
+     *
+     * @param context the context to use to get preferences from
+     * @param key the key of the preference to set
+     * @param value the value to set
+     */
+    @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: String?) {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        prefs?.edit()?.putString(key, value)?.apply()
+    }
+
+    @JvmStatic fun setSharedPreference(context: Context?, key: String?, values: Array<String?>) {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val set: LinkedHashSet<String?> = LinkedHashSet<String?>()
+        for (value in values) {
+            set.add(value)
+        }
+        prefs?.edit()?.putStringSet(key, set)?.apply()
+    }
+
+    internal fun tardis() {
+        tardis = System.currentTimeMillis()
+    }
+
+    @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Boolean) {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val editor: SharedPreferences.Editor? = prefs?.edit()
+        editor?.putBoolean(key, value)
+        editor?.apply()
+    }
+
+    @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Int) {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val editor: SharedPreferences.Editor? = prefs?.edit()
+        editor?.putInt(key, value)
+        editor?.apply()
+    }
+
+    @JvmStatic fun removeSharedPreference(context: Context?, key: String?) {
+        val prefs: SharedPreferences? = context?.getSharedPreferences(
+            GeneralPreferences.SHARED_PREFS_NAME, Context.MODE_PRIVATE
+        )
+        prefs?.edit()?.remove(key)?.apply()
+    }
+
+    /**
+     * Save default agenda/day/week/month view for next time
+     *
+     * @param context
+     * @param viewId [CalendarController.ViewType]
+     */
+    @JvmStatic fun setDefaultView(context: Context?, viewId: Int) {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val editor: SharedPreferences.Editor? = prefs?.edit()
+        var validDetailView = false
+        validDetailView =
+            if (allowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
+                true
+            } else {
+                (viewId == CalendarController.ViewType.AGENDA ||
+                    viewId == CalendarController.ViewType.DAY)
+            }
+        if (validDetailView) {
+            // Record the detail start view
+            editor?.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId)
+        }
+
+        // Record the (new) start view
+        editor?.putInt(GeneralPreferences.KEY_START_VIEW, viewId)
+        editor?.apply()
+    }
+
+    @JvmStatic fun matrixCursorFromCursor(cursor: Cursor?): MatrixCursor? {
+        if (cursor == null) {
+            return null
+        }
+        var columnNames: Array<String?> = cursor.getColumnNames()
+        if (columnNames == null) {
+            columnNames = arrayOf()
+        }
+        val newCursor = MatrixCursor(columnNames)
+        val numColumns: Int = cursor.getColumnCount()
+        val data = arrayOfNulls<String>(numColumns)
+        cursor.moveToPosition(-1)
+        while (cursor.moveToNext()) {
+            for (i in 0 until numColumns) {
+                data[i] = cursor.getString(i)
+            }
+            newCursor.addRow(data)
+        }
+        return newCursor
+    }
+
+    /**
+     * Compares two cursors to see if they contain the same data.
+     *
+     * @return Returns true of the cursors contain the same data and are not
+     * null, false otherwise
+     */
+    @JvmStatic fun compareCursors(c1: Cursor?, c2: Cursor?): Boolean {
+        if (c1 == null || c2 == null) {
+            return false
+        }
+        val numColumns: Int = c1.getColumnCount()
+        if (numColumns != c2.getColumnCount()) {
+            return false
+        }
+        if (c1.getCount() !== c2.getCount()) {
+            return false
+        }
+        c1.moveToPosition(-1)
+        c2.moveToPosition(-1)
+        while (c1.moveToNext() && c2.moveToNext()) {
+            for (i in 0 until numColumns) {
+                if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
+                    return false
+                }
+            }
+        }
+        return true
+    }
+
+    /**
+     * If the given intent specifies a time (in milliseconds since the epoch),
+     * then that time is returned. Otherwise, the current time is returned.
+     */
+    @JvmStatic fun timeFromIntentInMillis(intent: Intent?): Long? {
+        // If the time was specified, then use that. Otherwise, use the current
+        // time.
+        val data: Uri? = intent?.getData()
+        var millis: Long? = intent?.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1)?.toLong()
+        if (millis == -1L && data != null && data?.isHierarchical()) {
+            val path: List<String> = data?.getPathSegments() as List<String>
+            if (path.size == 2 && path[0].equals("time")) {
+                try {
+                    millis = (data?.getLastPathSegment()?.toLong())
+                } catch (e: NumberFormatException) {
+                    Log.i(
+                        "Calendar", "timeFromIntentInMillis: Data existed but no valid time " +
+                            "found. Using current time."
+                    )
+                }
+            }
+        }
+        if ((millis ?: 0L) <= 0) {
+            millis = System.currentTimeMillis()
+        }
+        return millis
+    }
+
+    /**
+     * Formats the given Time object so that it gives the month and year (for
+     * example, "September 2007").
+     *
+     * @param time the time to format
+     * @return the string containing the weekday and the date
+     */
+    @JvmStatic fun formatMonthYear(context: Context?, time: Time): String? {
+        val flags: Int = (DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY
+            or DateUtils.FORMAT_SHOW_YEAR)
+        val millis: Long = time.toMillis(true)
+        return formatDateRange(context, millis, millis, flags)
+    }
+
+    /**
+     * Returns a list joined together by the provided delimiter, for example,
+     * ["a", "b", "c"] could be joined into "a,b,c"
+     *
+     * @param things the things to join together
+     * @param delim the delimiter to use
+     * @return a string contained the things joined together
+     */
+    @JvmStatic fun join(things: List<*>, delim: String?): String {
+        val builder = StringBuilder()
+        var first = true
+        for (thing in things) {
+            if (first) {
+                first = false
+            } else {
+                builder.append(delim)
+            }
+            builder.append(thing.toString())
+        }
+        return builder.toString()
+    }
+
+    /**
+     * Returns the week since [Time.EPOCH_JULIAN_DAY] (Jan 1, 1970)
+     * adjusted for first day of week.
+     *
+     * This takes a julian day and the week start day and calculates which
+     * week since [Time.EPOCH_JULIAN_DAY] that day occurs in, starting
+     * at 0. *Do not* use this to compute the ISO week number for the year.
+     *
+     * @param julianDay The julian day to calculate the week number for
+     * @param firstDayOfWeek Which week day is the first day of the week,
+     * see [Time.SUNDAY]
+     * @return Weeks since the epoch
+     */
+    @JvmStatic fun getWeeksSinceEpochFromJulianDay(julianDay: Int, firstDayOfWeek: Int): Int {
+        var diff: Int = Time.THURSDAY - firstDayOfWeek
+        if (diff < 0) {
+            diff += 7
+        }
+        val refDay: Int = Time.EPOCH_JULIAN_DAY - diff
+        return (julianDay - refDay) / 7
+    }
+
+    /**
+     * Takes a number of weeks since the epoch and calculates the Julian day of
+     * the Monday for that week.
+     *
+     * This assumes that the week containing the [Time.EPOCH_JULIAN_DAY]
+     * is considered week 0. It returns the Julian day for the Monday
+     * `week` weeks after the Monday of the week containing the epoch.
+     *
+     * @param week Number of weeks since the epoch
+     * @return The julian day for the Monday of the given week since the epoch
+     */
+    @JvmStatic fun getJulianMondayFromWeeksSinceEpoch(week: Int): Int {
+        return MONDAY_BEFORE_JULIAN_EPOCH + week * 7
+    }
+
+    /**
+     * Get first day of week as android.text.format.Time constant.
+     *
+     * @return the first day of week in android.text.format.Time
+     */
+    @JvmStatic fun getFirstDayOfWeek(context: Context?): Int {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        val pref: String? = prefs?.getString(
+            GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT
+        )
+        val startDay: Int
+        startDay = if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
+            Calendar.getInstance().getFirstDayOfWeek()
+        } else {
+            Integer.parseInt(pref)
+        }
+        return if (startDay == Calendar.SATURDAY) {
+            Time.SATURDAY
+        } else if (startDay == Calendar.MONDAY) {
+            Time.MONDAY
+        } else {
+            Time.SUNDAY
+        }
+    }
+
+    /**
+     * Get first day of week as java.util.Calendar constant.
+     *
+     * @return the first day of week as a java.util.Calendar constant
+     */
+    @JvmStatic fun getFirstDayOfWeekAsCalendar(context: Context?): Int {
+        return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context))
+    }
+
+    /**
+     * Converts the day of the week from android.text.format.Time to java.util.Calendar
+     */
+    @JvmStatic fun convertDayOfWeekFromTimeToCalendar(timeDayOfWeek: Int): Int {
+        return when (timeDayOfWeek) {
+            Time.MONDAY -> Calendar.MONDAY
+            Time.TUESDAY -> Calendar.TUESDAY
+            Time.WEDNESDAY -> Calendar.WEDNESDAY
+            Time.THURSDAY -> Calendar.THURSDAY
+            Time.FRIDAY -> Calendar.FRIDAY
+            Time.SATURDAY -> Calendar.SATURDAY
+            Time.SUNDAY -> Calendar.SUNDAY
+            else -> throw IllegalArgumentException(
+                "Argument must be between Time.SUNDAY and " +
+                    "Time.SATURDAY"
+            )
+        }
+    }
+
+    /**
+     * @return true when week number should be shown.
+     */
+    @JvmStatic fun getShowWeekNumber(context: Context?): Boolean {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getBoolean(
+            GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM
+        ) as Boolean
+    }
+
+    /**
+     * @return true when declined events should be hidden.
+     */
+    @JvmStatic fun getHideDeclinedEvents(context: Context?): Boolean {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false) as Boolean
+    }
+
+    @JvmStatic fun getDaysPerWeek(context: Context?): Int {
+        val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
+        return prefs?.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7) as Int
+    }
+
+    /**
+     * Determine whether the column position is Saturday or not.
+     *
+     * @param column the column position
+     * @param firstDayOfWeek the first day of week in android.text.format.Time
+     * @return true if the column is Saturday position
+     */
+    @JvmStatic fun isSaturday(column: Int, firstDayOfWeek: Int): Boolean {
+        return (firstDayOfWeek == Time.SUNDAY && column == 6 ||
+            firstDayOfWeek == Time.MONDAY && column == 5 ||
+            firstDayOfWeek == Time.SATURDAY && column == 0)
+    }
+
+    /**
+     * Determine whether the column position is Sunday or not.
+     *
+     * @param column the column position
+     * @param firstDayOfWeek the first day of week in android.text.format.Time
+     * @return true if the column is Sunday position
+     */
+    @JvmStatic fun isSunday(column: Int, firstDayOfWeek: Int): Boolean {
+        return (firstDayOfWeek == Time.SUNDAY && column == 0 ||
+            firstDayOfWeek == Time.MONDAY && column == 6 ||
+            firstDayOfWeek == Time.SATURDAY && column == 1)
+    }
+
+    /**
+     * Convert given UTC time into current local time. This assumes it is for an
+     * allday event and will adjust the time to be on a midnight boundary.
+     *
+     * @param recycle Time object to recycle, otherwise null.
+     * @param utcTime Time to convert, in UTC.
+     * @param tz The time zone to convert this time to.
+     */
+    @JvmStatic fun convertAlldayUtcToLocal(recycle: Time?, utcTime: Long, tz: String): Long {
+        var recycle: Time? = recycle
+        if (recycle == null) {
+            recycle = Time()
+        }
+        recycle.timezone = Time.TIMEZONE_UTC
+        recycle.set(utcTime)
+        recycle.timezone = tz
+        return recycle.normalize(true)
+    }
+
+    @JvmStatic fun convertAlldayLocalToUTC(recycle: Time?, localTime: Long, tz: String): Long {
+        var recycle: Time? = recycle
+        if (recycle == null) {
+            recycle = Time()
+        }
+        recycle.timezone = tz
+        recycle.set(localTime)
+        recycle.timezone = Time.TIMEZONE_UTC
+        return recycle.normalize(true)
+    }
+
+    /**
+     * Finds and returns the next midnight after "theTime" in milliseconds UTC
+     *
+     * @param recycle - Time object to recycle, otherwise null.
+     * @param theTime - Time used for calculations (in UTC)
+     * @param tz The time zone to convert this time to.
+     */
+    @JvmStatic fun getNextMidnight(recycle: Time?, theTime: Long, tz: String): Long {
+        var recycle: Time? = recycle
+        if (recycle == null) {
+            recycle = Time()
+        }
+        recycle.timezone = tz
+        recycle.set(theTime)
+        recycle.monthDay++
+        recycle.hour = 0
+        recycle.minute = 0
+        recycle.second = 0
+        return recycle.normalize(true)
+    }
+
+    @JvmStatic fun setAllowWeekForDetailView(allowWeekView: Boolean) {
+        this.allowWeekForDetailView = allowWeekView
+    }
+
+    @JvmStatic fun getAllowWeekForDetailView(): Boolean {
+        return this.allowWeekForDetailView
+    }
+
+    @JvmStatic fun getConfigBool(c: Context, key: Int): Boolean {
+        return c.getResources().getBoolean(key)
+    }
+
+    /**
+     * For devices with Jellybean or later, darkens the given color to ensure that white text is
+     * clearly visible on top of it.  For devices prior to Jellybean, does nothing, as the
+     * sync adapter handles the color change.
+     *
+     * @param color
+     */
+    @JvmStatic fun getDisplayColorFromColor(color: Int): Int {
+        if (!isJellybeanOrLater()) {
+            return color
+        }
+        val hsv = FloatArray(3)
+        Color.colorToHSV(color, hsv)
+        hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f)
+        hsv[2] = hsv[2] * INTENSITY_ADJUST
+        return Color.HSVToColor(hsv)
+    }
+
+    // This takes a color and computes what it would look like blended with
+    // white. The result is the color that should be used for declined events.
+    @JvmStatic fun getDeclinedColorFromColor(color: Int): Int {
+        val bg = -0x1
+        val a = DECLINED_EVENT_ALPHA
+        val r = (color and 0x00ff0000) * a + (bg and 0x00ff0000) * (0xff - a) and -0x1000000
+        val g = (color and 0x0000ff00) * a + (bg and 0x0000ff00) * (0xff - a) and 0x00ff0000
+        val b = (color and 0x000000ff) * a + (bg and 0x000000ff) * (0xff - a) and 0x0000ff00
+        return -0x1000000 or (r or g or b shr 8)
+    }
+
+    @JvmStatic fun trySyncAndDisableUpgradeReceiver(context: Context?) {
+        val pm: PackageManager? = context?.getPackageManager()
+        val upgradeComponent = ComponentName(context as Context, UpgradeReceiver::class.java)
+        if (pm?.getComponentEnabledSetting(upgradeComponent) ===
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+        ) {
+            // The upgrade receiver has been disabled, which means this code has been run before,
+            // so no need to sync.
+            return
+        }
+        val extras = Bundle()
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
+        ContentResolver.requestSync(
+            null /* no account */,
+            Calendars.CONTENT_URI.getAuthority(),
+            extras
+        )
+
+        // Now unregister the receiver so that we won't continue to sync every time.
+        pm?.setComponentEnabledSetting(
+            upgradeComponent,
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
+        )
+    }
+
+    /**
+     * Converts a list of events to a list of segments to draw. Assumes list is
+     * ordered by start time of the events. The function processes events for a
+     * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1.
+     * The algorithm goes over all the events and creates a set of segments
+     * ordered by start time. This list of segments is then converted into a
+     * HashMap of strands which contain the draw points and are organized by
+     * color. The strands can then be drawn by setting the paint color to each
+     * strand's color and calling drawLines on its set of points. The points are
+     * set up using the following parameters.
+     *
+     *  * Events between midnight and WORK_DAY_START_MINUTES are compressed
+     * into the first 1/8th of the space between top and bottom.
+     *  * Events between WORK_DAY_END_MINUTES and the following midnight are
+     * compressed into the last 1/8th of the space between top and bottom
+     *  * Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use
+     * the remaining 3/4ths of the space
+     *  * All segments drawn will maintain at least minPixels height, except
+     * for conflicts in the first or last 1/8th, which may be smaller
+     *
+     *
+     * @param firstJulianDay The julian day of the first day of events
+     * @param events A list of events sorted by start time
+     * @param top The lowest y value the dna should be drawn at
+     * @param bottom The highest y value the dna should be drawn at
+     * @param dayXs An array of x values to draw the dna at, one for each day
+     * @param conflictColor the color to use for conflicts
+     * @return
+     */
+    @JvmStatic fun createDNAStrands(
+        firstJulianDay: Int,
+        events: ArrayList<Event?>?,
+        top: Int,
+        bottom: Int,
+        minPixels: Int,
+        dayXs: IntArray?,
+        context: Context?
+    ): HashMap<Int, DNAStrand>? {
+        if (!mMinutesLoaded) {
+            if (context == null) {
+                Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA.")
+            }
+            val res: Resources? = context?.getResources()
+            CONFLICT_COLOR = res?.getColor(R.color.month_dna_conflict_time_color) as Int
+            WORK_DAY_START_MINUTES = res?.getInteger(R.integer.work_start_minutes) as Int
+            WORK_DAY_END_MINUTES = res?.getInteger(R.integer.work_end_minutes) as Int
+            WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES
+            WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES
+            mMinutesLoaded = true
+        }
+        if (events == null || events.isEmpty() || dayXs == null || dayXs.size < 1 ||
+            bottom - top < 8 || minPixels < 0) {
+            Log.e(
+                TAG,
+                "Bad values for createDNAStrands! events:" + events + " dayXs:" +
+                    Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:" +
+                    minPixels
+            )
+            return null
+        }
+        val segments: LinkedList<DNASegment> = LinkedList<DNASegment>()
+        val strands: HashMap<Int, DNAStrand> = HashMap<Int, DNAStrand>()
+        // add a black strand by default, other colors will get added in
+        // the loop
+        val blackStrand = DNAStrand()
+        blackStrand.color = CONFLICT_COLOR
+        strands.put(CONFLICT_COLOR, blackStrand)
+        // the min length is the number of minutes that will occupy
+        // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the
+        // minutes/pixel * minpx where the number of pixels are 3/4 the total
+        // dna height: 4*(mins/(px * 3/4))
+        val minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top))
+
+        // There are slightly fewer than half as many pixels in 1/6 the space,
+        // so round to 2.5x for the min minutes in the non-work area
+        val minOtherMinutes = minMinutes * 5 / 2
+        val lastJulianDay = firstJulianDay + dayXs.size - 1
+        val event = Event()
+        // Go through all the events for the week
+        for (currEvent in events) {
+            // if this event is outside the weeks range skip it
+            if (currEvent != null &&
+                (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay)) {
+                continue
+            }
+            if (currEvent?.drawAsAllday() == true) {
+                addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.size)
+                continue
+            }
+            // Copy the event over so we can clip its start and end to our range
+            currEvent?.copyTo(event)
+            if (event.startDay < firstJulianDay) {
+                event.startDay = firstJulianDay
+                event.startTime = 0
+            }
+            // If it starts after the work day make sure the start is at least
+            // minPixels from midnight
+            if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) {
+                event.startTime = DAY_IN_MINUTES - minOtherMinutes
+            }
+            if (event.endDay > lastJulianDay) {
+                event.endDay = lastJulianDay
+                event.endTime = DAY_IN_MINUTES - 1
+            }
+            // If the end time is before the work day make sure it ends at least
+            // minPixels after midnight
+            if (event.endTime < minOtherMinutes) {
+                event.endTime = minOtherMinutes
+            }
+            // If the start and end are on the same day make sure they are at
+            // least minPixels apart. This only needs to be done for times
+            // outside the work day as the min distance for within the work day
+            // is enforced in the segment code.
+            if (event.startDay === event.endDay &&
+                event.endTime - event.startTime < minOtherMinutes
+            ) {
+                // If it's less than minPixels in an area before the work
+                // day
+                if (event.startTime < WORK_DAY_START_MINUTES) {
+                    // extend the end to the first easy guarantee that it's
+                    // minPixels
+                    event.endTime = Math.min(
+                        event.startTime + minOtherMinutes,
+                        WORK_DAY_START_MINUTES + minMinutes
+                    )
+                    // if it's in the area after the work day
+                } else if (event.endTime > WORK_DAY_END_MINUTES) {
+                    // First try shifting the end but not past midnight
+                    event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1)
+                    // if it's still too small move the start back
+                    if (event.endTime - event.startTime < minOtherMinutes) {
+                        event.startTime = event.endTime - minOtherMinutes
+                    }
+                }
+            }
+
+            // This handles adding the first segment
+            if (segments.size == 0) {
+                addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes)
+                continue
+            }
+            // Now compare our current start time to the end time of the last
+            // segment in the list
+            val lastSegment: DNASegment = segments.getLast()
+            var startMinute: Int =
+                (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime
+            var endMinute: Int = Math.max(
+                (event.endDay - firstJulianDay) * DAY_IN_MINUTES +
+                    event.endTime, startMinute + minMinutes
+            )
+            if (startMinute < 0) {
+                startMinute = 0
+            }
+            if (endMinute >= WEEK_IN_MINUTES) {
+                endMinute = WEEK_IN_MINUTES - 1
+            }
+            // If we start before the last segment in the list ends we need to
+            // start going through the list as this may conflict with other
+            // events
+            if (startMinute < lastSegment.endMinute) {
+                var i: Int = segments.size
+                // find the last segment this event intersects with
+                while (--i >= 0 && endMinute < segments.get(i).startMinute) {}
+
+                var currSegment: DNASegment = DNASegment()
+                // for each segment this event intersects with
+                while (i >= 0 && startMinute <= segments.get(i)
+                        .also { currSegment = it }.endMinute) {
+
+                    // if the segment is already a conflict ignore it
+                    if (currSegment.color == CONFLICT_COLOR) {
+                        i--
+                        continue
+                    }
+                    // if the event ends before the segment and wouldn't create
+                    // a segment that is too small split off the right side
+                    if (endMinute < currSegment.endMinute - minMinutes) {
+                        val rhs = DNASegment()
+                        rhs.endMinute = currSegment.endMinute
+                        rhs.color = currSegment.color
+                        rhs.startMinute = endMinute + 1
+                        rhs.day = currSegment.day
+                        currSegment.endMinute = endMinute
+                        segments.add(i + 1, rhs)
+                        // Equivalent to strands.get(rhs.color)?.count++
+                        // but there is no null safe invocation for ++
+                        strands.get(rhs.color)?.count = strands.get(rhs.color)?.count?.inc() as Int
+                        if (DEBUG) {
+                            Log.d(
+                                TAG, "Added rhs, curr:" + currSegment.toString() + " i:" +
+                                    segments.get(i).toString()
+                            )
+                        }
+                    }
+                    // if the event starts after the segment and wouldn't create
+                    // a segment that is too small split off the left side
+                    if (startMinute > currSegment.startMinute + minMinutes) {
+                        val lhs = DNASegment()
+                        lhs.startMinute = currSegment.startMinute
+                        lhs.color = currSegment.color
+                        lhs.endMinute = startMinute - 1
+                        lhs.day = currSegment.day
+                        currSegment.startMinute = startMinute
+                        // increment i so that we are at the right position when
+                        // referencing the segments to the right and left of the
+                        // current segment.
+                        segments.add(i++, lhs)
+                        strands.get(lhs.color)?.count = strands.get(lhs.color)?.count?.inc() as Int
+                        if (DEBUG) {
+                            Log.d(
+                                TAG, "Added lhs, curr:" + currSegment.toString() + " i:" +
+                                    segments.get(i).toString()
+                            )
+                        }
+                    }
+                    // if the right side is black merge this with the segment to
+                    // the right if they're on the same day and overlap
+                    if (i + 1 < segments.size) {
+                        val rhs: DNASegment = segments.get(i + 1)
+                        if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day &&
+                            rhs.startMinute <= currSegment.endMinute + 1) {
+                            rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute)
+                            segments.remove(currSegment)
+                            strands.get(currSegment.color)?.count =
+                                strands.get(currSegment.color)?.count?.dec() as Int
+                            // point at the new current segment
+                            currSegment = rhs
+                        }
+                    }
+                    // if the left side is black merge this with the segment to
+                    // the left if they're on the same day and overlap
+                    if (i - 1 >= 0) {
+                        val lhs: DNASegment = segments.get(i - 1)
+                        if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day &&
+                            lhs.endMinute >= currSegment.startMinute - 1) {
+                            lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute)
+                            segments.remove(currSegment)
+                            strands.get(currSegment.color)?.count =
+                                strands.get(currSegment.color)?.count?.dec() as Int
+                            // point at the new current segment
+                            currSegment = lhs
+                            // point i at the new current segment in case new
+                            // code is added
+                            i--
+                        }
+                    }
+                    // if we're still not black, decrement the count for the
+                    // color being removed, change this to black, and increment
+                    // the black count
+                    if (currSegment.color != CONFLICT_COLOR) {
+                        strands.get(currSegment.color)?.count =
+                            strands.get(currSegment.color)?.count?.dec() as Int
+                        currSegment.color = CONFLICT_COLOR
+                        strands.get(CONFLICT_COLOR)?.count =
+                            strands.get(CONFLICT_COLOR)?.count?.inc() as Int
+                    }
+                    i--
+                }
+            }
+            // If this event extends beyond the last segment add a new segment
+            if (endMinute > lastSegment.endMinute) {
+                addNewSegment(
+                    segments, event, strands, firstJulianDay, lastSegment.endMinute,
+                    minMinutes
+                )
+            }
+        }
+        weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs)
+        return strands
+    }
+
+    // This figures out allDay colors as allDay events are found
+    private fun addAllDayToStrands(
+        event: Event?,
+        strands: HashMap<Int, DNAStrand>,
+        firstJulianDay: Int,
+        numDays: Int
+    ) {
+        val strand = getOrCreateStrand(strands, CONFLICT_COLOR)
+        // if we haven't initialized the allDay portion create it now
+        if (strand?.allDays == null) {
+            strand?.allDays = IntArray(numDays)
+        }
+
+        // For each day this event is on update the color
+        val end: Int = Math.min((event?.endDay ?: 0) - firstJulianDay, numDays - 1)
+        for (i in Math.max((event?.startDay ?: 0) - firstJulianDay, 0)..end) {
+            if (strand?.allDays!![i] != 0) {
+                // if this day already had a color, it is now a conflict
+                strand?.allDays!![i] = CONFLICT_COLOR
+            } else {
+                // else it's just the color of the event
+                strand?.allDays!![i] = event?.color as Int
+            }
+        }
+    }
+
+    // This processes all the segments, sorts them by color, and generates a
+    // list of points to draw
+    private fun weaveDNAStrands(
+        segments: LinkedList<DNASegment>,
+        firstJulianDay: Int,
+        strands: HashMap<Int, DNAStrand>,
+        top: Int,
+        bottom: Int,
+        dayXs: IntArray
+    ) {
+        // First, get rid of any colors that ended up with no segments
+        val strandIterator = strands.values.iterator()
+        while (strandIterator.hasNext()) {
+            val strand = strandIterator.next()
+            if (strand?.count < 1 && strand.allDays == null) {
+                strandIterator.remove()
+                continue
+            }
+            strand.points = FloatArray(strand.count * 4)
+            strand.position = 0
+        }
+        // Go through each segment and compute its points
+        for (segment in segments) {
+            // Add the points to the strand of that color
+            val strand: DNAStrand? = strands.get(segment.color)
+            val dayIndex = segment.day - firstJulianDay
+            val dayStartMinute = segment.startMinute % DAY_IN_MINUTES
+            val dayEndMinute = segment.endMinute % DAY_IN_MINUTES
+            val height = bottom - top
+            val workDayHeight = height * 3 / 4
+            val remainderHeight = (height - workDayHeight) / 2
+            val x = dayXs[dayIndex]
+            var y0 = 0
+            var y1 = 0
+            y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight)
+            y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight)
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "Adding " + Integer.toHexString(segment.color).toString() + " at x,y0,y1: " + x
+                        .toString() + " " + y0.toString() + " " + y1.toString() +
+                        " for " + dayStartMinute.toString() + " " + dayEndMinute
+                )
+            }
+            strand?.points!![strand?.position] = x.toFloat()
+            strand?.position = strand?.position?.inc() as Int
+
+            strand?.points!![strand?.position] = y0.toFloat()
+            strand?.position = strand?.position?.inc() as Int
+
+            strand?.points!![strand?.position] = x.toFloat()
+            strand?.position = strand?.position.inc() as Int
+
+            strand?.points!![strand?.position] = y1.toFloat()
+            strand?.position = strand?.position.inc() as Int
+        }
+    }
+
+    /**
+     * Compute a pixel offset from the top for a given minute from the work day
+     * height and the height of the top area.
+     */
+    private fun getPixelOffsetFromMinutes(
+        minute: Int,
+        workDayHeight: Int,
+        remainderHeight: Int
+    ): Int {
+        val y: Int
+        if (minute < WORK_DAY_START_MINUTES) {
+            y = minute * remainderHeight / WORK_DAY_START_MINUTES
+        } else if (minute < WORK_DAY_END_MINUTES) {
+            y = remainderHeight + (minute - WORK_DAY_START_MINUTES) *
+                workDayHeight / WORK_DAY_MINUTES
+        } else {
+            y = remainderHeight + workDayHeight +
+                (minute - WORK_DAY_END_MINUTES) * remainderHeight / WORK_DAY_END_LENGTH
+        }
+        return y
+    }
+
+    /**
+     * Add a new segment based on the event provided. This will handle splitting
+     * segments across day boundaries and ensures a minimum size for segments.
+     */
+    private fun addNewSegment(
+        segments: LinkedList<DNASegment>,
+        event: Event,
+        strands: HashMap<Int, DNAStrand>,
+        firstJulianDay: Int,
+        minStart: Int,
+        minMinutes: Int
+    ) {
+        var event: Event = event
+        var minStart = minStart
+        if (event.startDay > event.endDay) {
+            Log.wtf(TAG, "Event starts after it ends: " + event.toString())
+        }
+        // If this is a multiday event split it up by day
+        if (event.startDay !== event.endDay) {
+            val lhs = Event()
+            lhs.color = event.color
+            lhs.startDay = event.startDay
+            // the first day we want the start time to be the actual start time
+            lhs.startTime = event.startTime
+            lhs.endDay = lhs.startDay
+            lhs.endTime = DAY_IN_MINUTES - 1
+            // Nearly recursive iteration!
+            while (lhs.startDay !== event.endDay) {
+                addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes)
+                // The days in between are all day, even though that shouldn't
+                // actually happen due to the allday filtering
+                lhs.startDay++
+                lhs.endDay = lhs.startDay
+                lhs.startTime = 0
+                minStart = 0
+            }
+            // The last day we want the end time to be the actual end time
+            lhs.endTime = event.endTime
+            event = lhs
+        }
+        // Create the new segment and compute its fields
+        val segment = DNASegment()
+        val dayOffset: Int = (event.startDay - firstJulianDay) * DAY_IN_MINUTES
+        val endOfDay = dayOffset + DAY_IN_MINUTES - 1
+        // clip the start if needed
+        segment.startMinute = Math.max(dayOffset + event.startTime, minStart)
+        // and extend the end if it's too small, but not beyond the end of the
+        // day
+        val minEnd: Int = Math.min(segment.startMinute + minMinutes, endOfDay)
+        segment.endMinute = Math.max(dayOffset + event.endTime, minEnd)
+        if (segment.endMinute > endOfDay) {
+            segment.endMinute = endOfDay
+        }
+        segment.color = event.color
+        segment.day = event.startDay
+        segments.add(segment)
+        // increment the count for the correct color or add a new strand if we
+        // don't have that color yet
+        val strand = getOrCreateStrand(strands, segment.color)
+        strand?.count
+        strand?.count = strand?.count?.inc() as Int
+    }
+
+    /**
+     * Try to get a strand of the given color. Create it if it doesn't exist.
+     */
+    private fun getOrCreateStrand(strands: HashMap<Int, DNAStrand>, color: Int): DNAStrand? {
+        var strand: DNAStrand? = strands.get(color)
+        if (strand == null) {
+            strand = DNAStrand()
+            strand?.color = color
+            strand?.count = 0
+            strands?.put(strand?.color, strand)
+        }
+        return strand
+    }
+
+    /**
+     * Sends an intent to launch the top level Calendar view.
+     *
+     * @param context
+     */
+    @JvmStatic fun returnToCalendarHome(context: Context) {
+        val launchIntent = Intent(context, AllInOneActivity::class.java)
+        launchIntent.setAction(Intent.ACTION_DEFAULT)
+        launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        launchIntent.putExtra(INTENT_KEY_HOME, true)
+        context.startActivity(launchIntent)
+    }
+
+    /**
+     * Given a context and a time in millis since unix epoch figures out the
+     * correct week of the year for that time.
+     *
+     * @param millisSinceEpoch
+     * @return
+     */
+    @JvmStatic fun getWeekNumberFromTime(millisSinceEpoch: Long, context: Context?): Int {
+        val weekTime = Time(getTimeZone(context, null))
+        weekTime.set(millisSinceEpoch)
+        weekTime.normalize(true)
+        val firstDayOfWeek = getFirstDayOfWeek(context)
+        // if the date is on Saturday or Sunday and the start of the week
+        // isn't Monday we may need to shift the date to be in the correct
+        // week
+        if (weekTime.weekDay === Time.SUNDAY &&
+            (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)
+        ) {
+            weekTime.monthDay++
+            weekTime.normalize(true)
+        } else if (weekTime.weekDay === Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) {
+            weekTime.monthDay += 2
+            weekTime.normalize(true)
+        }
+        return weekTime.getWeekNumber()
+    }
+
+    /**
+     * Formats a day of the week string. This is either just the name of the day
+     * or a combination of yesterday/today/tomorrow and the day of the week.
+     *
+     * @param julianDay The julian day to get the string for
+     * @param todayJulianDay The julian day for today's date
+     * @param millis A utc millis since epoch time that falls on julian day
+     * @param context The calling context, used to get the timezone and do the
+     * formatting
+     * @return
+     */
+    @JvmStatic fun getDayOfWeekString(
+        julianDay: Int,
+        todayJulianDay: Int,
+        millis: Long,
+        context: Context
+    ): String {
+        getTimeZone(context, null)
+        val flags: Int = DateUtils.FORMAT_SHOW_WEEKDAY
+        var dayViewText: String
+        dayViewText = if (julianDay == todayJulianDay) {
+            context.getString(
+                R.string.agenda_today,
+                mTZUtils?.formatDateRange(context, millis, millis, flags)
+                    .toString()
+            )
+        } else if (julianDay == todayJulianDay - 1) {
+            context.getString(
+                R.string.agenda_yesterday,
+                mTZUtils?.formatDateRange(context, millis, millis, flags)
+                    .toString()
+            )
+        } else if (julianDay == todayJulianDay + 1) {
+            context.getString(
+                R.string.agenda_tomorrow,
+                mTZUtils?.formatDateRange(context, millis, millis, flags)
+                    .toString()
+            )
+        } else {
+            mTZUtils?.formatDateRange(context, millis, millis, flags)
+                .toString()
+        }
+        dayViewText = dayViewText.toUpperCase()
+        return dayViewText
+    }
+
+    // Calculate the time until midnight + 1 second and set the handler to
+    // do run the runnable
+    @JvmStatic fun setMidnightUpdater(h: Handler?, r: Runnable?, timezone: String?) {
+        if (h == null || r == null || timezone == null) {
+            return
+        }
+        val now: Long = System.currentTimeMillis()
+        val time = Time(timezone)
+        time.set(now)
+        val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 -
+            time.second + 1) * 1000).toLong()
+        h.removeCallbacks(r)
+        h.postDelayed(r, runInMillis)
+    }
+
+    // Stop the midnight update thread
+    @JvmStatic fun resetMidnightUpdater(h: Handler?, r: Runnable?) {
+        if (h == null || r == null) {
+            return
+        }
+        h.removeCallbacks(r)
+    }
+
+    /**
+     * Returns a string description of the specified time interval.
+     */
+    @JvmStatic fun getDisplayedDatetime(
+        startMillis: Long,
+        endMillis: Long,
+        currentMillis: Long,
+        localTimezone: String,
+        allDay: Boolean,
+        context: Context
+    ): String? {
+        // Configure date/time formatting.
+        val flagsDate: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY
+        var flagsTime: Int = DateUtils.FORMAT_SHOW_TIME
+        if (DateFormat.is24HourFormat(context)) {
+            flagsTime = flagsTime or DateUtils.FORMAT_24HOUR
+        }
+        val currentTime = Time(localTimezone)
+        currentTime.set(currentMillis)
+        val resources: Resources = context.getResources()
+        var datetimeString: String? = null
+        if (allDay) {
+            // All day events require special timezone adjustment.
+            val localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone)
+            val localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone)
+            if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
+                // If possible, use "Today" or "Tomorrow" instead of a full date string.
+                val todayOrTomorrow = isTodayOrTomorrow(
+                    context.getResources(),
+                    localStartMillis, currentMillis, currentTime.gmtoff
+                )
+                if (TODAY == todayOrTomorrow) {
+                    datetimeString = resources.getString(R.string.today)
+                } else if (TOMORROW == todayOrTomorrow) {
+                    datetimeString = resources.getString(R.string.tomorrow)
+                }
+            }
+            if (datetimeString == null) {
+                // For multi-day allday events or single-day all-day events that are not
+                // today or tomorrow, use framework formatter.
+                val f = Formatter(StringBuilder(50), Locale.getDefault())
+                datetimeString = DateUtils.formatDateRange(
+                    context, f, startMillis,
+                    endMillis, flagsDate, Time.TIMEZONE_UTC
+                ).toString()
+            }
+        } else {
+            datetimeString = if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
+                // Format the time.
+                val timeString = formatDateRange(
+                    context, startMillis, endMillis,
+                    flagsTime
+                )
+
+                // If possible, use "Today" or "Tomorrow" instead of a full date string.
+                val todayOrTomorrow = isTodayOrTomorrow(
+                    context.getResources(), startMillis,
+                    currentMillis, currentTime.gmtoff
+                )
+                if (TODAY == todayOrTomorrow) {
+                    // Example: "Today at 1:00pm - 2:00 pm"
+                    resources.getString(
+                        R.string.today_at_time_fmt,
+                        timeString
+                    )
+                } else if (TOMORROW == todayOrTomorrow) {
+                    // Example: "Tomorrow at 1:00pm - 2:00 pm"
+                    resources.getString(
+                        R.string.tomorrow_at_time_fmt,
+                        timeString
+                    )
+                } else {
+                    // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
+                    val dateString = formatDateRange(
+                        context, startMillis, endMillis,
+                        flagsDate
+                    )
+                    resources.getString(
+                        R.string.date_time_fmt, dateString,
+                        timeString
+                    )
+                }
+            } else {
+                // For multiday events, shorten day/month names.
+                // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
+                val flagsDatetime = flagsDate or flagsTime or DateUtils.FORMAT_ABBREV_MONTH or
+                    DateUtils.FORMAT_ABBREV_WEEKDAY
+                formatDateRange(
+                    context, startMillis, endMillis,
+                    flagsDatetime
+                )
+            }
+        }
+        return datetimeString
+    }
+
+    /**
+     * Returns the timezone to display in the event info, if the local timezone is different
+     * from the event timezone.  Otherwise returns null.
+     */
+    @JvmStatic fun getDisplayedTimezone(
+        startMillis: Long,
+        localTimezone: String?,
+        eventTimezone: String?
+    ): String? {
+        var tzDisplay: String? = null
+        if (!TextUtils.equals(localTimezone, eventTimezone)) {
+            // Figure out if this is in DST
+            val tz: TimeZone = TimeZone.getTimeZone(localTimezone)
+            tzDisplay = if (tz == null || tz.getID().equals("GMT")) {
+                localTimezone
+            } else {
+                val startTime = Time(localTimezone)
+                startTime.set(startMillis)
+                tz.getDisplayName(startTime.isDst !== 0, TimeZone.SHORT)
+            }
+        }
+        return tzDisplay
+    }
+
+    /**
+     * Returns whether the specified time interval is in a single day.
+     */
+    private fun singleDayEvent(startMillis: Long, endMillis: Long, localGmtOffset: Long): Boolean {
+        if (startMillis == endMillis) {
+            return true
+        }
+
+        // An event ending at midnight should still be a single-day event, so check
+        // time end-1.
+        val startDay: Int = Time.getJulianDay(startMillis, localGmtOffset)
+        val endDay: Int = Time.getJulianDay(endMillis - 1, localGmtOffset)
+        return startDay == endDay
+    }
+
+    // Using int constants as a return value instead of an enum to minimize resources.
+    private const val TODAY = 1
+    private const val TOMORROW = 2
+    private const val NONE = 0
+
+    /**
+     * Returns TODAY or TOMORROW if applicable.  Otherwise returns NONE.
+     */
+    private fun isTodayOrTomorrow(
+        r: Resources,
+        dayMillis: Long,
+        currentMillis: Long,
+        localGmtOffset: Long
+    ): Int {
+        val startDay: Int = Time.getJulianDay(dayMillis, localGmtOffset)
+        val currentDay: Int = Time.getJulianDay(currentMillis, localGmtOffset)
+        val days = startDay - currentDay
+        return if (days == 1) {
+            TOMORROW
+        } else if (days == 0) {
+            TODAY
+        } else {
+            NONE
+        }
+    }
+
+    /**
+     * Inserts a drawable with today's day into the today's icon in the option menu
+     * @param icon - today's icon from the options menu
+     */
+    @JvmStatic fun setTodayIcon(icon: LayerDrawable, c: Context?, timezone: String?) {
+        val today: DayOfMonthDrawable
+
+        // Reuse current drawable if possible
+        val currentDrawable: Drawable? = icon.findDrawableByLayerId(R.id.today_icon_day)
+        if (currentDrawable != null && currentDrawable is DayOfMonthDrawable) {
+            today = currentDrawable as DayOfMonthDrawable
+        } else {
+            today = DayOfMonthDrawable(c as Context)
+        }
+        // Set the day and update the icon
+        val now = Time(timezone)
+        now.setToNow()
+        now.normalize(false)
+        today.setDayOfMonth(now.monthDay)
+        icon.mutate()
+        icon.setDrawableByLayerId(R.id.today_icon_day, today)
+    }
+
+    /**
+     * Get a list of quick responses used for emailing guests from the
+     * SharedPreferences. If not are found, get the hard coded ones that shipped
+     * with the app
+     *
+     * @param context
+     * @return a list of quick responses.
+     */
+    fun getQuickResponses(context: Context): Array<String> {
+        var s = getSharedPreference(context, KEY_QUICK_RESPONSES, null as Array<String>?)
+        if (s == null) {
+            s = context.getResources().getStringArray(R.array.quick_response_defaults)
+        }
+        return s
+    }
+
+    /**
+     * Return the app version code.
+     */
+    fun getVersionCode(context: Context): String? {
+        if (sVersion == null) {
+            try {
+                sVersion = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0
+                ).versionName
+            } catch (e: PackageManager.NameNotFoundException) {
+                // Can't find version; just leave it blank.
+                Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName)
+            }
+        }
+        return sVersion
+    }
+
+    // A single strand represents one color of events. Events are divided up by
+    // color to make them convenient to draw. The black strand is special in
+    // that it holds conflicting events as well as color settings for allday on
+    // each day.
+    class DNAStrand {
+        @JvmField var points: FloatArray? = null
+        @JvmField var allDays: IntArray? = null // color for the allday, 0 means no event
+        @JvmField var position = 0
+        @JvmField var color = 0
+        @JvmField var count = 0
+    }
+
+    // A segment is a single continuous length of time occupied by a single
+    // color. Segments should never span multiple days.
+    private class DNASegment {
+        var startMinute = 0 // in minutes since the start of the week =
+        var endMinute = 0
+        var color = 0 // Calendar color or black for conflicts =
+        var day = 0 // quick reference to the day this segment is on =
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlarmManagerInterface.java b/src/com/android/calendar/alerts/AlarmManagerInterface.kt
similarity index 71%
rename from src/com/android/calendar/alerts/AlarmManagerInterface.java
rename to src/com/android/calendar/alerts/AlarmManagerInterface.kt
index 3c66434..be9d86f 100644
--- a/src/com/android/calendar/alerts/AlarmManagerInterface.java
+++ b/src/com/android/calendar/alerts/AlarmManagerInterface.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,14 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.calendar.alerts
 
-package com.android.calendar.alerts;
-
-import android.app.PendingIntent;
+import android.app.PendingIntent
 
 /**
  * AlarmManager abstracted to an interface for testability.
  */
-public interface AlarmManagerInterface {
-    public void set(int type, long triggerAtMillis, PendingIntent operation);
-}
+interface AlarmManagerInterface {
+    operator fun set(type: Int, triggerAtMillis: Long, operation: PendingIntent?)
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.java b/src/com/android/calendar/alerts/AlarmScheduler.java
deleted file mode 100644
index 9782822..0000000
--- a/src/com/android/calendar/alerts/AlarmScheduler.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.alerts;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Instances;
-import android.provider.CalendarContract.Reminders;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events
- * and reminders tables for the next upcoming alert.
- */
-public class AlarmScheduler {
-    private static final String TAG = "AlarmScheduler";
-
-    private static final String INSTANCES_WHERE = Events.VISIBLE + "=? AND "
-            + Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND "
-            + Events.ALL_DAY + "=?";
-    static final String[] INSTANCES_PROJECTION = new String[] {
-        Instances.EVENT_ID,
-        Instances.BEGIN,
-        Instances.ALL_DAY,
-    };
-    private static final int INSTANCES_INDEX_EVENTID = 0;
-    private static final int INSTANCES_INDEX_BEGIN = 1;
-    private static final int INSTANCES_INDEX_ALL_DAY = 2;
-
-    private static final String REMINDERS_WHERE = Reminders.METHOD + "=1 AND "
-            + Reminders.EVENT_ID + " IN ";
-    static final String[] REMINDERS_PROJECTION = new String[] {
-        Reminders.EVENT_ID,
-        Reminders.MINUTES,
-        Reminders.METHOD,
-    };
-    private static final int REMINDERS_INDEX_EVENT_ID = 0;
-    private static final int REMINDERS_INDEX_MINUTES = 1;
-    private static final int REMINDERS_INDEX_METHOD = 2;
-
-    // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons:
-    // (1) so that the concurrent reminder broadcast from the provider doesn't result
-    // in a double ring, and (2) some OEMs modified the provider to not add an alert to
-    // the CalendarAlerts table until the alert time, so for the unbundled app's
-    // notifications to work on these devices, a delay ensures that AlertService won't
-    // read from the CalendarAlerts table until the alert is present.
-    static final int ALARM_DELAY_MS = 1000;
-
-    // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...".  This
-    // sets the max # of events in the query before batching into multiple queries, to
-    // limit the SQL query length.
-    private static final int REMINDER_QUERY_BATCH_SIZE = 50;
-
-    // We really need to query for reminder times that fall in some interval, but
-    // the Reminders table only stores the reminder interval (10min, 15min, etc), and
-    // we cannot do the join with the Events table to calculate the actual alert time
-    // from outside of the provider.  So the best we can do for now consider events
-    // whose start times begin within some interval (ie. 1 week out).  This means
-    // reminders which are configured for more than 1 week out won't fire on time.  We
-    // can minimize this to being only 1 day late by putting a 1 day max on the alarm time.
-    private static final long EVENT_LOOKAHEAD_WINDOW_MS = DateUtils.WEEK_IN_MILLIS;
-    private static final long MAX_ALARM_ELAPSED_MS = DateUtils.DAY_IN_MILLIS;
-
-    /**
-     * Schedules the nearest upcoming alarm, to refresh notifications.
-     *
-     * This is historically done in the provider but we dupe this here so the unbundled
-     * app will work on devices that have modified this portion of the provider.  This
-     * has the limitation of querying events within some interval from now (ie. looks at
-     * reminders for all events occurring in the next week).  This means for example,
-     * a 2 week notification will not fire on time.
-     */
-    public static void scheduleNextAlarm(Context context) {
-        scheduleNextAlarm(context, AlertUtils.createAlarmManager(context),
-                REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis());
-    }
-
-    // VisibleForTesting
-    static void scheduleNextAlarm(Context context, AlarmManagerInterface alarmManager,
-            int batchSize, long currentMillis) {
-        Cursor instancesCursor = null;
-        try {
-            instancesCursor = queryUpcomingEvents(context, context.getContentResolver(),
-                    currentMillis);
-            if (instancesCursor != null) {
-                queryNextReminderAndSchedule(instancesCursor, context,
-                        context.getContentResolver(), alarmManager, batchSize, currentMillis);
-            }
-        } finally {
-            if (instancesCursor != null) {
-                instancesCursor.close();
-            }
-        }
-    }
-
-    /**
-     * Queries events starting within a fixed interval from now.
-     */
-    private static Cursor queryUpcomingEvents(Context context, ContentResolver contentResolver,
-            long currentMillis) {
-        Time time = new Time();
-        time.normalize(false);
-        long localOffset = time.gmtoff * 1000;
-        final long localStartMin = currentMillis;
-        final long localStartMax = localStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
-        final long utcStartMin = localStartMin - localOffset;
-        final long utcStartMax = utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
-
-        // Expand Instances table range by a day on either end to account for
-        // all-day events.
-        Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon();
-        ContentUris.appendId(uriBuilder, localStartMin - DateUtils.DAY_IN_MILLIS);
-        ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS);
-
-        // Build query for all events starting within the fixed interval.
-        StringBuilder queryBuilder = new StringBuilder();
-        queryBuilder.append("(");
-        queryBuilder.append(INSTANCES_WHERE);
-        queryBuilder.append(") OR (");
-        queryBuilder.append(INSTANCES_WHERE);
-        queryBuilder.append(")");
-        String[] queryArgs = new String[] {
-                // allday selection
-                "1",                           /* visible = ? */
-                String.valueOf(utcStartMin),   /* begin >= ? */
-                String.valueOf(utcStartMax),   /* begin <= ? */
-                "1",                           /* allDay = ? */
-
-                // non-allday selection
-                "1",                           /* visible = ? */
-                String.valueOf(localStartMin), /* begin >= ? */
-                String.valueOf(localStartMax), /* begin <= ? */
-                "0"                            /* allDay = ? */
-        };
-
-        Cursor cursor = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION,
-                queryBuilder.toString(), queryArgs, null);
-        return cursor;
-    }
-
-    /**
-     * Queries for all the reminders of the events in the instancesCursor, and schedules
-     * the alarm for the next upcoming reminder.
-     */
-    private static void queryNextReminderAndSchedule(Cursor instancesCursor, Context context,
-            ContentResolver contentResolver, AlarmManagerInterface alarmManager,
-            int batchSize, long currentMillis) {
-        if (AlertService.DEBUG) {
-            int eventCount = instancesCursor.getCount();
-            if (eventCount == 0) {
-                Log.d(TAG, "No events found starting within 1 week.");
-            } else {
-                Log.d(TAG, "Query result count for events starting within 1 week: " + eventCount);
-            }
-        }
-
-        // Put query results of all events starting within some interval into map of event ID to
-        // local start time.
-        Map<Integer, List<Long>> eventMap = new HashMap<Integer, List<Long>>();
-        Time timeObj = new Time();
-        long nextAlarmTime = Long.MAX_VALUE;
-        int nextAlarmEventId = 0;
-        instancesCursor.moveToPosition(-1);
-        while (!instancesCursor.isAfterLast()) {
-            int index = 0;
-            eventMap.clear();
-            StringBuilder eventIdsForQuery = new StringBuilder();
-            eventIdsForQuery.append('(');
-            while (index++ < batchSize && instancesCursor.moveToNext()) {
-                int eventId = instancesCursor.getInt(INSTANCES_INDEX_EVENTID);
-                long begin = instancesCursor.getLong(INSTANCES_INDEX_BEGIN);
-                boolean allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0;
-                long localStartTime;
-                if (allday) {
-                    // Adjust allday to local time.
-                    localStartTime = Utils.convertAlldayUtcToLocal(timeObj, begin,
-                            Time.getCurrentTimezone());
-                } else {
-                    localStartTime = begin;
-                }
-                List<Long> startTimes = eventMap.get(eventId);
-                if (startTimes == null) {
-                    startTimes = new ArrayList<Long>();
-                    eventMap.put(eventId, startTimes);
-                    eventIdsForQuery.append(eventId);
-                    eventIdsForQuery.append(",");
-                }
-                startTimes.add(localStartTime);
-
-                // Log for debugging.
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    timeObj.set(localStartTime);
-                    StringBuilder msg = new StringBuilder();
-                    msg.append("Events cursor result -- eventId:").append(eventId);
-                    msg.append(", allDay:").append(allday);
-                    msg.append(", start:").append(localStartTime);
-                    msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")");
-                    Log.d(TAG, msg.toString());
-                }
-            }
-            if (eventIdsForQuery.charAt(eventIdsForQuery.length() - 1) == ',') {
-                eventIdsForQuery.deleteCharAt(eventIdsForQuery.length() - 1);
-            }
-            eventIdsForQuery.append(')');
-
-            // Query the reminders table for the events found.
-            Cursor cursor = null;
-            try {
-                cursor = contentResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
-                        REMINDERS_WHERE + eventIdsForQuery, null, null);
-
-                // Process the reminders query results to find the next reminder time.
-                cursor.moveToPosition(-1);
-                while (cursor.moveToNext()) {
-                    int eventId = cursor.getInt(REMINDERS_INDEX_EVENT_ID);
-                    int reminderMinutes = cursor.getInt(REMINDERS_INDEX_MINUTES);
-                    List<Long> startTimes = eventMap.get(eventId);
-                    if (startTimes != null) {
-                        for (Long startTime : startTimes) {
-                            long alarmTime = startTime -
-                                    reminderMinutes * DateUtils.MINUTE_IN_MILLIS;
-                            if (alarmTime > currentMillis && alarmTime < nextAlarmTime) {
-                                nextAlarmTime = alarmTime;
-                                nextAlarmEventId = eventId;
-                            }
-
-                            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                                timeObj.set(alarmTime);
-                                StringBuilder msg = new StringBuilder();
-                                msg.append("Reminders cursor result -- eventId:").append(eventId);
-                                msg.append(", startTime:").append(startTime);
-                                msg.append(", minutes:").append(reminderMinutes);
-                                msg.append(", alarmTime:").append(alarmTime);
-                                msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P"))
-                                        .append(")");
-                                Log.d(TAG, msg.toString());
-                            }
-                        }
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-
-        // Schedule the alarm for the next reminder time.
-        if (nextAlarmTime < Long.MAX_VALUE) {
-            scheduleAlarm(context, nextAlarmEventId, nextAlarmTime, currentMillis, alarmManager);
-        }
-    }
-
-    /**
-     * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified
-     * alarm time with a slight delay (to account for the possible duplicate broadcast
-     * from the provider).
-     */
-    private static void scheduleAlarm(Context context, long eventId, long alarmTime,
-            long currentMillis, AlarmManagerInterface alarmManager) {
-        // Max out the alarm time to 1 day out, so an alert for an event far in the future
-        // (not present in our event query results for a limited range) can only be at
-        // most 1 day late.
-        long maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS;
-        if (alarmTime > maxAlarmTime) {
-            alarmTime = maxAlarmTime;
-        }
-
-        // Add a slight delay (see comments on the member var).
-        alarmTime += ALARM_DELAY_MS;
-
-        if (AlertService.DEBUG) {
-            Time time = new Time();
-            time.set(alarmTime);
-            String schedTime = time.format("%a, %b %d, %Y %I:%M%P");
-            Log.d(TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId
-                    + " at " + alarmTime + " (" + schedTime + ")");
-        }
-
-        // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager.  The extra is
-        // only used by AlertService for logging.  It is ignored by Intent.filterEquals,
-        // so this scheduling will still overwrite the alarm that was previously pending.
-        // Note that the 'setClass' is required, because otherwise it seems the broadcast
-        // can be eaten by other apps and we somehow may never receive it.
-        Intent intent = new Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION);
-        intent.setClass(context, AlertReceiver.class);
-        intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime);
-        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
-        alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
-    }
-}
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.kt b/src/com/android/calendar/alerts/AlarmScheduler.kt
new file mode 100644
index 0000000..c93bbb0
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlarmScheduler.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.alerts
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.database.Cursor
+import android.net.Uri
+import android.provider.CalendarContract
+import android.provider.CalendarContract.Events
+import android.provider.CalendarContract.Instances
+import android.provider.CalendarContract.Reminders
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import com.android.calendar.Utils
+import java.util.HashMap
+import java.util.List
+
+/**
+ * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events
+ * and reminders tables for the next upcoming alert.
+ */
+object AlarmScheduler {
+    private const val TAG = "AlarmScheduler"
+    private val INSTANCES_WHERE: String = (Events.VISIBLE.toString() + "=? AND " +
+        Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND " +
+        Events.ALL_DAY + "=?")
+    val INSTANCES_PROJECTION = arrayOf<String>(
+        Instances.EVENT_ID,
+        Instances.BEGIN,
+        Instances.ALL_DAY
+    )
+    private const val INSTANCES_INDEX_EVENTID = 0
+    private const val INSTANCES_INDEX_BEGIN = 1
+    private const val INSTANCES_INDEX_ALL_DAY = 2
+    private val REMINDERS_WHERE: String = (Reminders.METHOD.toString() + "=1 AND " +
+        Reminders.EVENT_ID + " IN ")
+    val REMINDERS_PROJECTION = arrayOf<String>(
+        Reminders.EVENT_ID,
+        Reminders.MINUTES,
+        Reminders.METHOD
+    )
+    private const val REMINDERS_INDEX_EVENT_ID = 0
+    private const val REMINDERS_INDEX_MINUTES = 1
+    private const val REMINDERS_INDEX_METHOD = 2
+
+    // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons:
+    // (1) so that the concurrent reminder broadcast from the provider doesn't result
+    // in a double ring, and (2) some OEMs modified the provider to not add an alert to
+    // the CalendarAlerts table until the alert time, so for the unbundled app's
+    // notifications to work on these devices, a delay ensures that AlertService won't
+    // read from the CalendarAlerts table until the alert is present.
+    const val ALARM_DELAY_MS = 1000
+
+    // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...".  This
+    // sets the max # of events in the query before batching into multiple queries, to
+    // limit the SQL query length.
+    private const val REMINDER_QUERY_BATCH_SIZE = 50
+
+    // We really need to query for reminder times that fall in some interval, but
+    // the Reminders table only stores the reminder interval (10min, 15min, etc), and
+    // we cannot do the join with the Events table to calculate the actual alert time
+    // from outside of the provider.  So the best we can do for now consider events
+    // whose start times begin within some interval (ie. 1 week out).  This means
+    // reminders which are configured for more than 1 week out won't fire on time.  We
+    // can minimize this to being only 1 day late by putting a 1 day max on the alarm time.
+    private val EVENT_LOOKAHEAD_WINDOW_MS: Long = DateUtils.WEEK_IN_MILLIS
+    private val MAX_ALARM_ELAPSED_MS: Long = DateUtils.DAY_IN_MILLIS
+
+    /**
+     * Schedules the nearest upcoming alarm, to refresh notifications.
+     *
+     * This is historically done in the provider but we dupe this here so the unbundled
+     * app will work on devices that have modified this portion of the provider.  This
+     * has the limitation of querying events within some interval from now (ie. looks at
+     * reminders for all events occurring in the next week).  This means for example,
+     * a 2 week notification will not fire on time.
+     */
+    @JvmStatic fun scheduleNextAlarm(context: Context) {
+        scheduleNextAlarm(
+            context, AlertUtils.createAlarmManager(context),
+            REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis()
+        )
+    }
+
+    // VisibleForTesting
+    @JvmStatic fun scheduleNextAlarm(
+        context: Context,
+        alarmManager: AlarmManagerInterface?,
+        batchSize: Int,
+        currentMillis: Long
+    ) {
+        var instancesCursor: Cursor? = null
+        try {
+            instancesCursor = queryUpcomingEvents(
+                context, context.getContentResolver(),
+                currentMillis
+            )
+            if (instancesCursor != null) {
+                queryNextReminderAndSchedule(
+                    instancesCursor,
+                    context,
+                    context.getContentResolver(),
+                    alarmManager as AlarmManagerInterface,
+                    batchSize,
+                    currentMillis
+                )
+            }
+        } finally {
+            if (instancesCursor != null) {
+                instancesCursor.close()
+            }
+        }
+    }
+
+    /**
+     * Queries events starting within a fixed interval from now.
+     */
+    @JvmStatic private fun queryUpcomingEvents(
+        context: Context,
+        contentResolver: ContentResolver,
+        currentMillis: Long
+    ): Cursor? {
+        val time = Time()
+        time.normalize(false)
+        val localOffset: Long = time.gmtoff * 1000
+        val localStartMax =
+            currentMillis + EVENT_LOOKAHEAD_WINDOW_MS
+        val utcStartMin = currentMillis - localOffset
+        val utcStartMax =
+            utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS
+
+        // Expand Instances table range by a day on either end to account for
+        // all-day events.
+        val uriBuilder: Uri.Builder = Instances.CONTENT_URI.buildUpon()
+        ContentUris.appendId(uriBuilder, currentMillis - DateUtils.DAY_IN_MILLIS)
+        ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS)
+
+        // Build query for all events starting within the fixed interval.
+        val queryBuilder = StringBuilder()
+        queryBuilder.append("(")
+        queryBuilder.append(INSTANCES_WHERE)
+        queryBuilder.append(") OR (")
+        queryBuilder.append(INSTANCES_WHERE)
+        queryBuilder.append(")")
+        val queryArgs = arrayOf(
+            // allday selection
+            "1", /* visible = ? */
+            utcStartMin.toString(),  /* begin >= ? */
+            utcStartMax.toString(),  /* begin <= ? */
+            "1", /* allDay = ? */ // non-allday selection
+            "1", /* visible = ? */
+            currentMillis.toString(),  /* begin >= ? */
+            localStartMax.toString(),  /* begin <= ? */
+            "0" /* allDay = ? */
+        )
+
+        val cursor: Cursor? = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION,
+        queryBuilder.toString(), queryArgs, null)
+        return cursor
+    }
+
+    /**
+     * Queries for all the reminders of the events in the instancesCursor, and schedules
+     * the alarm for the next upcoming reminder.
+     */
+    @JvmStatic private fun queryNextReminderAndSchedule(
+        instancesCursor: Cursor,
+        context: Context,
+        contentResolver: ContentResolver,
+        alarmManager: AlarmManagerInterface,
+        batchSize: Int,
+        currentMillis: Long
+    ) {
+        if (AlertService.DEBUG) {
+            val eventCount: Int = instancesCursor.getCount()
+            if (eventCount == 0) {
+                Log.d(TAG, "No events found starting within 1 week.")
+            } else {
+                Log.d(TAG, "Query result count for events starting within 1 week: $eventCount")
+            }
+        }
+
+        // Put query results of all events starting within some interval into map of event ID to
+        // local start time.
+        val eventMap: HashMap<Int?, List<Long>?> = HashMap<Int?, List<Long>?>()
+        val timeObj = Time()
+        var nextAlarmTime = Long.MAX_VALUE
+        var nextAlarmEventId = 0
+        instancesCursor.moveToPosition(-1)
+        while (!instancesCursor.isAfterLast()) {
+            var index = 0
+            eventMap.clear()
+            val eventIdsForQuery = StringBuilder()
+            eventIdsForQuery.append('(')
+            while (index++ < batchSize && instancesCursor.moveToNext()) {
+                val eventId: Int = instancesCursor.getInt(INSTANCES_INDEX_EVENTID)
+                val begin: Long = instancesCursor.getLong(INSTANCES_INDEX_BEGIN)
+                val allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0
+                var localStartTime: Long
+                localStartTime = if (allday) {
+                    // Adjust allday to local time.
+                    Utils.convertAlldayUtcToLocal(
+                        timeObj, begin,
+                        Time.getCurrentTimezone()
+                    )
+                } else {
+                    begin
+                }
+                var startTimes: List<Long>? = eventMap.get(eventId)
+                if (startTimes == null) {
+                    startTimes = mutableListOf<Long>() as List<Long>
+                    eventMap.put(eventId, startTimes)
+                    eventIdsForQuery.append(eventId)
+                    eventIdsForQuery.append(",")
+                }
+                startTimes.add(localStartTime)
+
+                // Log for debugging.
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    timeObj.set(localStartTime)
+                    val msg = StringBuilder()
+                    msg.append("Events cursor result -- eventId:").append(eventId)
+                    msg.append(", allDay:").append(allday)
+                    msg.append(", start:").append(localStartTime)
+                    msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")")
+                    Log.d(TAG, msg.toString())
+                }
+            }
+            if (eventIdsForQuery[eventIdsForQuery.length - 1] == ',') {
+                eventIdsForQuery.deleteCharAt(eventIdsForQuery.length - 1)
+            }
+            eventIdsForQuery.append(')')
+
+            // Query the reminders table for the events found.
+            var cursor: Cursor? = null
+            try {
+                cursor = contentResolver.query(
+                    Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+                    REMINDERS_WHERE + eventIdsForQuery, null, null
+                )
+
+                // Process the reminders query results to find the next reminder time.
+                cursor?.moveToPosition(-1)
+                while (cursor!!.moveToNext()) {
+                    val eventId: Int = cursor.getInt(REMINDERS_INDEX_EVENT_ID)
+                    val reminderMinutes: Int = cursor.getInt(REMINDERS_INDEX_MINUTES)
+                    val startTimes: List<Long>? = eventMap.get(eventId)
+                    if (startTimes != null) {
+                        for (startTime in startTimes) {
+                            val alarmTime: Long = startTime -
+                                reminderMinutes * DateUtils.MINUTE_IN_MILLIS
+                            if (alarmTime > currentMillis && alarmTime < nextAlarmTime) {
+                                nextAlarmTime = alarmTime
+                                nextAlarmEventId = eventId
+                            }
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                timeObj.set(alarmTime)
+                                val msg = StringBuilder()
+                                msg.append("Reminders cursor result -- eventId:").append(eventId)
+                                msg.append(", startTime:").append(startTime)
+                                msg.append(", minutes:").append(reminderMinutes)
+                                msg.append(", alarmTime:").append(alarmTime)
+                                msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P"))
+                                    .append(")")
+                                Log.d(TAG, msg.toString())
+                            }
+                        }
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close()
+                }
+            }
+        }
+
+        // Schedule the alarm for the next reminder time.
+        if (nextAlarmTime < Long.MAX_VALUE) {
+            scheduleAlarm(
+                context,
+                nextAlarmEventId.toLong(),
+                nextAlarmTime,
+                currentMillis,
+                alarmManager
+            )
+        }
+    }
+
+    /**
+     * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified
+     * alarm time with a slight delay (to account for the possible duplicate broadcast
+     * from the provider).
+     */
+    @JvmStatic private fun scheduleAlarm(
+        context: Context,
+        eventId: Long,
+        alarmTimeInput: Long,
+        currentMillis: Long,
+        alarmManager: AlarmManagerInterface
+    ) {
+        // Max out the alarm time to 1 day out, so an alert for an event far in the future
+        // (not present in our event query results for a limited range) can only be at
+        // most 1 day late.
+        var alarmTime = alarmTimeInput
+        val maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS
+        if (alarmTime > maxAlarmTime) {
+            alarmTime = maxAlarmTime
+        }
+
+        // Add a slight delay (see comments on the member var).
+        alarmTime += ALARM_DELAY_MS.toLong()
+        if (AlertService.DEBUG) {
+            val time = Time()
+            time.set(alarmTime)
+            val schedTime: String = time.format("%a, %b %d, %Y %I:%M%P")
+            Log.d(
+                TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId +
+                    " at " + alarmTime + " (" + schedTime + ")"
+            )
+        }
+
+        // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager.  The extra is
+        // only used by AlertService for logging.  It is ignored by Intent.filterEquals,
+        // so this scheduling will still overwrite the alarm that was previously pending.
+        // Note that the 'setClass' is required, because otherwise it seems the broadcast
+        // can be eaten by other apps and we somehow may never receive it.
+        val intent = Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION)
+        intent.setClass(context, AlertReceiver::class.java)
+        intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime)
+        val pi: PendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
+        alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java
deleted file mode 100644
index ee0c508..0000000
--- a/src/com/android/calendar/alerts/AlertReceiver.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.alerts;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.telephony.TelephonyManager;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.TextAppearanceSpan;
-import android.text.style.URLSpan;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-import com.android.calendar.alerts.AlertService.NotificationWrapper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Receives android.intent.action.EVENT_REMINDER intents and handles
- * event reminders.  The intent URI specifies an alert id in the
- * CalendarAlerts database table.  This class also receives the
- * BOOT_COMPLETED intent so that it can add a status bar notification
- * if there are Calendar event alarms that have not been dismissed.
- * It also receives the TIME_CHANGED action so that it can fire off
- * snoozed alarms that have become ready.  The real work is done in
- * the AlertService class.
- *
- * To trigger this code after pushing the apk to device:
- * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
- *    -n "com.android.calendar/.alerts.AlertReceiver"
- */
-public class AlertReceiver extends BroadcastReceiver {
-    private static final String TAG = "AlertReceiver";
-
-    // The broadcast for notification refreshes scheduled by the app. This is to
-    // distinguish the EVENT_REMINDER broadcast sent by the provider.
-    public static final String EVENT_REMINDER_APP_ACTION =
-            "com.android.calendar.EVENT_REMINDER_APP";
-
-    public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders";
-
-    @Override
-    public void onReceive(final Context context, final Intent intent) {
-        if (AlertService.DEBUG) {
-            Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
-        }
-    }
-
-    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();
-    }
-}
diff --git a/src/com/android/calendar/alerts/AlertReceiver.kt b/src/com/android/calendar/alerts/AlertReceiver.kt
new file mode 100644
index 0000000..21afa90
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertReceiver.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.alerts
+
+import android.app.Notification
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.util.Log
+import com.android.calendar.R
+import com.android.calendar.Utils
+import com.android.calendar.alerts.AlertService.NotificationWrapper
+
+/**
+ * Receives android.intent.action.EVENT_REMINDER intents and handles
+ * event reminders.  The intent URI specifies an alert id in the
+ * CalendarAlerts database table.  This class also receives the
+ * BOOT_COMPLETED intent so that it can add a status bar notification
+ * if there are Calendar event alarms that have not been dismissed.
+ * It also receives the TIME_CHANGED action so that it can fire off
+ * snoozed alarms that have become ready.  The real work is done in
+ * the AlertService class.
+ *
+ * To trigger this code after pushing the apk to device:
+ * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
+ * -n "com.android.calendar/.alerts.AlertReceiver"
+ */
+class AlertReceiver : BroadcastReceiver() {
+    @Override
+    override fun onReceive(context: Context, intent: Intent) {
+        if (AlertService.DEBUG) {
+            Log.d(TAG, "onReceive: a=" + intent.getAction().toString() + " " + intent.toString())
+        }
+    }
+
+    companion object {
+        private const val TAG = "AlertReceiver"
+
+        // The broadcast for notification refreshes scheduled by the app. This is to
+        // distinguish the EVENT_REMINDER broadcast sent by the provider.
+        const val EVENT_REMINDER_APP_ACTION = "com.android.calendar.EVENT_REMINDER_APP"
+        const val ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"
+        fun makeBasicNotification(
+            context: Context,
+            title: String,
+            summaryText: String,
+            startMillis: Long,
+            endMillis: Long,
+            eventId: Long,
+            notificationId: Int,
+            doPopup: Boolean,
+            priority: Int
+        ): NotificationWrapper {
+            val n: Notification = buildBasicNotification(
+                Notification.Builder(context),
+                context,
+                title,
+                summaryText,
+                startMillis,
+                endMillis,
+                eventId,
+                notificationId,
+                doPopup,
+                priority, false
+            )
+            return NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup)
+        }
+
+        private fun buildBasicNotification(
+            notificationBuilder: Notification.Builder,
+            context: Context,
+            title: String,
+            summaryText: String,
+            startMillis: Long,
+            endMillis: Long,
+            eventId: Long,
+            notificationId: Int,
+            doPopup: Boolean,
+            priority: Int,
+            addActionButtons: Boolean
+        ): Notification {
+            var title: String? = title
+            val resources: Resources = context.getResources()
+            if (title == null || title.length == 0) {
+                title = resources.getString(R.string.no_title_label)
+            }
+
+            // Create the base notification.
+            notificationBuilder.setContentTitle(title)
+            notificationBuilder.setContentText(summaryText)
+            notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar)
+            if (Utils.isJellybeanOrLater()) {
+                // Turn off timestamp.
+                notificationBuilder.setWhen(0)
+
+                // Should be one of the values in Notification
+                // (ie. Notification.PRIORITY_HIGH, etc).
+                // A higher priority will encourage notification manager to expand it.
+                notificationBuilder.setPriority(priority)
+            }
+            return notificationBuilder.getNotification()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java
deleted file mode 100644
index d2c994d..0000000
--- a/src/com/android/calendar/alerts/AlertService.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.alerts;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * This service is used to handle calendar event reminders.
- */
-public class AlertService extends Service {
-    static final boolean DEBUG = true;
-    private static final String TAG = "AlertService";
-
-    private volatile Looper mServiceLooper;
-
-    static final String[] ALERT_PROJECTION = new String[] {
-        CalendarAlerts._ID,                     // 0
-        CalendarAlerts.EVENT_ID,                // 1
-        CalendarAlerts.STATE,                   // 2
-        CalendarAlerts.TITLE,                   // 3
-        CalendarAlerts.EVENT_LOCATION,          // 4
-        CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
-        CalendarAlerts.ALL_DAY,                 // 6
-        CalendarAlerts.ALARM_TIME,              // 7
-        CalendarAlerts.MINUTES,                 // 8
-        CalendarAlerts.BEGIN,                   // 9
-        CalendarAlerts.END,                     // 10
-        CalendarAlerts.DESCRIPTION,             // 11
-    };
-
-    private static final int ALERT_INDEX_ID = 0;
-    private static final int ALERT_INDEX_EVENT_ID = 1;
-    private static final int ALERT_INDEX_STATE = 2;
-    private static final int ALERT_INDEX_TITLE = 3;
-    private static final int ALERT_INDEX_EVENT_LOCATION = 4;
-    private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
-    private static final int ALERT_INDEX_ALL_DAY = 6;
-    private static final int ALERT_INDEX_ALARM_TIME = 7;
-    private static final int ALERT_INDEX_MINUTES = 8;
-    private static final int ALERT_INDEX_BEGIN = 9;
-    private static final int ALERT_INDEX_END = 10;
-    private static final int ALERT_INDEX_DESCRIPTION = 11;
-
-    private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
-            + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
-
-    private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
-            Integer.toString(CalendarAlerts.STATE_FIRED),
-            Integer.toString(CalendarAlerts.STATE_SCHEDULED)
-    };
-
-    private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
-
-    private static final String DISMISS_OLD_SELECTION = CalendarAlerts.END + "<? AND "
-            + CalendarAlerts.STATE + "=?";
-
-    private static final int MINUTE_MS = 60 * 1000;
-
-    // The grace period before changing a notification's priority bucket.
-    private static final int MIN_DEPRIORITIZE_GRACE_PERIOD_MS = 15 * MINUTE_MS;
-
-    // Hard limit to the number of notifications displayed.
-    public static final int MAX_NOTIFICATIONS = 20;
-
-    // Added wrapper for testing
-    public static class NotificationWrapper {
-        Notification mNotification;
-        long mEventId;
-        long mBegin;
-        long mEnd;
-        ArrayList<NotificationWrapper> mNw;
-
-        public NotificationWrapper(Notification n, int notificationId, long eventId,
-                long startMillis, long endMillis, boolean doPopup) {
-            mNotification = n;
-            mEventId = eventId;
-            mBegin = startMillis;
-            mEnd = endMillis;
-
-            // popup?
-            // notification id?
-        }
-
-        public NotificationWrapper(Notification n) {
-            mNotification = n;
-        }
-
-        public void add(NotificationWrapper nw) {
-            if (mNw == null) {
-                mNw = new ArrayList<NotificationWrapper>();
-            }
-            mNw.add(nw);
-        }
-    }
-
-    // Added wrapper for testing
-    public static class NotificationMgrWrapper extends NotificationMgr {
-        NotificationManager mNm;
-
-        public NotificationMgrWrapper(NotificationManager nm) {
-            mNm = nm;
-        }
-
-        @Override
-        public void cancel(int id) {
-            mNm.cancel(id);
-        }
-
-        @Override
-        public void notify(int id, NotificationWrapper nw) {
-            mNm.notify(id, nw.mNotification);
-        }
-    }
-
-    static class NotificationInfo {
-        String eventName;
-        String location;
-        String description;
-        long startMillis;
-        long endMillis;
-        long eventId;
-        boolean allDay;
-        boolean newAlert;
-
-        NotificationInfo(String eventName, String location, String description, long startMillis,
-                long endMillis, long eventId, boolean allDay, boolean newAlert) {
-            this.eventName = eventName;
-            this.location = location;
-            this.description = description;
-            this.startMillis = startMillis;
-            this.endMillis = endMillis;
-            this.eventId = eventId;
-            this.newAlert = newAlert;
-            this.allDay = allDay;
-        }
-    }
-
-    @Override
-    public void onCreate() {
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        return START_REDELIVER_INTENT;
-    }
-
-    @Override
-    public void onDestroy() {
-        mServiceLooper.quit();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-}
diff --git a/src/com/android/calendar/alerts/AlertService.kt b/src/com/android/calendar/alerts/AlertService.kt
new file mode 100644
index 0000000..bc1b4e0
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertService.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.alerts
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.os.Looper
+import android.provider.CalendarContract.CalendarAlerts
+import java.util.ArrayList
+
+/**
+ * This service is used to handle calendar event reminders.
+ */
+class AlertService : Service() {
+    @Volatile
+    private var mServiceLooper: Looper? = null
+
+    // Added wrapper for testing
+    class NotificationWrapper {
+        var mNotification: Notification
+        var mEventId: Long = 0
+        var mBegin: Long = 0
+        var mEnd: Long = 0
+        var mNw: ArrayList<NotificationWrapper>? = null
+
+        constructor(
+            n: Notification,
+            notificationId: Int,
+            eventId: Long,
+            startMillis: Long,
+            endMillis: Long,
+            doPopup: Boolean
+        ) {
+            mNotification = n
+            mEventId = eventId
+            mBegin = startMillis
+            mEnd = endMillis
+
+            // popup?
+            // notification id?
+        }
+
+        constructor(n: Notification) {
+            mNotification = n
+        }
+
+        fun add(nw: NotificationWrapper?) {
+            val temp = mNw
+            if (temp == null) {
+                mNw = ArrayList<NotificationWrapper>()
+            }
+            mNw?.add(nw as AlertService.NotificationWrapper)
+        }
+    }
+
+    // Added wrapper for testing
+    class NotificationMgrWrapper(nm: NotificationManager) : NotificationMgr() {
+        var mNm: NotificationManager
+        @Override
+        override fun cancel(id: Int) {
+            mNm.cancel(id)
+        }
+
+        @Override
+        override fun notify(id: Int, nw: NotificationWrapper?) {
+            mNm.notify(id, nw?.mNotification)
+        }
+
+        init {
+            mNm = nm
+        }
+    }
+
+    internal class NotificationInfo(
+        var eventName: String,
+        var location: String,
+        var description: String,
+        var startMillis: Long,
+        var endMillis: Long,
+        var eventId: Long,
+        var allDay: Boolean,
+        var newAlert: Boolean
+    )
+
+    @Override
+    override fun onCreate() {
+    }
+
+    @Override
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        return START_REDELIVER_INTENT
+    }
+
+    @Override
+    override fun onDestroy() {
+        mServiceLooper?.quit()
+    }
+
+    @Override
+    override fun onBind(intent: Intent?): IBinder? {
+        return null
+    }
+
+    companion object {
+        const val DEBUG = true
+        private const val TAG = "AlertService"
+        val ALERT_PROJECTION = arrayOf<String>(
+            CalendarAlerts._ID, // 0
+            CalendarAlerts.EVENT_ID, // 1
+            CalendarAlerts.STATE, // 2
+            CalendarAlerts.TITLE, // 3
+            CalendarAlerts.EVENT_LOCATION, // 4
+            CalendarAlerts.SELF_ATTENDEE_STATUS, // 5
+            CalendarAlerts.ALL_DAY, // 6
+            CalendarAlerts.ALARM_TIME, // 7
+            CalendarAlerts.MINUTES, // 8
+            CalendarAlerts.BEGIN, // 9
+            CalendarAlerts.END, // 10
+            CalendarAlerts.DESCRIPTION
+        )
+        private const val ALERT_INDEX_ID = 0
+        private const val ALERT_INDEX_EVENT_ID = 1
+        private const val ALERT_INDEX_STATE = 2
+        private const val ALERT_INDEX_TITLE = 3
+        private const val ALERT_INDEX_EVENT_LOCATION = 4
+        private const val ALERT_INDEX_SELF_ATTENDEE_STATUS = 5
+        private const val ALERT_INDEX_ALL_DAY = 6
+        private const val ALERT_INDEX_ALARM_TIME = 7
+        private const val ALERT_INDEX_MINUTES = 8
+        private const val ALERT_INDEX_BEGIN = 9
+        private const val ALERT_INDEX_END = 10
+        private const val ALERT_INDEX_DESCRIPTION = 11
+        private val ACTIVE_ALERTS_SELECTION = ("(" + CalendarAlerts.STATE.toString() + "=? OR " +
+            CalendarAlerts.STATE.toString() + "=?) AND " +
+            CalendarAlerts.ALARM_TIME.toString() + "<=")
+        private val ACTIVE_ALERTS_SELECTION_ARGS = arrayOf<String>(
+            Integer.toString(CalendarAlerts.STATE_FIRED),
+            Integer.toString(CalendarAlerts.STATE_SCHEDULED)
+        )
+        private const val ACTIVE_ALERTS_SORT = "begin DESC, end DESC"
+        private val DISMISS_OLD_SELECTION: String = (CalendarAlerts.END.toString() + "<? AND " +
+            CalendarAlerts.STATE + "=?")
+        private const val MINUTE_MS = 60 * 1000
+
+        // The grace period before changing a notification's priority bucket.
+        private const val MIN_DEPRIORITIZE_GRACE_PERIOD_MS = 15 * MINUTE_MS
+
+        // Hard limit to the number of notifications displayed.
+        const val MAX_NOTIFICATIONS = 20
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/AlertUtils.java b/src/com/android/calendar/alerts/AlertUtils.java
deleted file mode 100644
index b9aaec2..0000000
--- a/src/com/android/calendar/alerts/AlertUtils.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.calendar.alerts;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-public class AlertUtils {
-    private static final String TAG = "AlertUtils";
-    static final boolean DEBUG = true;
-
-    public static final long SNOOZE_DELAY = 5 * 60 * 1000L;
-
-    // We use one notification id for the expired events notification.  All
-    // other notifications (the 'active' future/concurrent ones) use a unique ID.
-    public static final int EXPIRED_GROUP_NOTIFICATION_ID = 0;
-
-    public static final String EVENT_ID_KEY = "eventid";
-    public static final String EVENT_START_KEY = "eventstart";
-    public static final String EVENT_END_KEY = "eventend";
-    public static final String NOTIFICATION_ID_KEY = "notificationid";
-    public static final String EVENT_IDS_KEY = "eventids";
-    public static final String EVENT_STARTS_KEY = "starts";
-
-    // A flag for using local storage to save alert state instead of the alerts DB table.
-    // This allows the unbundled app to run alongside other calendar apps without eating
-    // alerts from other apps.
-    static boolean BYPASS_DB = true;
-
-    /**
-     * Creates an AlarmManagerInterface that wraps a real AlarmManager.  The alarm code
-     * was abstracted to an interface to make it testable.
-     */
-    public static AlarmManagerInterface createAlarmManager(Context context) {
-        final AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        return new AlarmManagerInterface() {
-            @Override
-            public void set(int type, long triggerAtMillis, PendingIntent operation) {
-                if (Utils.isKeyLimePieOrLater()) {
-                    mgr.setExact(type, triggerAtMillis, operation);
-                } else {
-                    mgr.set(type, triggerAtMillis, operation);
-                }
-            }
-        };
-    }
-
-    /**
-     * Schedules an alarm intent with the system AlarmManager that will notify
-     * listeners when a reminder should be fired. The provider will keep
-     * scheduled reminders up to date but apps may use this to implement snooze
-     * functionality without modifying the reminders table. Scheduled alarms
-     * will generate an intent using AlertReceiver.EVENT_REMINDER_APP_ACTION.
-     *
-     * @param context A context for referencing system resources
-     * @param manager The AlarmManager to use or null
-     * @param alarmTime The time to fire the intent in UTC millis since epoch
-     */
-    public static void scheduleAlarm(Context context, AlarmManagerInterface manager,
-            long alarmTime) {
-    }
-
-    public static Intent buildEventViewIntent(Context c, long eventId, long begin, long end) {
-        Intent i = new Intent(Intent.ACTION_VIEW);
-        Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
-        builder.appendEncodedPath("events/" + eventId);
-        i.setData(builder.build());
-        i.setClass(c, EventInfoActivity.class);
-        i.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin);
-        i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
-        return i;
-    }
-}
diff --git a/src/com/android/calendar/alerts/AlertUtils.kt b/src/com/android/calendar/alerts/AlertUtils.kt
new file mode 100644
index 0000000..18b7e7d
--- /dev/null
+++ b/src/com/android/calendar/alerts/AlertUtils.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.calendar.alerts
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.Utils
+
+object AlertUtils {
+    private const val TAG = "AlertUtils"
+    const val DEBUG = true
+    const val SNOOZE_DELAY = 5 * 60 * 1000L
+
+    // We use one notification id for the expired events notification.  All
+    // other notifications (the 'active' future/concurrent ones) use a unique ID.
+    const val EXPIRED_GROUP_NOTIFICATION_ID = 0
+    const val EVENT_ID_KEY = "eventid"
+    const val EVENT_START_KEY = "eventstart"
+    const val EVENT_END_KEY = "eventend"
+    const val NOTIFICATION_ID_KEY = "notificationid"
+    const val EVENT_IDS_KEY = "eventids"
+    const val EVENT_STARTS_KEY = "starts"
+
+    // A flag for using local storage to save alert state instead of the alerts DB table.
+    // This allows the unbundled app to run alongside other calendar apps without eating
+    // alerts from other apps.
+    var BYPASS_DB = true
+
+    /**
+     * Creates an AlarmManagerInterface that wraps a real AlarmManager.  The alarm code
+     * was abstracted to an interface to make it testable.
+     */
+    @JvmStatic fun createAlarmManager(context: Context): AlarmManagerInterface {
+        val mgr: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+        return object : AlarmManagerInterface {
+            override operator fun set(type: Int, triggerAtMillis: Long, operation: PendingIntent?) {
+                if (com.android.calendar.Utils.isKeyLimePieOrLater()) {
+                    mgr.setExact(type, triggerAtMillis, operation)
+                } else {
+                    mgr.set(type, triggerAtMillis, operation)
+                }
+            }
+        }
+    }
+
+    /**
+     * Schedules an alarm intent with the system AlarmManager that will notify
+     * listeners when a reminder should be fired. The provider will keep
+     * scheduled reminders up to date but apps may use this to implement snooze
+     * functionality without modifying the reminders table. Scheduled alarms
+     * will generate an intent using AlertReceiver.EVENT_REMINDER_APP_ACTION.
+     *
+     * @param context A context for referencing system resources
+     * @param manager The AlarmManager to use or null
+     * @param alarmTime The time to fire the intent in UTC millis since epoch
+     */
+    @JvmStatic fun scheduleAlarm(
+        context: Context?,
+        manager: AlarmManagerInterface?,
+        alarmTime: Long
+    ) {
+    }
+
+    @JvmStatic fun buildEventViewIntent(c: Context, eventId: Long, begin: Long, end: Long): Intent {
+        val i = Intent(Intent.ACTION_VIEW)
+        val builder: Uri.Builder = CalendarContract.CONTENT_URI.buildUpon()
+        builder.appendEncodedPath("events/$eventId")
+        i.setData(builder.build())
+        i.setClass(c, EventInfoActivity::class.java)
+        i.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin)
+        i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end)
+        return i
+    }
+}
diff --git a/src/com/android/calendar/alerts/DismissAlarmsService.java b/src/com/android/calendar/alerts/DismissAlarmsService.java
deleted file mode 100644
index 1ec3c22..0000000
--- a/src/com/android/calendar/alerts/DismissAlarmsService.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.calendar.alerts;
-
-import android.app.IntentService;
-import android.app.NotificationManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.IBinder;
-import android.provider.CalendarContract.CalendarAlerts;
-import androidx.core.app.TaskStackBuilder;
-
-import android.util.Log;
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.alerts.GlobalDismissManager.AlarmId;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Service for asynchronously marking fired alarms as dismissed.
- */
-public class DismissAlarmsService extends IntentService {
-    private static final String TAG = "DismissAlarmsService";
-    public static final String SHOW_ACTION = "com.android.calendar.SHOW";
-    public static final String DISMISS_ACTION = "com.android.calendar.DISMISS";
-
-    private static final String[] PROJECTION = new String[] {
-            CalendarAlerts.STATE,
-    };
-    private static final int COLUMN_INDEX_STATE = 0;
-
-    public DismissAlarmsService() {
-        super("DismissAlarmsService");
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public void onHandleIntent(Intent intent) {
-        if (AlertService.DEBUG) {
-            Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
-        }
-
-        long eventId = intent.getLongExtra(AlertUtils.EVENT_ID_KEY, -1);
-        long eventStart = intent.getLongExtra(AlertUtils.EVENT_START_KEY, -1);
-        long eventEnd = intent.getLongExtra(AlertUtils.EVENT_END_KEY, -1);
-        long[] eventIds = intent.getLongArrayExtra(AlertUtils.EVENT_IDS_KEY);
-        long[] eventStarts = intent.getLongArrayExtra(AlertUtils.EVENT_STARTS_KEY);
-        int notificationId = intent.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, -1);
-        List<AlarmId> alarmIds = new LinkedList<AlarmId>();
-
-        Uri uri = CalendarAlerts.CONTENT_URI;
-        String selection;
-
-        // Dismiss a specific fired alarm if id is present, otherwise, dismiss all alarms
-        if (eventId != -1) {
-            alarmIds.add(new AlarmId(eventId, eventStart));
-            selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED + " AND " +
-            CalendarAlerts.EVENT_ID + "=" + eventId;
-        } else if (eventIds != null && eventIds.length > 0 &&
-                eventStarts != null && eventIds.length == eventStarts.length) {
-            selection = buildMultipleEventsQuery(eventIds);
-            for (int i = 0; i < eventIds.length; i++) {
-                alarmIds.add(new AlarmId(eventIds[i], eventStarts[i]));
-            }
-        } else {
-            // NOTE: I don't believe that this ever happens.
-            selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED;
-        }
-
-        GlobalDismissManager.dismissGlobally(getApplicationContext(), alarmIds);
-
-        ContentResolver resolver = getContentResolver();
-        ContentValues values = new ContentValues();
-        values.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
-        resolver.update(uri, values, selection, null);
-
-        // Remove from notification bar.
-        if (notificationId != -1) {
-            NotificationManager nm =
-                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-            nm.cancel(notificationId);
-        }
-
-        if (SHOW_ACTION.equals(intent.getAction())) {
-            // Show event on Calendar app by building an intent and task stack to start
-            // EventInfoActivity with AllInOneActivity as the parent activity rooted to home.
-            Intent i = AlertUtils.buildEventViewIntent(this, eventId, eventStart, eventEnd);
-
-            TaskStackBuilder.create(this)
-                    .addParentStack(EventInfoActivity.class).addNextIntent(i).startActivities();
-        }
-    }
-
-    private String buildMultipleEventsQuery(long[] eventIds) {
-        StringBuilder selection = new StringBuilder();
-        selection.append(CalendarAlerts.STATE);
-        selection.append("=");
-        selection.append(CalendarAlerts.STATE_FIRED);
-        if (eventIds.length > 0) {
-            selection.append(" AND (");
-            selection.append(CalendarAlerts.EVENT_ID);
-            selection.append("=");
-            selection.append(eventIds[0]);
-            for (int i = 1; i < eventIds.length; i++) {
-                selection.append(" OR ");
-                selection.append(CalendarAlerts.EVENT_ID);
-                selection.append("=");
-                selection.append(eventIds[i]);
-            }
-            selection.append(")");
-        }
-        return selection.toString();
-    }
-}
diff --git a/src/com/android/calendar/alerts/DismissAlarmsService.kt b/src/com/android/calendar/alerts/DismissAlarmsService.kt
new file mode 100644
index 0000000..88683d3
--- /dev/null
+++ b/src/com/android/calendar/alerts/DismissAlarmsService.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.calendar.alerts
+
+import android.app.IntentService
+import android.app.NotificationManager
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.IBinder
+import android.provider.CalendarContract.CalendarAlerts
+import androidx.core.app.TaskStackBuilder
+import android.util.Log
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.alerts.GlobalDismissManager.AlarmId
+import java.util.LinkedList
+import java.util.List
+
+/**
+ * Service for asynchronously marking fired alarms as dismissed.
+ */
+class DismissAlarmsService : IntentService("DismissAlarmsService") {
+    @Override
+    override fun onBind(intent: Intent?): IBinder? {
+        return null
+    }
+
+    @Override
+    override fun onHandleIntent(intent: Intent?) {
+        if (AlertService.DEBUG) {
+            Log.d(TAG, "onReceive: a=" + intent?.getAction().toString() + " " + intent.toString())
+        }
+        val eventId = intent?.getLongExtra(AlertUtils.EVENT_ID_KEY, -1)
+        val eventStart = intent?.getLongExtra(AlertUtils.EVENT_START_KEY, -1)
+        val eventEnd = intent?.getLongExtra(AlertUtils.EVENT_END_KEY, -1)
+        val eventIds = intent?.getLongArrayExtra(AlertUtils.EVENT_IDS_KEY)
+        val eventStarts = intent?.getLongArrayExtra(AlertUtils.EVENT_STARTS_KEY)
+        val notificationId = intent?.getIntExtra(AlertUtils.NOTIFICATION_ID_KEY, -1)
+        val alarmIds = LinkedList<AlarmId>()
+        val uri: Uri = CalendarAlerts.CONTENT_URI
+        val selection: String
+
+        // Dismiss a specific fired alarm if id is present, otherwise, dismiss all alarms
+        if (eventId != -1L) {
+            alarmIds.add(AlarmId(eventId as Long, eventStart as Long))
+            selection =
+                CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED + " AND " +
+                    CalendarAlerts.EVENT_ID + "=" + eventId
+        } else if (eventIds != null && eventIds.size > 0 && eventStarts != null &&
+            eventIds.size == eventStarts.size) {
+            selection = buildMultipleEventsQuery(eventIds)
+            for (i in eventIds.indices) {
+                alarmIds.add(AlarmId(eventIds[i], eventStarts[i]))
+            }
+        } else {
+            // NOTE: I don't believe that this ever happens.
+            selection = CalendarAlerts.STATE.toString() + "=" + CalendarAlerts.STATE_FIRED
+        }
+        GlobalDismissManager.dismissGlobally(getApplicationContext(),
+            alarmIds as List<GlobalDismissManager.AlarmId>)
+        val resolver: ContentResolver = getContentResolver()
+        val values = ContentValues()
+        values.put(PROJECTION[COLUMN_INDEX_STATE], CalendarAlerts.STATE_DISMISSED)
+        resolver.update(uri, values, selection, null)
+
+        // Remove from notification bar.
+        if (notificationId != -1) {
+            val nm: NotificationManager =
+                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+            nm.cancel(notificationId as Int)
+        }
+        if (SHOW_ACTION.equals(intent?.getAction())) {
+            // Show event on Calendar app by building an intent and task stack to start
+            // EventInfoActivity with AllInOneActivity as the parent activity rooted to home.
+            val i: Intent = AlertUtils.buildEventViewIntent(this, eventId as Long,
+                                                            eventStart as Long, eventEnd as Long)
+            TaskStackBuilder.create(this)
+                .addParentStack(EventInfoActivity::class.java).addNextIntent(i).startActivities()
+        }
+    }
+
+    private fun buildMultipleEventsQuery(eventIds: LongArray): String {
+        val selection = StringBuilder()
+        selection.append(CalendarAlerts.STATE)
+        selection.append("=")
+        selection.append(CalendarAlerts.STATE_FIRED)
+        if (eventIds.size > 0) {
+            selection.append(" AND (")
+            selection.append(CalendarAlerts.EVENT_ID)
+            selection.append("=")
+            selection.append(eventIds[0])
+            for (i in 1 until eventIds.size) {
+                selection.append(" OR ")
+                selection.append(CalendarAlerts.EVENT_ID)
+                selection.append("=")
+                selection.append(eventIds[i])
+            }
+            selection.append(")")
+        }
+        return selection.toString()
+    }
+
+    companion object {
+        private const val TAG = "DismissAlarmsService"
+        const val SHOW_ACTION = "com.android.calendar.SHOW"
+        const val DISMISS_ACTION = "com.android.calendar.DISMISS"
+        private val PROJECTION = arrayOf<String>(
+            CalendarAlerts.STATE
+        )
+        private const val COLUMN_INDEX_STATE = 0
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.java b/src/com/android/calendar/alerts/GlobalDismissManager.java
deleted file mode 100644
index 27b3e16..0000000
--- a/src/com/android/calendar/alerts/GlobalDismissManager.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.calendar.alerts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.CalendarContract.CalendarAlerts;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Events;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.calendar.R;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Utilities for managing notification dismissal across devices.
- */
-public class GlobalDismissManager extends BroadcastReceiver {
-    public static class AlarmId {
-        public long mEventId;
-        public long mStart;
-
-        public AlarmId(long id, long start) {
-            mEventId = id;
-            mStart = start;
-        }
-    }
-
-    /**
-     * Globally dismiss notifications that are backed by the same events.
-     *
-     * @param context application context
-     * @param alarmIds Unique identifiers for events that have been dismissed by the user.
-     * @return true if notification_sender_id is available
-     */
-    public static void dismissGlobally(Context context, List<AlarmId> alarmIds) {
-        Set<Long> eventIds = new HashSet<Long>(alarmIds.size());
-        for (AlarmId alarmId: alarmIds) {
-            eventIds.add(alarmId.mEventId);
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void onReceive(Context context, Intent intent) {
-        new AsyncTask<Pair<Context, Intent>, Void, Void>() {
-            @Override
-            protected Void doInBackground(Pair<Context, Intent>... params) {
-                return null;
-            }
-        }.execute(new Pair<Context, Intent>(context, intent));
-    }
-}
diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.kt b/src/com/android/calendar/alerts/GlobalDismissManager.kt
new file mode 100644
index 0000000..4cf0bc0
--- /dev/null
+++ b/src/com/android/calendar/alerts/GlobalDismissManager.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.calendar.alerts
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.AsyncTask
+import android.util.Pair
+import java.util.HashSet
+import java.util.List
+
+/**
+ * Utilities for managing notification dismissal across devices.
+ */
+class GlobalDismissManager : BroadcastReceiver() {
+    class AlarmId(var mEventId: Long, var mStart: Long)
+
+    @Override
+    @SuppressWarnings("unchecked")
+    override fun onReceive(context: Context?, intent: Intent?) {
+        object : AsyncTask<Pair<Context?, Intent?>?, Void?, Void?>() {
+            @Override
+            protected override fun doInBackground(vararg params: Pair<Context?, Intent?>?): Void? {
+                return null
+            }
+        }.execute(Pair<Context?, Intent?>(context, intent))
+    }
+
+    companion object {
+        /**
+         * Globally dismiss notifications that are backed by the same events.
+         *
+         * @param context application context
+         * @param alarmIds Unique identifiers for events that have been dismissed by the user.
+         * @return true if notification_sender_id is available
+         */
+        @JvmStatic fun dismissGlobally(context: Context?, alarmIds: List<AlarmId>) {
+            val eventIds: HashSet<Long> = HashSet<Long>(alarmIds.size)
+            for (alarmId in alarmIds) {
+                eventIds.add(alarmId.mEventId)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/InitAlarmsService.java b/src/com/android/calendar/alerts/InitAlarmsService.java
deleted file mode 100644
index 3a9b0b2..0000000
--- a/src/com/android/calendar/alerts/InitAlarmsService.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.calendar.alerts;
-
-import android.app.IntentService;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.CalendarContract;
-import android.util.Log;
-
-/**
- * Service for clearing all scheduled alerts from the CalendarAlerts table and
- * rescheduling them.  This is expected to be called only on boot up, to restore
- * the AlarmManager alarms that were lost on device restart.
- */
-public class InitAlarmsService extends IntentService {
-    private static final String TAG = "InitAlarmsService";
-    private static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
-    private static final Uri SCHEDULE_ALARM_REMOVE_URI = Uri.withAppendedPath(
-            CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
-
-    // Delay for rescheduling the alarms must be great enough to minimize race
-    // conditions with the provider's boot up actions.
-    private static final long DELAY_MS = 30000;
-
-    public InitAlarmsService() {
-        super("InitAlarmsService");
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        // Delay to avoid race condition of in-progress alarm scheduling in provider.
-        SystemClock.sleep(DELAY_MS);
-        Log.d(TAG, "Clearing and rescheduling alarms.");
-        try {
-            getContentResolver().update(SCHEDULE_ALARM_REMOVE_URI, new ContentValues(), null,
-                    null);
-        } catch (java.lang.IllegalArgumentException e) {
-            // java.lang.IllegalArgumentException:
-            //     Unknown URI content://com.android.calendar/schedule_alarms_remove
-
-            // Until b/7742576 is resolved, just catch the exception so the app won't crash
-            Log.e(TAG, "update failed: " + e.toString());
-        }
-    }
-}
diff --git a/src/com/android/calendar/alerts/InitAlarmsService.kt b/src/com/android/calendar/alerts/InitAlarmsService.kt
new file mode 100644
index 0000000..0ac8a47
--- /dev/null
+++ b/src/com/android/calendar/alerts/InitAlarmsService.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.calendar.alerts
+
+import android.app.IntentService
+import android.content.ContentValues
+import android.content.Intent
+import android.net.Uri
+import android.os.SystemClock
+import android.provider.CalendarContract
+import android.util.Log
+
+/**
+ * Service for clearing all scheduled alerts from the CalendarAlerts table and
+ * rescheduling them.  This is expected to be called only on boot up, to restore
+ * the AlarmManager alarms that were lost on device restart.
+ */
+class InitAlarmsService : IntentService("InitAlarmsService") {
+    @Override
+    protected override fun onHandleIntent(intent: Intent?) {
+        // Delay to avoid race condition of in-progress alarm scheduling in provider.
+        SystemClock.sleep(DELAY_MS)
+        Log.d(TAG, "Clearing and rescheduling alarms.")
+        try {
+            getContentResolver().update(
+                SCHEDULE_ALARM_REMOVE_URI, ContentValues(), null,
+                null
+            )
+        } catch (e: java.lang.IllegalArgumentException) {
+            // java.lang.IllegalArgumentException:
+            //     Unknown URI content://com.android.calendar/schedule_alarms_remove
+
+            // Until b/7742576 is resolved, just catch the exception so the app won't crash
+            Log.e(TAG, "update failed: " + e.toString())
+        }
+    }
+
+    companion object {
+        private const val TAG = "InitAlarmsService"
+        private const val SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove"
+        private val SCHEDULE_ALARM_REMOVE_URI: Uri = Uri.withAppendedPath(
+            CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH
+        )
+
+        // Delay for rescheduling the alarms must be great enough to minimize race
+        // conditions with the provider's boot up actions.
+        private const val DELAY_MS: Long = 30000
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/NotificationMgr.java b/src/com/android/calendar/alerts/NotificationMgr.kt
similarity index 65%
rename from src/com/android/calendar/alerts/NotificationMgr.java
rename to src/com/android/calendar/alerts/NotificationMgr.kt
index 0ab475c..609b814 100644
--- a/src/com/android/calendar/alerts/NotificationMgr.java
+++ b/src/com/android/calendar/alerts/NotificationMgr.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,29 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.calendar.alerts
 
-package com.android.calendar.alerts;
+import com.android.calendar.alerts.AlertService.NotificationWrapper
 
-import com.android.calendar.alerts.AlertService.NotificationWrapper;
-
-public abstract class NotificationMgr {
-    public abstract void notify(int id, NotificationWrapper notification);
-    public abstract void cancel(int id);
+abstract class NotificationMgr {
+    abstract fun notify(id: Int, notification: NotificationWrapper?)
+    abstract fun cancel(id: Int)
 
     /**
      * Don't actually use the notification framework's cancelAll since the SyncAdapter
      * might post notifications and we don't want to affect those.
      */
-    public void cancelAll() {
-        cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS);
+    fun cancelAll() {
+        cancelAllBetween(0, AlertService.MAX_NOTIFICATIONS)
     }
 
     /**
      * Cancels IDs between the specified bounds, inclusively.
      */
-    public void cancelAllBetween(int from, int to) {
-        for (int i = from; i <= to; i++) {
-            cancel(i);
+    fun cancelAllBetween(from: Int, to: Int) {
+        for (i in from..to) {
+            cancel(i)
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/alerts/QuickResponseActivity.java b/src/com/android/calendar/alerts/QuickResponseActivity.java
deleted file mode 100644
index 3d291d0..0000000
--- a/src/com/android/calendar/alerts/QuickResponseActivity.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.alerts;
-
-import android.app.ListActivity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.Toast;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.Arrays;
-
-/**
- * Activity which displays when the user wants to email guests from notifications.
- *
- * This presents the user with list if quick responses to be populated in an email
- * to minimize typing.
- *
- */
-public class QuickResponseActivity extends ListActivity implements OnItemClickListener {
-    private static final String TAG = "QuickResponseActivity";
-    public static final String EXTRA_EVENT_ID = "eventId";
-
-    private String[] mResponses = null;
-    static long mEventId;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        Intent intent = getIntent();
-        if (intent == null) {
-            finish();
-            return;
-        }
-
-        mEventId = intent.getLongExtra(EXTRA_EVENT_ID, -1);
-        if (mEventId == -1) {
-            finish();
-            return;
-        }
-
-        // Set listener
-        getListView().setOnItemClickListener(QuickResponseActivity.this);
-
-        // Populate responses
-        String[] responses = Utils.getQuickResponses(this);
-        Arrays.sort(responses);
-
-        // Add "Custom response..."
-        mResponses = new String[responses.length + 1];
-        int i;
-        for (i = 0; i < responses.length; i++) {
-            mResponses[i] = responses[i];
-        }
-        mResponses[i] = getResources().getString(R.string.quick_response_custom_msg);
-
-        setListAdapter(new ArrayAdapter<String>(this, R.layout.quick_response_item, mResponses));
-    }
-
-    // implements OnItemClickListener
-    @Override
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-
-        String body = null;
-        if (mResponses != null && position < mResponses.length - 1) {
-            body = mResponses[position];
-        }
-
-        // Start thread to query provider and send mail
-        new QueryThread(mEventId, body).start();
-    }
-
-    private class QueryThread extends Thread {
-        long mEventId;
-        String mBody;
-
-        QueryThread(long eventId, String body) {
-            mEventId = eventId;
-            mBody = body;
-        }
-
-        @Override
-        public void run() {
-        }
-    }
-}
diff --git a/src/com/android/calendar/alerts/QuickResponseActivity.kt b/src/com/android/calendar/alerts/QuickResponseActivity.kt
new file mode 100644
index 0000000..afccaff
--- /dev/null
+++ b/src/com/android/calendar/alerts/QuickResponseActivity.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.alerts
+
+import android.app.ListActivity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ArrayAdapter
+import com.android.calendar.R
+import com.android.calendar.Utils
+import java.util.Arrays
+
+/**
+ * Activity which displays when the user wants to email guests from notifications.
+ *
+ * This presents the user with list if quick responses to be populated in an email
+ * to minimize typing.
+ *
+ */
+class QuickResponseActivity : ListActivity(), OnItemClickListener {
+    private var mResponses: Array<String?>? = null
+    @Override
+    protected override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        val intent: Intent? = getIntent()
+        if (intent == null) {
+            finish()
+            return
+        }
+        mEventId = intent?.getLongExtra(EXTRA_EVENT_ID, -1) as Long
+        if (mEventId == -1L) {
+            finish()
+            return
+        }
+
+        // Set listener
+        getListView().setOnItemClickListener(this@QuickResponseActivity)
+
+        // Populate responses
+        val responses: Array<String> = Utils.getQuickResponses(this)
+        Arrays.sort(responses)
+
+        // Add "Custom response..."
+        mResponses = arrayOfNulls(responses.size + 1)
+        var i: Int
+        i = 0
+        while (i < responses.size) {
+            mResponses!![i] = responses[i]
+            i++
+        }
+        mResponses!![i] = getResources().getString(R.string.quick_response_custom_msg)
+        setListAdapter(ArrayAdapter<String>(this, R.layout.quick_response_item,
+                                                mResponses as Array<String?>))
+    }
+
+    // implements OnItemClickListener
+    @Override
+    override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+        var body: String? = null
+        if (mResponses != null && position < mResponses!!.size - 1) {
+            body = mResponses!![position]
+        }
+
+        // Start thread to query provider and send mail
+        QueryThread(mEventId, body).start()
+    }
+
+    private inner class QueryThread internal constructor(var mEventId: Long, var mBody: String?) :
+        Thread() {
+        @Override
+        override fun run() {
+        }
+    }
+
+    companion object {
+        private const val TAG = "QuickResponseActivity"
+        const val EXTRA_EVENT_ID = "eventId"
+        var mEventId: Long = 0
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthByWeekAdapter.java b/src/com/android/calendar/month/MonthByWeekAdapter.java
deleted file mode 100644
index 45a1bea..0000000
--- a/src/com/android/calendar/month/MonthByWeekAdapter.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.Message;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-
-import com.android.calendar.CalendarController;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class MonthByWeekAdapter extends SimpleWeeksAdapter {
-    private static final String TAG = "MonthByWeekAdapter";
-
-    public static final String WEEK_PARAMS_IS_MINI = "mini_month";
-    protected static int DEFAULT_QUERY_DAYS = 7 * 8; // 8 weeks
-    private static final long ANIMATE_TODAY_TIMEOUT = 1000;
-
-    protected CalendarController mController;
-    protected String mHomeTimeZone;
-    protected Time mTempTime;
-    protected Time mToday;
-    protected int mFirstJulianDay;
-    protected int mQueryDays;
-    protected boolean mIsMiniMonth = true;
-    protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
-    private final boolean mShowAgendaWithMonth;
-
-    protected ArrayList<ArrayList<Event>> mEventDayList = new ArrayList<ArrayList<Event>>();
-    protected ArrayList<Event> mEvents = null;
-
-    private boolean mAnimateToday = false;
-    private long mAnimateTime = 0;
-
-    private Handler mEventDialogHandler;
-
-    MonthWeekEventsView mClickedView;
-    MonthWeekEventsView mSingleTapUpView;
-    MonthWeekEventsView mLongClickedView;
-
-    float mClickedXLocation;                // Used to find which day was clicked
-    long mClickTime;                        // Used to calculate minimum click animation time
-    // Used to insure minimal time for seeing the click animation before switching views
-    private static final int mOnTapDelay = 100;
-    // Minimal time for a down touch action before stating the click animation, this insures that
-    // there is no click animation on flings
-    private static int mOnDownDelay;
-    private static int mTotalClickDelay;
-    // Minimal distance to move the finger in order to cancel the click animation
-    private static float mMovedPixelToCancel;
-
-    public MonthByWeekAdapter(Context context, HashMap<String, Integer> params) {
-        super(context, params);
-        if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
-            mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0;
-        }
-        mShowAgendaWithMonth = Utils.getConfigBool(context, R.bool.show_agenda_with_month);
-        ViewConfiguration vc = ViewConfiguration.get(context);
-        mOnDownDelay = ViewConfiguration.getTapTimeout();
-        mMovedPixelToCancel = vc.getScaledTouchSlop();
-        mTotalClickDelay = mOnDownDelay + mOnTapDelay;
-    }
-
-    public void animateToday() {
-        mAnimateToday = true;
-        mAnimateTime = System.currentTimeMillis();
-    }
-
-    @Override
-    protected void init() {
-        super.init();
-        mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
-        mController = CalendarController.getInstance(mContext);
-        mHomeTimeZone = Utils.getTimeZone(mContext, null);
-        mSelectedDay.switchTimezone(mHomeTimeZone);
-        mToday = new Time(mHomeTimeZone);
-        mToday.setToNow();
-        mTempTime = new Time(mHomeTimeZone);
-    }
-
-    private void updateTimeZones() {
-        mSelectedDay.timezone = mHomeTimeZone;
-        mSelectedDay.normalize(true);
-        mToday.timezone = mHomeTimeZone;
-        mToday.setToNow();
-        mTempTime.switchTimezone(mHomeTimeZone);
-    }
-
-    @Override
-    public void setSelectedDay(Time selectedTime) {
-        mSelectedDay.set(selectedTime);
-        long millis = mSelectedDay.normalize(true);
-        mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
-                Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
-        notifyDataSetChanged();
-    }
-
-    public void setEvents(int firstJulianDay, int numDays, ArrayList<Event> events) {
-        if (mIsMiniMonth) {
-            if (Log.isLoggable(TAG, Log.ERROR)) {
-                Log.e(TAG, "Attempted to set events for mini view. Events only supported in full"
-                        + " view.");
-            }
-            return;
-        }
-        mEvents = events;
-        mFirstJulianDay = firstJulianDay;
-        mQueryDays = numDays;
-        // Create a new list, this is necessary since the weeks are referencing
-        // pieces of the old list
-        ArrayList<ArrayList<Event>> eventDayList = new ArrayList<ArrayList<Event>>();
-        for (int i = 0; i < numDays; i++) {
-            eventDayList.add(new ArrayList<Event>());
-        }
-
-        if (events == null || events.size() == 0) {
-            if(Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "No events. Returning early--go schedule something fun.");
-            }
-            mEventDayList = eventDayList;
-            refresh();
-            return;
-        }
-
-        // Compute the new set of days with events
-        for (Event event : events) {
-            int startDay = event.startDay - mFirstJulianDay;
-            int endDay = event.endDay - mFirstJulianDay + 1;
-            if (startDay < numDays || endDay >= 0) {
-                if (startDay < 0) {
-                    startDay = 0;
-                }
-                if (startDay > numDays) {
-                    continue;
-                }
-                if (endDay < 0) {
-                    continue;
-                }
-                if (endDay > numDays) {
-                    endDay = numDays;
-                }
-                for (int j = startDay; j < endDay; j++) {
-                    eventDayList.get(j).add(event);
-                }
-            }
-        }
-        if(Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Processed " + events.size() + " events.");
-        }
-        mEventDayList = eventDayList;
-        refresh();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        if (mIsMiniMonth) {
-            return super.getView(position, convertView, parent);
-        }
-        MonthWeekEventsView v;
-        LayoutParams params = new LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-        HashMap<String, Integer> drawingParams = null;
-        boolean isAnimatingToday = false;
-        if (convertView != null) {
-            v = (MonthWeekEventsView) convertView;
-            // Checking updateToday uses the current params instead of the new
-            // params, so this is assuming the view is relatively stable
-            if (mAnimateToday && v.updateToday(mSelectedDay.timezone)) {
-                long currentTime = System.currentTimeMillis();
-                // If it's been too long since we tried to start the animation
-                // don't show it. This can happen if the user stops a scroll
-                // before reaching today.
-                if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
-                    mAnimateToday = false;
-                    mAnimateTime = 0;
-                } else {
-                    isAnimatingToday = true;
-                    // There is a bug that causes invalidates to not work some
-                    // of the time unless we recreate the view.
-                    v = new MonthWeekEventsView(mContext);
-               }
-            } else {
-                drawingParams = (HashMap<String, Integer>) v.getTag();
-            }
-        } else {
-            v = new MonthWeekEventsView(mContext);
-        }
-        if (drawingParams == null) {
-            drawingParams = new HashMap<String, Integer>();
-        }
-        drawingParams.clear();
-
-        v.setLayoutParams(params);
-        v.setClickable(true);
-        v.setOnTouchListener(this);
-
-        int selectedDay = -1;
-        if (mSelectedWeek == position) {
-            selectedDay = mSelectedDay.weekDay;
-        }
-
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
-                (parent.getHeight() + parent.getTop()) / mNumWeeks);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
-        drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation);
-
-        if (isAnimatingToday) {
-            drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1);
-            mAnimateToday = false;
-        }
-
-        v.setWeekParams(drawingParams, mSelectedDay.timezone);
-        return v;
-    }
-
-    @Override
-    protected void refresh() {
-        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
-        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
-        mHomeTimeZone = Utils.getTimeZone(mContext, null);
-        mOrientation = mContext.getResources().getConfiguration().orientation;
-        updateTimeZones();
-        notifyDataSetChanged();
-    }
-
-    @Override
-    protected void onDayTapped(Time day) {
-        setDayParameters(day);
-         if (mShowAgendaWithMonth || mIsMiniMonth) {
-            // If agenda view is visible with month view , refresh the views
-            // with the selected day's info
-            mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
-                    ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
-        } else {
-            // Else , switch to the detailed view
-            mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
-                    ViewType.DETAIL,
-                            CalendarController.EXTRA_GOTO_DATE
-                            | CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null);
-        }
-    }
-
-    private void setDayParameters(Time day) {
-        day.timezone = mHomeTimeZone;
-        Time currTime = new Time(mHomeTimeZone);
-        currTime.set(mController.getTime());
-        day.hour = currTime.hour;
-        day.minute = currTime.minute;
-        day.allDay = false;
-        day.normalize(true);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (!(v instanceof MonthWeekEventsView)) {
-            return super.onTouch(v, event);
-        }
-
-        int action = event.getAction();
-
-        // Event was tapped - switch to the detailed view making sure the click animation
-        // is done first.
-        if (mGestureDetector.onTouchEvent(event)) {
-            mSingleTapUpView = (MonthWeekEventsView) v;
-            long delay = System.currentTimeMillis() - mClickTime;
-            // Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
-            mListView.postDelayed(mDoSingleTapUp,
-                    delay > mTotalClickDelay ? 0 : mTotalClickDelay - delay);
-            return true;
-        } else {
-            // Animate a click - on down: show the selected day in the "clicked" color.
-            // On Up/scroll/move/cancel: hide the "clicked" color.
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                    mClickedView = (MonthWeekEventsView)v;
-                    mClickedXLocation = event.getX();
-                    mClickTime = System.currentTimeMillis();
-                    mListView.postDelayed(mDoClick, mOnDownDelay);
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_SCROLL:
-                case MotionEvent.ACTION_CANCEL:
-                    clearClickedView((MonthWeekEventsView)v);
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    // No need to cancel on vertical movement, ACTION_SCROLL will do that.
-                    if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
-                        clearClickedView((MonthWeekEventsView)v);
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-        // Do not tell the frameworks we consumed the touch action so that fling actions can be
-        // processed by the fragment.
-        return false;
-    }
-
-    /**
-     * This is here so we can identify events and process them
-     */
-    protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
-        @Override
-        public boolean onSingleTapUp(MotionEvent e) {
-            return true;
-        }
-
-        @Override
-        public void onLongPress(MotionEvent e) {
-            if (mLongClickedView != null) {
-                Time day = mLongClickedView.getDayFromLocation(mClickedXLocation);
-                if (day != null) {
-                    mLongClickedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                    Message message = new Message();
-                    message.obj = day;
-                }
-                mLongClickedView.clearClickedDay();
-                mLongClickedView = null;
-             }
-        }
-    }
-
-    // Clear the visual cues of the click animation and related running code.
-    private void clearClickedView(MonthWeekEventsView v) {
-        mListView.removeCallbacks(mDoClick);
-        synchronized(v) {
-            v.clearClickedDay();
-        }
-        mClickedView = null;
-    }
-
-    // Perform the tap animation in a runnable to allow a delay before showing the tap color.
-    // This is done to prevent a click animation when a fling is done.
-    private final Runnable mDoClick = new Runnable() {
-        @Override
-        public void run() {
-            if (mClickedView != null) {
-                synchronized(mClickedView) {
-                    mClickedView.setClickedDay(mClickedXLocation);
-                }
-                mLongClickedView = mClickedView;
-                mClickedView = null;
-                // This is a workaround , sometimes the top item on the listview doesn't refresh on
-                // invalidate, so this forces a re-draw.
-                mListView.invalidate();
-            }
-        }
-    };
-
-    // Performs the single tap operation: go to the tapped day.
-    // This is done in a runnable to allow the click animation to finish before switching views
-    private final Runnable mDoSingleTapUp = new Runnable() {
-        @Override
-        public void run() {
-            if (mSingleTapUpView != null) {
-                Time day = mSingleTapUpView.getDayFromLocation(mClickedXLocation);
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Touched day at Row=" + mSingleTapUpView.mWeek + " day=" + day.toString());
-                }
-                if (day != null) {
-                    onDayTapped(day);
-                }
-                clearClickedView(mSingleTapUpView);
-                mSingleTapUpView = null;
-            }
-        }
-    };
-}
diff --git a/src/com/android/calendar/month/MonthByWeekAdapter.kt b/src/com/android/calendar/month/MonthByWeekAdapter.kt
new file mode 100644
index 0000000..da61795
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekAdapter.kt
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Handler
+import android.os.Message
+import android.text.format.Time
+import android.util.Log
+import android.view.GestureDetector
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.widget.AbsListView.LayoutParams
+import com.android.calendar.CalendarController
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+import java.util.ArrayList
+import java.util.HashMap
+
+class MonthByWeekAdapter(context: Context?, params: HashMap<String?, Int?>) :
+    SimpleWeeksAdapter(context as Context, params) {
+    protected var mController: CalendarController? = null
+    protected var mHomeTimeZone: String? = null
+    protected var mTempTime: Time? = null
+    protected var mToday: Time? = null
+    protected var mFirstJulianDay = 0
+    protected var mQueryDays = 0
+    protected var mIsMiniMonth = true
+    protected var mOrientation: Int = Configuration.ORIENTATION_LANDSCAPE
+    private val mShowAgendaWithMonth: Boolean
+    protected var mEventDayList: ArrayList<ArrayList<Event>> = ArrayList<ArrayList<Event>>()
+    protected var mEvents: ArrayList<Event>? = null
+    private var mAnimateToday = false
+    private var mAnimateTime: Long = 0
+    private val mEventDialogHandler: Handler? = null
+    var mClickedView: MonthWeekEventsView? = null
+    var mSingleTapUpView: MonthWeekEventsView? = null
+    var mLongClickedView: MonthWeekEventsView? = null
+    var mClickedXLocation = 0f // Used to find which day was clicked
+    var mClickTime: Long = 0 // Used to calculate minimum click animation time
+
+    fun animateToday() {
+        mAnimateToday = true
+        mAnimateTime = System.currentTimeMillis()
+    }
+
+    @Override
+    protected override fun init() {
+        super.init()
+        mGestureDetector = GestureDetector(mContext, CalendarGestureListener())
+        mController = CalendarController.getInstance(mContext)
+        mHomeTimeZone = Utils.getTimeZone(mContext, null)
+        mSelectedDay?.switchTimezone(mHomeTimeZone)
+        mToday = Time(mHomeTimeZone)
+        mToday?.setToNow()
+        mTempTime = Time(mHomeTimeZone)
+    }
+
+    private fun updateTimeZones() {
+        mSelectedDay!!.timezone = mHomeTimeZone
+        mSelectedDay?.normalize(true)
+        mToday!!.timezone = mHomeTimeZone
+        mToday?.setToNow()
+        mTempTime?.switchTimezone(mHomeTimeZone)
+    }
+
+    @Override
+    override fun setSelectedDay(selectedTime: Time?) {
+        mSelectedDay?.set(selectedTime)
+        val millis: Long = mSelectedDay!!.normalize(true)
+        mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
+            Time.getJulianDay(millis, mSelectedDay!!.gmtoff), mFirstDayOfWeek
+        )
+        notifyDataSetChanged()
+    }
+
+    fun setEvents(firstJulianDay: Int, numDays: Int, events: ArrayList<Event>?) {
+        if (mIsMiniMonth) {
+            if (Log.isLoggable(TAG, Log.ERROR)) {
+                Log.e(
+                    TAG, "Attempted to set events for mini view. Events only supported in full" +
+                        " view."
+                )
+            }
+            return
+        }
+        mEvents = events
+        mFirstJulianDay = firstJulianDay
+        mQueryDays = numDays
+        // Create a new list, this is necessary since the weeks are referencing
+        // pieces of the old list
+        val eventDayList: ArrayList<ArrayList<Event>> = ArrayList<ArrayList<Event>>()
+        for (i in 0 until numDays) {
+            eventDayList.add(ArrayList<Event>())
+        }
+        if (events == null || events.size == 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No events. Returning early--go schedule something fun.")
+            }
+            mEventDayList = eventDayList
+            refresh()
+            return
+        }
+
+        // Compute the new set of days with events
+        for (event in events) {
+            var startDay: Int = event.startDay - mFirstJulianDay
+            var endDay: Int = event.endDay - mFirstJulianDay + 1
+            if (startDay < numDays || endDay >= 0) {
+                if (startDay < 0) {
+                    startDay = 0
+                }
+                if (startDay > numDays) {
+                    continue
+                }
+                if (endDay < 0) {
+                    continue
+                }
+                if (endDay > numDays) {
+                    endDay = numDays
+                }
+                for (j in startDay until endDay) {
+                    eventDayList.get(j).add(event)
+                }
+            }
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Processed " + events.size.toString() + " events.")
+        }
+        mEventDayList = eventDayList
+        refresh()
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+        if (mIsMiniMonth) {
+            return super.getView(position, convertView, parent)
+        }
+        var v: MonthWeekEventsView
+        val params = LayoutParams(
+            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
+        )
+        var drawingParams: HashMap<String?, Int?>? = null
+        var isAnimatingToday = false
+        if (convertView != null) {
+            v = convertView as MonthWeekEventsView
+            // Checking updateToday uses the current params instead of the new
+            // params, so this is assuming the view is relatively stable
+            if (mAnimateToday && v.updateToday(mSelectedDay!!.timezone)) {
+                val currentTime: Long = System.currentTimeMillis()
+                // If it's been too long since we tried to start the animation
+                // don't show it. This can happen if the user stops a scroll
+                // before reaching today.
+                if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
+                    mAnimateToday = false
+                    mAnimateTime = 0
+                } else {
+                    isAnimatingToday = true
+                    // There is a bug that causes invalidates to not work some
+                    // of the time unless we recreate the view.
+                    v = MonthWeekEventsView(mContext)
+                }
+            } else {
+                drawingParams = v.getTag() as HashMap<String?, Int?>
+            }
+        } else {
+            v = MonthWeekEventsView(mContext)
+        }
+        if (drawingParams == null) {
+            drawingParams = HashMap<String?, Int?>()
+        }
+        drawingParams.clear()
+        v.setLayoutParams(params)
+        v.setClickable(true)
+        v.setOnTouchListener(this)
+        var selectedDay = -1
+        if (mSelectedWeek === position) {
+            selectedDay = mSelectedDay!!.weekDay
+        }
+        drawingParams.put(
+            SimpleWeekView.VIEW_PARAMS_HEIGHT,
+            (parent.getHeight() + parent.getTop()) / mNumWeeks
+        )
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, if (mShowWeekNumber) 1 else 0)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth)
+        drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation)
+        if (isAnimatingToday) {
+            drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1)
+            mAnimateToday = false
+        }
+        v.setWeekParams(drawingParams, mSelectedDay!!.timezone)
+        return v
+    }
+
+    @Override
+    internal override fun refresh() {
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+        mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+        mHomeTimeZone = Utils.getTimeZone(mContext, null)
+        mOrientation = mContext.getResources().getConfiguration().orientation
+        updateTimeZones()
+        notifyDataSetChanged()
+    }
+
+    @Override
+    protected override fun onDayTapped(day: Time) {
+        setDayParameters(day)
+        if (mShowAgendaWithMonth || mIsMiniMonth) {
+            // If agenda view is visible with month view , refresh the views
+            // with the selected day's info
+            mController?.sendEvent(
+                mContext as Object?, EventType.GO_TO, day, day, -1,
+                ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null
+            )
+        } else {
+            // Else , switch to the detailed view
+            mController?.sendEvent(
+                mContext as Object?, EventType.GO_TO, day, day, -1,
+                ViewType.DETAIL, CalendarController.EXTRA_GOTO_DATE
+                    or CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null
+            )
+        }
+    }
+
+    private fun setDayParameters(day: Time) {
+        day.timezone = mHomeTimeZone
+        val currTime = Time(mHomeTimeZone)
+        currTime.set(mController!!.time as Long)
+        day.hour = currTime.hour
+        day.minute = currTime.minute
+        day.allDay = false
+        day.normalize(true)
+    }
+
+    @Override
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        if (v !is MonthWeekEventsView) {
+            return super.onTouch(v, event)
+        }
+        val action: Int = event!!.getAction()
+
+        // Event was tapped - switch to the detailed view making sure the click animation
+        // is done first.
+        if (mGestureDetector!!.onTouchEvent(event)) {
+            mSingleTapUpView = v as MonthWeekEventsView?
+            val delay: Long = System.currentTimeMillis() - mClickTime
+            // Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
+            mListView?.postDelayed(
+                mDoSingleTapUp,
+                if (delay > mTotalClickDelay) 0 else mTotalClickDelay - delay
+            )
+            return true
+        } else {
+            // Animate a click - on down: show the selected day in the "clicked" color.
+            // On Up/scroll/move/cancel: hide the "clicked" color.
+            when (action) {
+                MotionEvent.ACTION_DOWN -> {
+                    mClickedView = v as MonthWeekEventsView
+                    mClickedXLocation = event.getX()
+                    mClickTime = System.currentTimeMillis()
+                    mListView?.postDelayed(mDoClick, mOnDownDelay.toLong())
+                }
+                MotionEvent.ACTION_UP, MotionEvent.ACTION_SCROLL, MotionEvent.ACTION_CANCEL ->
+                    clearClickedView(
+                    v as MonthWeekEventsView?
+                )
+                MotionEvent.ACTION_MOVE -> // No need to cancel on vertical movement,
+                    // ACTION_SCROLL will do that.
+                    if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
+                        clearClickedView(v as MonthWeekEventsView?)
+                    }
+                else -> {
+                }
+            }
+        }
+        // Do not tell the frameworks we consumed the touch action so that fling actions can be
+        // processed by the fragment.
+        return false
+    }
+
+    /**
+     * This is here so we can identify events and process them
+     */
+    protected inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+        @Override
+        override fun onSingleTapUp(e: MotionEvent?): Boolean {
+            return true
+        }
+
+        @Override
+        override fun onLongPress(e: MotionEvent?) {
+            if (mLongClickedView != null) {
+                val day: Time? = mLongClickedView?.getDayFromLocation(mClickedXLocation)
+                if (day != null) {
+                    mLongClickedView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+                    val message = Message()
+                    message.obj = day
+                }
+                mLongClickedView?.clearClickedDay()
+                mLongClickedView = null
+            }
+        }
+    }
+
+    // Clear the visual cues of the click animation and related running code.
+    private fun clearClickedView(v: MonthWeekEventsView?) {
+        mListView?.removeCallbacks(mDoClick)
+        synchronized(v as Any) { v?.clearClickedDay() }
+        mClickedView = null
+    }
+
+    // Perform the tap animation in a runnable to allow a delay before showing the tap color.
+    // This is done to prevent a click animation when a fling is done.
+    private val mDoClick: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            if (mClickedView != null) {
+                synchronized(mClickedView as MonthWeekEventsView) {
+                    mClickedView?.setClickedDay(mClickedXLocation) }
+                mLongClickedView = mClickedView
+                mClickedView = null
+                // This is a workaround , sometimes the top item on the listview doesn't refresh on
+                // invalidate, so this forces a re-draw.
+                mListView?.invalidate()
+            }
+        }
+    }
+
+    // Performs the single tap operation: go to the tapped day.
+    // This is done in a runnable to allow the click animation to finish before switching views
+    private val mDoSingleTapUp: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            if (mSingleTapUpView != null) {
+                val day: Time? = mSingleTapUpView?.getDayFromLocation(mClickedXLocation)
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(
+                        TAG,
+                        "Touched day at Row=" + mSingleTapUpView?.mWeek?.toString() +
+                            " day=" + day?.toString()
+                    )
+                }
+                if (day != null) {
+                    onDayTapped(day)
+                }
+                clearClickedView(mSingleTapUpView)
+                mSingleTapUpView = null
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "MonthByWeekAdapter"
+        const val WEEK_PARAMS_IS_MINI = "mini_month"
+        protected var DEFAULT_QUERY_DAYS = 7 * 8 // 8 weeks
+        private const val ANIMATE_TODAY_TIMEOUT: Long = 1000
+
+        // Used to insure minimal time for seeing the click animation before switching views
+        private const val mOnTapDelay = 100
+
+        // Minimal time for a down touch action before stating the click animation, this ensures
+        // that there is no click animation on flings
+        private var mOnDownDelay: Int = 0
+        private var mTotalClickDelay: Int = 0
+
+        // Minimal distance to move the finger in order to cancel the click animation
+        private var mMovedPixelToCancel: Float = 0f
+    }
+
+    init {
+        if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
+            mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0
+        }
+        mShowAgendaWithMonth = Utils.getConfigBool(context as Context,
+            R.bool.show_agenda_with_month)
+        val vc: ViewConfiguration = ViewConfiguration.get(context)
+        mOnDownDelay = ViewConfiguration.getTapTimeout()
+        mMovedPixelToCancel = vc.getScaledTouchSlop().toFloat()
+        mTotalClickDelay = mOnDownDelay + mOnTapDelay
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthByWeekFragment.java b/src/com/android/calendar/month/MonthByWeekFragment.java
deleted file mode 100644
index f8a518d..0000000
--- a/src/com/android/calendar/month/MonthByWeekFragment.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.LoaderManager;
-import android.content.ContentUris;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.drawable.StateListDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Instances;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-
-import com.android.calendar.CalendarController;
-import com.android.calendar.CalendarController.EventInfo;
-import com.android.calendar.CalendarController.EventType;
-import com.android.calendar.CalendarController.ViewType;
-import com.android.calendar.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-
-public class MonthByWeekFragment extends SimpleDayPickerFragment implements
-        CalendarController.EventHandler, LoaderManager.LoaderCallbacks<Cursor>, OnScrollListener,
-        OnTouchListener {
-    private static final String TAG = "MonthFragment";
-    private static final String TAG_EVENT_DIALOG = "event_dialog";
-
-    // Selection and selection args for adding event queries
-    private static final String WHERE_CALENDARS_VISIBLE = Calendars.VISIBLE + "=1";
-    private static final String INSTANCES_SORT_ORDER = Instances.START_DAY + ","
-            + Instances.START_MINUTE + "," + Instances.TITLE;
-    protected static boolean mShowDetailsInMonth = false;
-
-    protected float mMinimumTwoMonthFlingVelocity;
-    protected boolean mIsMiniMonth;
-    protected boolean mHideDeclined;
-
-    protected int mFirstLoadedJulianDay;
-    protected int mLastLoadedJulianDay;
-
-    private static final int WEEKS_BUFFER = 1;
-    // How long to wait after scroll stops before starting the loader
-    // Using scroll duration because scroll state changes don't update
-    // correctly when a scroll is triggered programmatically.
-    private static final int LOADER_DELAY = 200;
-    // The minimum time between requeries of the data if the db is
-    // changing
-    private static final int LOADER_THROTTLE_DELAY = 500;
-
-    private CursorLoader mLoader;
-    private Uri mEventUri;
-    private final Time mDesiredDay = new Time();
-
-    private volatile boolean mShouldLoad = true;
-    private boolean mUserScrolled = false;
-
-    private int mEventsLoadingDelay;
-    private boolean mShowCalendarControls;
-    private boolean mIsDetached;
-
-    private final Runnable mTZUpdater = new Runnable() {
-        @Override
-        public void run() {
-            String tz = Utils.getTimeZone(mContext, mTZUpdater);
-            mSelectedDay.timezone = tz;
-            mSelectedDay.normalize(true);
-            mTempTime.timezone = tz;
-            mFirstDayOfMonth.timezone = tz;
-            mFirstDayOfMonth.normalize(true);
-            mFirstVisibleDay.timezone = tz;
-            mFirstVisibleDay.normalize(true);
-            if (mAdapter != null) {
-                mAdapter.refresh();
-            }
-        }
-    };
-
-
-    private final Runnable mUpdateLoader = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (this) {
-                if (!mShouldLoad || mLoader == null) {
-                    return;
-                }
-                // Stop any previous loads while we update the uri
-                stopLoader();
-
-                // Start the loader again
-                mEventUri = updateUri();
-
-                mLoader.setUri(mEventUri);
-                mLoader.startLoading();
-                mLoader.onContentChanged();
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Started loader with uri: " + mEventUri);
-                }
-            }
-        }
-    };
-    // Used to load the events when a delay is needed
-    Runnable mLoadingRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (!mIsDetached) {
-                mLoader = (CursorLoader) getLoaderManager().initLoader(0, null,
-                        MonthByWeekFragment.this);
-            }
-        }
-    };
-
-
-    /**
-     * Updates the uri used by the loader according to the current position of
-     * the listview.
-     *
-     * @return The new Uri to use
-     */
-    private Uri updateUri() {
-        SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
-        if (child != null) {
-            int julianDay = child.getFirstJulianDay();
-            mFirstLoadedJulianDay = julianDay;
-        }
-        // -1 to ensure we get all day events from any time zone
-        mTempTime.setJulianDay(mFirstLoadedJulianDay - 1);
-        long start = mTempTime.toMillis(true);
-        mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7;
-        // +1 to ensure we get all day events from any time zone
-        mTempTime.setJulianDay(mLastLoadedJulianDay + 1);
-        long end = mTempTime.toMillis(true);
-
-        // Create a new uri with the updated times
-        Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
-        ContentUris.appendId(builder, start);
-        ContentUris.appendId(builder, end);
-        return builder.build();
-    }
-
-    // Extract range of julian days from URI
-    private void updateLoadedDays() {
-        List<String> pathSegments = mEventUri.getPathSegments();
-        int size = pathSegments.size();
-        if (size <= 2) {
-            return;
-        }
-        long first = Long.parseLong(pathSegments.get(size - 2));
-        long last = Long.parseLong(pathSegments.get(size - 1));
-        mTempTime.set(first);
-        mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff);
-        mTempTime.set(last);
-        mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff);
-    }
-
-    protected String updateWhere() {
-        // TODO fix selection/selection args after b/3206641 is fixed
-        String where = WHERE_CALENDARS_VISIBLE;
-        if (mHideDeclined || !mShowDetailsInMonth) {
-            where += " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
-                    + Attendees.ATTENDEE_STATUS_DECLINED;
-        }
-        return where;
-    }
-
-    private void stopLoader() {
-        synchronized (mUpdateLoader) {
-            mHandler.removeCallbacks(mUpdateLoader);
-            if (mLoader != null) {
-                mLoader.stopLoading();
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Stopped loader from loading");
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mTZUpdater.run();
-        if (mAdapter != null) {
-            mAdapter.setSelectedDay(mSelectedDay);
-        }
-        mIsDetached = false;
-
-        ViewConfiguration viewConfig = ViewConfiguration.get(activity);
-        mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity() / 2;
-        Resources res = activity.getResources();
-        mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls);
-        // Synchronized the loading time of the month's events with the animation of the
-        // calendar controls.
-        if (mShowCalendarControls) {
-            mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time);
-        }
-        mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month);
-    }
-
-    @Override
-    public void onDetach() {
-        mIsDetached = true;
-        super.onDetach();
-        if (mShowCalendarControls) {
-            if (mListView != null) {
-                mListView.removeCallbacks(mLoadingRunnable);
-            }
-        }
-    }
-
-    @Override
-    protected void setUpAdapter() {
-        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
-        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
-
-        HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
-        weekParams.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, mIsMiniMonth ? 1 : 0);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
-                Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff));
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek);
-        if (mAdapter == null) {
-            mAdapter = new MonthByWeekAdapter(getActivity(), weekParams);
-            mAdapter.registerDataSetObserver(mObserver);
-        } else {
-            mAdapter.updateParams(weekParams);
-        }
-        mAdapter.notifyDataSetChanged();
-    }
-
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View v;
-        if (mIsMiniMonth) {
-            v = inflater.inflate(R.layout.month_by_week, container, false);
-        } else {
-            v = inflater.inflate(R.layout.full_month_by_week, container, false);
-        }
-        mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
-        return v;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        mListView.setSelector(new StateListDrawable());
-        mListView.setOnTouchListener(this);
-
-        if (!mIsMiniMonth) {
-            mListView.setBackgroundColor(getResources().getColor(R.color.month_bgcolor));
-        }
-
-        // To get a smoother transition when showing this fragment, delay loading of events until
-        // the fragment is expended fully and the calendar controls are gone.
-        if (mShowCalendarControls) {
-            mListView.postDelayed(mLoadingRunnable, mEventsLoadingDelay);
-        } else {
-            mLoader = (CursorLoader) getLoaderManager().initLoader(0, null, this);
-        }
-        mAdapter.setListView(mListView);
-    }
-
-    public MonthByWeekFragment() {
-        this(System.currentTimeMillis(), true);
-    }
-
-    public MonthByWeekFragment(long initialTime, boolean isMiniMonth) {
-        super(initialTime);
-        mIsMiniMonth = isMiniMonth;
-    }
-
-    @Override
-    protected void setUpHeader() {
-        if (mIsMiniMonth) {
-            super.setUpHeader();
-            return;
-        }
-
-        mDayLabels = new String[7];
-        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
-            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
-                    DateUtils.LENGTH_MEDIUM).toUpperCase();
-        }
-    }
-
-    // TODO
-    @Override
-    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        if (mIsMiniMonth) {
-            return null;
-        }
-        CursorLoader loader;
-        synchronized (mUpdateLoader) {
-            mFirstLoadedJulianDay =
-                    Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
-                    - (mNumWeeks * 7 / 2);
-            mEventUri = updateUri();
-            String where = updateWhere();
-
-            loader = new CursorLoader(
-                    getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
-                    null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER);
-            loader.setUpdateThrottle(LOADER_THROTTLE_DELAY);
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Returning new loader with uri: " + mEventUri);
-        }
-        return loader;
-    }
-
-    @Override
-    public void doResumeUpdates() {
-        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
-        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
-        boolean prevHideDeclined = mHideDeclined;
-        mHideDeclined = Utils.getHideDeclinedEvents(mContext);
-        if (prevHideDeclined != mHideDeclined && mLoader != null) {
-            mLoader.setSelection(updateWhere());
-        }
-        mDaysPerWeek = Utils.getDaysPerWeek(mContext);
-        updateHeader();
-        mAdapter.setSelectedDay(mSelectedDay);
-        mTZUpdater.run();
-        mTodayUpdater.run();
-        goTo(mSelectedDay.toMillis(true), false, true, false);
-    }
-
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-        synchronized (mUpdateLoader) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri);
-            }
-            CursorLoader cLoader = (CursorLoader) loader;
-            if (mEventUri == null) {
-                mEventUri = cLoader.getUri();
-                updateLoadedDays();
-            }
-            if (cLoader.getUri().compareTo(mEventUri) != 0) {
-                // We've started a new query since this loader ran so ignore the
-                // result
-                return;
-            }
-            ArrayList<Event> events = new ArrayList<Event>();
-            Event.buildEventsFromCursor(
-                    events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay);
-            ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay,
-                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events);
-        }
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-    }
-
-    @Override
-    public void eventsChanged() {
-        // TODO remove this after b/3387924 is resolved
-        if (mLoader != null) {
-            mLoader.forceLoad();
-        }
-    }
-
-    @Override
-    public long getSupportedEventTypes() {
-        return EventType.GO_TO | EventType.EVENTS_CHANGED;
-    }
-
-    @Override
-    public void handleEvent(EventInfo event) {
-        if (event.eventType == EventType.GO_TO) {
-            boolean animate = true;
-            if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
-                    Time.getJulianDay(event.selectedTime.toMillis(true), event.selectedTime.gmtoff)
-                    - Time.getJulianDay(mFirstVisibleDay.toMillis(true), mFirstVisibleDay.gmtoff)
-                    - mDaysPerWeek * mNumWeeks / 2)) {
-                animate = false;
-            }
-            mDesiredDay.set(event.selectedTime);
-            mDesiredDay.normalize(true);
-            boolean animateToday = (event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0;
-            boolean delayAnimation = goTo(event.selectedTime.toMillis(true), animate, true, false);
-            if (animateToday) {
-                // If we need to flash today start the animation after any
-                // movement from listView has ended.
-                mHandler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        ((MonthByWeekAdapter) mAdapter).animateToday();
-                        mAdapter.notifyDataSetChanged();
-                    }
-                }, delayAnimation ? GOTO_SCROLL_DURATION : 0);
-            }
-        } else if (event.eventType == EventType.EVENTS_CHANGED) {
-            eventsChanged();
-        }
-    }
-
-    @Override
-    protected void setMonthDisplayed(Time time, boolean updateHighlight) {
-        super.setMonthDisplayed(time, updateHighlight);
-        if (!mIsMiniMonth) {
-            boolean useSelected = false;
-            if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
-                mSelectedDay.set(mDesiredDay);
-                mAdapter.setSelectedDay(mDesiredDay);
-                useSelected = true;
-            } else {
-                mSelectedDay.set(time);
-                mAdapter.setSelectedDay(time);
-            }
-            CalendarController controller = CalendarController.getInstance(mContext);
-            if (mSelectedDay.minute >= 30) {
-                mSelectedDay.minute = 30;
-            } else {
-                mSelectedDay.minute = 0;
-            }
-            long newTime = mSelectedDay.normalize(true);
-            if (newTime != controller.getTime() && mUserScrolled) {
-                long offset = useSelected ? 0 : DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3;
-                controller.setTime(newTime + offset);
-            }
-            controller.sendEvent(this, EventType.UPDATE_TITLE, time, time, time, -1,
-                    ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
-                            | DateUtils.FORMAT_SHOW_YEAR, null, null);
-        }
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-
-        synchronized (mUpdateLoader) {
-            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
-                mShouldLoad = false;
-                stopLoader();
-                mDesiredDay.setToNow();
-            } else {
-                mHandler.removeCallbacks(mUpdateLoader);
-                mShouldLoad = true;
-                mHandler.postDelayed(mUpdateLoader, LOADER_DELAY);
-            }
-        }
-        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
-            mUserScrolled = true;
-        }
-
-        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        mDesiredDay.setToNow();
-        return false;
-    }
-}
diff --git a/src/com/android/calendar/month/MonthByWeekFragment.kt b/src/com/android/calendar/month/MonthByWeekFragment.kt
new file mode 100644
index 0000000..9fe9fe4
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekFragment.kt
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.app.Activity
+import android.app.LoaderManager
+import android.content.ContentUris
+import android.content.CursorLoader
+import android.content.Loader
+import android.content.res.Resources
+import android.database.Cursor
+import android.graphics.drawable.StateListDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Instances
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+
+import com.android.calendar.CalendarController
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+
+import java.util.ArrayList
+import java.util.Calendar
+import java.util.HashMap
+
+class MonthByWeekFragment @JvmOverloads constructor(
+    initialTime: Long = System.currentTimeMillis(),
+    protected var mIsMiniMonth: Boolean = true
+) : SimpleDayPickerFragment(initialTime), CalendarController.EventHandler,
+        LoaderManager.LoaderCallbacks<Cursor?>, OnScrollListener, OnTouchListener {
+    protected var mMinimumTwoMonthFlingVelocity = 0f
+    protected var mHideDeclined = false
+    protected var mFirstLoadedJulianDay = 0
+    protected var mLastLoadedJulianDay = 0
+    private var mLoader: CursorLoader? = null
+    private var mEventUri: Uri? = null
+    private val mDesiredDay: Time = Time()
+
+    @Volatile
+    private var mShouldLoad = true
+    private var mUserScrolled = false
+    private var mEventsLoadingDelay = 0
+    private var mShowCalendarControls = false
+    private var mIsDetached = false
+    private val mTZUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            val tz: String? = Utils.getTimeZone(mContext, this)
+            mSelectedDay.timezone = tz
+            mSelectedDay.normalize(true)
+            mTempTime.timezone = tz
+            mFirstDayOfMonth.timezone = tz
+            mFirstDayOfMonth.normalize(true)
+            mFirstVisibleDay.timezone = tz
+            mFirstVisibleDay.normalize(true)
+            if (mAdapter != null) {
+                mAdapter?.refresh()
+            }
+        }
+    }
+    private val mUpdateLoader: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            synchronized(this) {
+                if (!mShouldLoad || mLoader == null) {
+                    return
+                }
+                // Stop any previous loads while we update the uri
+                stopLoader()
+
+                // Start the loader again
+                mEventUri = updateUri()
+                mLoader?.setUri(mEventUri)
+                mLoader?.startLoading()
+                mLoader?.onContentChanged()
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Started loader with uri: $mEventUri")
+                }
+            }
+        }
+    }
+
+    // Used to load the events when a delay is needed
+    var mLoadingRunnable: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            if (!mIsDetached) {
+                mLoader = getLoaderManager().initLoader(
+                        0, null,
+                        this@MonthByWeekFragment
+                ) as? CursorLoader
+            }
+        }
+    }
+
+    /**
+     * Updates the uri used by the loader according to the current position of
+     * the listview.
+     *
+     * @return The new Uri to use
+     */
+    private fun updateUri(): Uri {
+        val child: SimpleWeekView? = mListView?.getChildAt(0) as? SimpleWeekView
+        if (child != null) {
+            val julianDay: Int = child?.getFirstJulianDay()
+            mFirstLoadedJulianDay = julianDay
+        }
+        // -1 to ensure we get all day events from any time zone
+        mTempTime.setJulianDay(mFirstLoadedJulianDay - 1)
+        val start: Long = mTempTime.toMillis(true)
+        mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7
+        // +1 to ensure we get all day events from any time zone
+        mTempTime.setJulianDay(mLastLoadedJulianDay + 1)
+        val end: Long = mTempTime.toMillis(true)
+
+        // Create a new uri with the updated times
+        val builder: Uri.Builder = Instances.CONTENT_URI.buildUpon()
+        ContentUris.appendId(builder, start)
+        ContentUris.appendId(builder, end)
+        return builder.build()
+    }
+
+    // Extract range of julian days from URI
+    private fun updateLoadedDays() {
+        val pathSegments = mEventUri?.getPathSegments()
+        val size: Int = pathSegments?.size as Int
+        if (size <= 2) {
+            return
+        }
+        val first: Long = (pathSegments!![size - 2])?.toLong() as Long
+        val last: Long = (pathSegments!![size - 1])?.toLong() as Long
+        mTempTime.set(first)
+        mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff)
+        mTempTime.set(last)
+        mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff)
+    }
+
+    protected fun updateWhere(): String {
+        // TODO fix selection/selection args after b/3206641 is fixed
+        var where = WHERE_CALENDARS_VISIBLE
+        if (mHideDeclined || !mShowDetailsInMonth) {
+            where += (" AND " + Instances.SELF_ATTENDEE_STATUS.toString() + "!=" +
+                    Attendees.ATTENDEE_STATUS_DECLINED)
+        }
+        return where
+    }
+
+    private fun stopLoader() {
+        synchronized(mUpdateLoader) {
+            mHandler.removeCallbacks(mUpdateLoader)
+            if (mLoader != null) {
+                mLoader?.stopLoading()
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Stopped loader from loading")
+                }
+            }
+        }
+    }
+
+    @Override
+    override fun onAttach(activity: Activity) {
+        super.onAttach(activity)
+        mTZUpdater.run()
+        if (mAdapter != null) {
+            mAdapter?.setSelectedDay(mSelectedDay)
+        }
+        mIsDetached = false
+        val viewConfig: ViewConfiguration = ViewConfiguration.get(activity)
+        mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity().toFloat() / 2f
+        val res: Resources = activity.getResources()
+        mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls)
+        // Synchronized the loading time of the month's events with the animation of the
+        // calendar controls.
+        if (mShowCalendarControls) {
+            mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time)
+        }
+        mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month)
+    }
+
+    @Override
+    override fun onDetach() {
+        mIsDetached = true
+        super.onDetach()
+        if (mShowCalendarControls) {
+            if (mListView != null) {
+                mListView?.removeCallbacks(mLoadingRunnable)
+            }
+        }
+    }
+
+    @Override
+    protected override fun setUpAdapter() {
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+        mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+        val weekParams = HashMap<String?, Int?>()
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks)
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, if (mShowWeekNumber) 1 else 0)
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek)
+        weekParams?.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, if (mIsMiniMonth) 1 else 0)
+        weekParams?.put(
+                SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
+                Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
+        )
+        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek)
+        if (mAdapter == null) {
+            mAdapter = MonthByWeekAdapter(getActivity(), weekParams) as SimpleWeeksAdapter?
+            mAdapter?.registerDataSetObserver(mObserver)
+        } else {
+            mAdapter?.updateParams(weekParams)
+        }
+        mAdapter?.notifyDataSetChanged()
+    }
+
+    @Override
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val v: View
+        v = if (mIsMiniMonth) {
+            inflater.inflate(R.layout.month_by_week, container, false)
+        } else {
+            inflater.inflate(R.layout.full_month_by_week, container, false)
+        }
+        mDayNamesHeader = v.findViewById(R.id.day_names) as? ViewGroup
+        return v
+    }
+
+    @Override
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        mListView?.setSelector(StateListDrawable())
+        mListView?.setOnTouchListener(this)
+        if (!mIsMiniMonth) {
+            mListView?.setBackgroundColor(getResources().getColor(R.color.month_bgcolor))
+        }
+
+        // To get a smoother transition when showing this fragment, delay loading of events until
+        // the fragment is expended fully and the calendar controls are gone.
+        if (mShowCalendarControls) {
+            mListView?.postDelayed(mLoadingRunnable, mEventsLoadingDelay.toLong())
+        } else {
+            mLoader = getLoaderManager().initLoader(0, null, this) as? CursorLoader
+        }
+        mAdapter?.setListView(mListView)
+    }
+
+    @Override
+    protected override fun setUpHeader() {
+        if (mIsMiniMonth) {
+            super.setUpHeader()
+            return
+        }
+        mDayLabels = arrayOfNulls<String>(7)
+        for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(
+                    i,
+                    DateUtils.LENGTH_MEDIUM
+            ).toUpperCase()
+        }
+    }
+
+    // TODO
+    @Override
+    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?>? {
+        if (mIsMiniMonth) {
+            return null
+        }
+        var loader: CursorLoader?
+        synchronized(mUpdateLoader) {
+            mFirstLoadedJulianDay =
+                    (Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff) -
+                            mNumWeeks * 7 / 2)
+            mEventUri = updateUri()
+            val where = updateWhere()
+            loader = CursorLoader(
+                    getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
+                    null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER
+            )
+            loader?.setUpdateThrottle(LOADER_THROTTLE_DELAY.toLong())
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Returning new loader with uri: $mEventUri")
+        }
+        return loader
+    }
+
+    @Override
+    override fun doResumeUpdates() {
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+        mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+        val prevHideDeclined = mHideDeclined
+        mHideDeclined = Utils.getHideDeclinedEvents(mContext)
+        if (prevHideDeclined != mHideDeclined && mLoader != null) {
+            mLoader?.setSelection(updateWhere())
+        }
+        mDaysPerWeek = Utils.getDaysPerWeek(mContext)
+        updateHeader()
+        mAdapter?.setSelectedDay(mSelectedDay)
+        mTZUpdater.run()
+        mTodayUpdater.run()
+        goTo(mSelectedDay.toMillis(true), false, true, false)
+    }
+
+    @Override
+    override fun onLoadFinished(loader: Loader<Cursor?>?, data: Cursor?) {
+        synchronized(mUpdateLoader) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(
+                        TAG,
+                        "Found " + data?.getCount()?.toString() + " cursor entries for uri " +
+                            mEventUri
+                )
+            }
+            val cLoader: CursorLoader = loader as CursorLoader
+            if (mEventUri == null) {
+                mEventUri = cLoader.getUri()
+                updateLoadedDays()
+            }
+            if (cLoader.getUri().compareTo(mEventUri) !== 0) {
+                // We've started a new query since this loader ran so ignore the
+                // result
+                return
+            }
+            val events: ArrayList<Event?>? = ArrayList<Event?>()
+            Event.buildEventsFromCursor(
+                    events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay
+            )
+            (mAdapter as MonthByWeekAdapter).setEvents(
+                    mFirstLoadedJulianDay,
+                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events as ArrayList<Event>?
+            )
+        }
+    }
+
+    @Override
+    override fun onLoaderReset(loader: Loader<Cursor?>?) {
+    }
+
+    @Override
+    override fun eventsChanged() {
+        // TODO remove this after b/3387924 is resolved
+        if (mLoader != null) {
+            mLoader?.forceLoad()
+        }
+    }
+
+    @get:Override override val supportedEventTypes: Long
+        get() = EventType.GO_TO or EventType.EVENTS_CHANGED
+
+    @Override
+    override fun handleEvent(event: CalendarController.EventInfo?) {
+        if (event?.eventType === EventType.GO_TO) {
+            var animate = true
+            if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
+                            Time.getJulianDay(event?.selectedTime?.toMillis(true) as Long,
+                                    event?.selectedTime?.gmtoff as Long) -
+                                    Time.getJulianDay(mFirstVisibleDay?.toMillis(true) as Long,
+                                            mFirstVisibleDay?.gmtoff as Long) -
+                                    mDaysPerWeek * mNumWeeks / 2L
+                    )
+            ) {
+                animate = false
+            }
+            mDesiredDay.set(event?.selectedTime)
+            mDesiredDay.normalize(true)
+            val animateToday = event?.extraLong and
+                    CalendarController.EXTRA_GOTO_TODAY.toLong() != 0L
+            val delayAnimation: Boolean =
+                    goTo(event?.selectedTime?.toMillis(true)?.toLong() as Long,
+                        animate, true, false)
+            if (animateToday) {
+                // If we need to flash today start the animation after any
+                // movement from listView has ended.
+                mHandler.postDelayed(object : Runnable {
+                    @Override
+                    override fun run() {
+                        (mAdapter as? MonthByWeekAdapter)?.animateToday()
+                        mAdapter?.notifyDataSetChanged()
+                    }
+                }, if (delayAnimation) GOTO_SCROLL_DURATION.toLong() else 0L)
+            }
+        } else if (event?.eventType == EventType.EVENTS_CHANGED) {
+            eventsChanged()
+        }
+    }
+
+    @Override
+    protected override fun setMonthDisplayed(time: Time, updateHighlight: Boolean) {
+        super.setMonthDisplayed(time, updateHighlight)
+        if (!mIsMiniMonth) {
+            var useSelected = false
+            if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
+                mSelectedDay.set(mDesiredDay)
+                mAdapter?.setSelectedDay(mDesiredDay)
+                useSelected = true
+            } else {
+                mSelectedDay.set(time)
+                mAdapter?.setSelectedDay(time)
+            }
+            val controller: CalendarController? = CalendarController.getInstance(mContext)
+            if (mSelectedDay.minute >= 30) {
+                mSelectedDay.minute = 30
+            } else {
+                mSelectedDay.minute = 0
+            }
+            val newTime: Long = mSelectedDay.normalize(true)
+            if (newTime != controller?.time && mUserScrolled) {
+                val offset: Long =
+                        if (useSelected) 0 else DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3.toLong()
+                controller?.time = (newTime + offset)
+            }
+            controller?.sendEvent(
+                    this as Object?, EventType.UPDATE_TITLE, time, time, time, -1,
+                    ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE.toLong() or
+                    DateUtils.FORMAT_NO_MONTH_DAY.toLong() or
+                    DateUtils.FORMAT_SHOW_YEAR.toLong(), null, null
+            )
+        }
+    }
+
+    @Override
+    override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+        synchronized(mUpdateLoader) {
+            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+                mShouldLoad = false
+                stopLoader()
+                mDesiredDay.setToNow()
+            } else {
+                mHandler.removeCallbacks(mUpdateLoader)
+                mShouldLoad = true
+                mHandler.postDelayed(mUpdateLoader, LOADER_DELAY.toLong())
+            }
+        }
+        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+            mUserScrolled = true
+        }
+        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState)
+    }
+
+    @Override
+    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+        mDesiredDay.setToNow()
+        return false
+    }
+
+    companion object {
+        private const val TAG = "MonthFragment"
+        private const val TAG_EVENT_DIALOG = "event_dialog"
+
+        // Selection and selection args for adding event queries
+        private val WHERE_CALENDARS_VISIBLE: String = Calendars.VISIBLE.toString() + "=1"
+        private val INSTANCES_SORT_ORDER: String = (Instances.START_DAY.toString() + "," +
+                Instances.START_MINUTE + "," + Instances.TITLE)
+        protected var mShowDetailsInMonth = false
+        private const val WEEKS_BUFFER = 1
+
+        // How long to wait after scroll stops before starting the loader
+        // Using scroll duration because scroll state changes don't update
+        // correctly when a scroll is triggered programmatically.
+        private const val LOADER_DELAY = 200
+
+        // The minimum time between requeries of the data if the db is
+        // changing
+        private const val LOADER_THROTTLE_DELAY = 500
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthListView.java b/src/com/android/calendar/month/MonthListView.java
deleted file mode 100644
index f2621cc..0000000
--- a/src/com/android/calendar/month/MonthListView.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.ListView;
-
-import com.android.calendar.Utils;
-
-public class MonthListView extends ListView {
-
-    private static final String TAG = "MonthListView";
-
-    public MonthListView(Context context) {
-        super(context);
-    }
-
-    public MonthListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public MonthListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return super.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return super.onInterceptTouchEvent(ev);
-    }
-}
diff --git a/src/com/android/calendar/month/MonthListView.kt b/src/com/android/calendar/month/MonthListView.kt
new file mode 100644
index 0000000..1facb4c
--- /dev/null
+++ b/src/com/android/calendar/month/MonthListView.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.ListView
+import com.android.calendar.Utils
+
+class MonthListView : ListView {
+    constructor(context: Context?) : super(context) {}
+    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) :
+            super(context, attrs, defStyle) {}
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
+
+    @Override
+    override fun onTouchEvent(ev: MotionEvent?): Boolean {
+        return super.onTouchEvent(ev)
+    }
+
+    @Override
+    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+        return super.onInterceptTouchEvent(ev)
+    }
+
+    companion object {
+        private const val TAG = "MonthListView"
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/MonthWeekEventsView.java b/src/com/android/calendar/month/MonthWeekEventsView.java
deleted file mode 100644
index e1c78c6..0000000
--- a/src/com/android/calendar/month/MonthWeekEventsView.java
+++ /dev/null
@@ -1,1110 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import com.android.calendar.Event;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.app.Service;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.provider.CalendarContract.Attendees;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-
-public class MonthWeekEventsView extends SimpleWeekView {
-
-    private static final String TAG = "MonthView";
-
-    private static final boolean DEBUG_LAYOUT = false;
-
-    public static final String VIEW_PARAMS_ORIENTATION = "orientation";
-    public static final String VIEW_PARAMS_ANIMATE_TODAY = "animate_today";
-
-    /* NOTE: these are not constants, and may be multiplied by a scale factor */
-    private static int TEXT_SIZE_MONTH_NUMBER = 32;
-    private static int TEXT_SIZE_EVENT = 12;
-    private static int TEXT_SIZE_EVENT_TITLE = 14;
-    private static int TEXT_SIZE_MORE_EVENTS = 12;
-    private static int TEXT_SIZE_MONTH_NAME = 14;
-    private static int TEXT_SIZE_WEEK_NUM = 12;
-
-    private static int DNA_MARGIN = 4;
-    private static int DNA_ALL_DAY_HEIGHT = 4;
-    private static int DNA_MIN_SEGMENT_HEIGHT = 4;
-    private static int DNA_WIDTH = 8;
-    private static int DNA_ALL_DAY_WIDTH = 32;
-    private static int DNA_SIDE_PADDING = 6;
-    private static int CONFLICT_COLOR = Color.BLACK;
-    private static int EVENT_TEXT_COLOR = Color.WHITE;
-
-    private static int DEFAULT_EDGE_SPACING = 0;
-    private static int SIDE_PADDING_MONTH_NUMBER = 4;
-    private static int TOP_PADDING_MONTH_NUMBER = 4;
-    private static int TOP_PADDING_WEEK_NUMBER = 4;
-    private static int SIDE_PADDING_WEEK_NUMBER = 20;
-    private static int DAY_SEPARATOR_OUTER_WIDTH = 0;
-    private static int DAY_SEPARATOR_INNER_WIDTH = 1;
-    private static int DAY_SEPARATOR_VERTICAL_LENGTH = 53;
-    private static int DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT = 64;
-    private static int MIN_WEEK_WIDTH = 50;
-
-    private static int EVENT_X_OFFSET_LANDSCAPE = 38;
-    private static int EVENT_Y_OFFSET_LANDSCAPE = 8;
-    private static int EVENT_Y_OFFSET_PORTRAIT = 7;
-    private static int EVENT_SQUARE_WIDTH = 10;
-    private static int EVENT_SQUARE_BORDER = 2;
-    private static int EVENT_LINE_PADDING = 2;
-    private static int EVENT_RIGHT_PADDING = 4;
-    private static int EVENT_BOTTOM_PADDING = 3;
-
-    private static int TODAY_HIGHLIGHT_WIDTH = 2;
-
-    private static int SPACING_WEEK_NUMBER = 24;
-    private static boolean mInitialized = false;
-    private static boolean mShowDetailsInMonth;
-
-    protected Time mToday = new Time();
-    protected boolean mHasToday = false;
-    protected int mTodayIndex = -1;
-    protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
-    protected List<ArrayList<Event>> mEvents = null;
-    protected ArrayList<Event> mUnsortedEvents = null;
-    HashMap<Integer, Utils.DNAStrand> mDna = null;
-    // This is for drawing the outlines around event chips and supports up to 10
-    // events being drawn on each day. The code will expand this if necessary.
-    protected FloatRef mEventOutlines = new FloatRef(10 * 4 * 4 * 7);
-
-
-
-    protected static StringBuilder mStringBuilder = new StringBuilder(50);
-    // TODO recreate formatter when locale changes
-    protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
-
-    protected Paint mMonthNamePaint;
-    protected TextPaint mEventPaint;
-    protected TextPaint mSolidBackgroundEventPaint;
-    protected TextPaint mFramedEventPaint;
-    protected TextPaint mDeclinedEventPaint;
-    protected TextPaint mEventExtrasPaint;
-    protected TextPaint mEventDeclinedExtrasPaint;
-    protected Paint mWeekNumPaint;
-    protected Paint mDNAAllDayPaint;
-    protected Paint mDNATimePaint;
-    protected Paint mEventSquarePaint;
-
-
-    protected Drawable mTodayDrawable;
-
-    protected int mMonthNumHeight;
-    protected int mMonthNumAscentHeight;
-    protected int mEventHeight;
-    protected int mEventAscentHeight;
-    protected int mExtrasHeight;
-    protected int mExtrasAscentHeight;
-    protected int mExtrasDescent;
-    protected int mWeekNumAscentHeight;
-
-    protected int mMonthBGColor;
-    protected int mMonthBGOtherColor;
-    protected int mMonthBGTodayColor;
-    protected int mMonthNumColor;
-    protected int mMonthNumOtherColor;
-    protected int mMonthNumTodayColor;
-    protected int mMonthNameColor;
-    protected int mMonthNameOtherColor;
-    protected int mMonthEventColor;
-    protected int mMonthDeclinedEventColor;
-    protected int mMonthDeclinedExtrasColor;
-    protected int mMonthEventExtraColor;
-    protected int mMonthEventOtherColor;
-    protected int mMonthEventExtraOtherColor;
-    protected int mMonthWeekNumColor;
-    protected int mMonthBusyBitsBgColor;
-    protected int mMonthBusyBitsBusyTimeColor;
-    protected int mMonthBusyBitsConflictTimeColor;
-    private int mClickedDayIndex = -1;
-    private int mClickedDayColor;
-    private static final int mClickedAlpha = 128;
-
-    protected int mEventChipOutlineColor = 0xFFFFFFFF;
-    protected int mDaySeparatorInnerColor;
-    protected int mTodayAnimateColor;
-
-    private boolean mAnimateToday;
-    private int mAnimateTodayAlpha = 0;
-    private ObjectAnimator mTodayAnimator = null;
-
-    private final TodayAnimatorListener mAnimatorListener = new TodayAnimatorListener();
-
-    class TodayAnimatorListener extends AnimatorListenerAdapter {
-        private volatile Animator mAnimator = null;
-        private volatile boolean mFadingIn = false;
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            synchronized (this) {
-                if (mAnimator != animation) {
-                    animation.removeAllListeners();
-                    animation.cancel();
-                    return;
-                }
-                if (mFadingIn) {
-                    if (mTodayAnimator != null) {
-                        mTodayAnimator.removeAllListeners();
-                        mTodayAnimator.cancel();
-                    }
-                    mTodayAnimator = ObjectAnimator.ofInt(MonthWeekEventsView.this,
-                            "animateTodayAlpha", 255, 0);
-                    mAnimator = mTodayAnimator;
-                    mFadingIn = false;
-                    mTodayAnimator.addListener(this);
-                    mTodayAnimator.setDuration(600);
-                    mTodayAnimator.start();
-                } else {
-                    mAnimateToday = false;
-                    mAnimateTodayAlpha = 0;
-                    mAnimator.removeAllListeners();
-                    mAnimator = null;
-                    mTodayAnimator = null;
-                    invalidate();
-                }
-            }
-        }
-
-        public void setAnimator(Animator animation) {
-            mAnimator = animation;
-        }
-
-        public void setFadingIn(boolean fadingIn) {
-            mFadingIn = fadingIn;
-        }
-
-    }
-
-    private int[] mDayXs;
-
-    /**
-     * This provides a reference to a float array which allows for easy size
-     * checking and reallocation. Used for drawing lines.
-     */
-    private class FloatRef {
-        float[] array;
-
-        public FloatRef(int size) {
-            array = new float[size];
-        }
-
-        public void ensureSize(int newSize) {
-            if (newSize >= array.length) {
-                // Add enough space for 7 more boxes to be drawn
-                array = Arrays.copyOf(array, newSize + 16 * 7);
-            }
-        }
-    }
-
-    /**
-     * Shows up as an error if we don't include this.
-     */
-    public MonthWeekEventsView(Context context) {
-        super(context);
-    }
-
-    // Sets the list of events for this week. Takes a sorted list of arrays
-    // divided up by day for generating the large month version and the full
-    // arraylist sorted by start time to generate the dna version.
-    public void setEvents(List<ArrayList<Event>> sortedEvents, ArrayList<Event> unsortedEvents) {
-        setEvents(sortedEvents);
-        // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to
-        // generate dna bits before its width has been fixed.
-        createDna(unsortedEvents);
-    }
-
-    /**
-     * Sets up the dna bits for the view. This will return early if the view
-     * isn't in a state that will create a valid set of dna yet (such as the
-     * views width not being set correctly yet).
-     */
-    public void createDna(ArrayList<Event> unsortedEvents) {
-        if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) {
-            // Stash the list of events for use when this view is ready, or
-            // just clear it if a null set has been passed to this view
-            mUnsortedEvents = unsortedEvents;
-            mDna = null;
-            return;
-        } else {
-            // clear the cached set of events since we're ready to build it now
-            mUnsortedEvents = null;
-        }
-        // Create the drawing coordinates for dna
-        if (!mShowDetailsInMonth) {
-            int numDays = mEvents.size();
-            int effectiveWidth = mWidth - mPadding * 2;
-            if (mShowWeekNum) {
-                effectiveWidth -= SPACING_WEEK_NUMBER;
-            }
-            DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING;
-            mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH);
-            mDayXs = new int[numDays];
-            for (int day = 0; day < numDays; day++) {
-                mDayXs[day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING;
-
-            }
-
-            int top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1;
-            int bottom = mHeight - DNA_MARGIN;
-            mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom,
-                    DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext());
-        }
-    }
-
-    public void setEvents(List<ArrayList<Event>> sortedEvents) {
-        mEvents = sortedEvents;
-        if (sortedEvents == null) {
-            return;
-        }
-        if (sortedEvents.size() != mNumDays) {
-            if (Log.isLoggable(TAG, Log.ERROR)) {
-                Log.wtf(TAG, "Events size must be same as days displayed: size="
-                        + sortedEvents.size() + " days=" + mNumDays);
-            }
-            mEvents = null;
-            return;
-        }
-    }
-
-    protected void loadColors(Context context) {
-        Resources res = context.getResources();
-        mMonthWeekNumColor = res.getColor(R.color.month_week_num_color);
-        mMonthNumColor = res.getColor(R.color.month_day_number);
-        mMonthNumOtherColor = res.getColor(R.color.month_day_number_other);
-        mMonthNumTodayColor = res.getColor(R.color.month_today_number);
-        mMonthNameColor = mMonthNumColor;
-        mMonthNameOtherColor = mMonthNumOtherColor;
-        mMonthEventColor = res.getColor(R.color.month_event_color);
-        mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color);
-        mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color);
-        mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color);
-        mMonthEventOtherColor = res.getColor(R.color.month_event_other_color);
-        mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color);
-        mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor);
-        mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor);
-        mMonthBGColor = res.getColor(R.color.month_bgcolor);
-        mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines);
-        mTodayAnimateColor = res.getColor(R.color.today_highlight_color);
-        mClickedDayColor = res.getColor(R.color.day_clicked_background_color);
-        mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light);
-    }
-
-    /**
-     * Sets up the text and style properties for painting. Override this if you
-     * want to use a different paint.
-     */
-    @Override
-    protected void initView() {
-        super.initView();
-
-        if (!mInitialized) {
-            Resources resources = getContext().getResources();
-            mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month);
-            TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title);
-            TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number);
-            SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin);
-            CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color);
-            EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color);
-            if (mScale != 1) {
-                TOP_PADDING_MONTH_NUMBER *= mScale;
-                TOP_PADDING_WEEK_NUMBER *= mScale;
-                SIDE_PADDING_MONTH_NUMBER *= mScale;
-                SIDE_PADDING_WEEK_NUMBER *= mScale;
-                SPACING_WEEK_NUMBER *= mScale;
-                TEXT_SIZE_MONTH_NUMBER *= mScale;
-                TEXT_SIZE_EVENT *= mScale;
-                TEXT_SIZE_EVENT_TITLE *= mScale;
-                TEXT_SIZE_MORE_EVENTS *= mScale;
-                TEXT_SIZE_MONTH_NAME *= mScale;
-                TEXT_SIZE_WEEK_NUM *= mScale;
-                DAY_SEPARATOR_OUTER_WIDTH *= mScale;
-                DAY_SEPARATOR_INNER_WIDTH *= mScale;
-                DAY_SEPARATOR_VERTICAL_LENGTH *= mScale;
-                DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT *= mScale;
-                EVENT_X_OFFSET_LANDSCAPE *= mScale;
-                EVENT_Y_OFFSET_LANDSCAPE *= mScale;
-                EVENT_Y_OFFSET_PORTRAIT *= mScale;
-                EVENT_SQUARE_WIDTH *= mScale;
-                EVENT_SQUARE_BORDER *= mScale;
-                EVENT_LINE_PADDING *= mScale;
-                EVENT_BOTTOM_PADDING *= mScale;
-                EVENT_RIGHT_PADDING *= mScale;
-                DNA_MARGIN *= mScale;
-                DNA_WIDTH *= mScale;
-                DNA_ALL_DAY_HEIGHT *= mScale;
-                DNA_MIN_SEGMENT_HEIGHT *= mScale;
-                DNA_SIDE_PADDING *= mScale;
-                DEFAULT_EDGE_SPACING *= mScale;
-                DNA_ALL_DAY_WIDTH *= mScale;
-                TODAY_HIGHLIGHT_WIDTH *= mScale;
-            }
-            if (!mShowDetailsInMonth) {
-                TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN;
-            }
-            mInitialized = true;
-        }
-        mPadding = DEFAULT_EDGE_SPACING;
-        loadColors(getContext());
-        // TODO modify paint properties depending on isMini
-
-        mMonthNumPaint = new Paint();
-        mMonthNumPaint.setFakeBoldText(false);
-        mMonthNumPaint.setAntiAlias(true);
-        mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER);
-        mMonthNumPaint.setColor(mMonthNumColor);
-        mMonthNumPaint.setStyle(Style.FILL);
-        mMonthNumPaint.setTextAlign(Align.RIGHT);
-        mMonthNumPaint.setTypeface(Typeface.DEFAULT);
-
-        mMonthNumAscentHeight = (int) (-mMonthNumPaint.ascent() + 0.5f);
-        mMonthNumHeight = (int) (mMonthNumPaint.descent() - mMonthNumPaint.ascent() + 0.5f);
-
-        mEventPaint = new TextPaint();
-        mEventPaint.setFakeBoldText(true);
-        mEventPaint.setAntiAlias(true);
-        mEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE);
-        mEventPaint.setColor(mMonthEventColor);
-
-        mSolidBackgroundEventPaint = new TextPaint(mEventPaint);
-        mSolidBackgroundEventPaint.setColor(EVENT_TEXT_COLOR);
-        mFramedEventPaint = new TextPaint(mSolidBackgroundEventPaint);
-
-        mDeclinedEventPaint = new TextPaint();
-        mDeclinedEventPaint.setFakeBoldText(true);
-        mDeclinedEventPaint.setAntiAlias(true);
-        mDeclinedEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE);
-        mDeclinedEventPaint.setColor(mMonthDeclinedEventColor);
-
-        mEventAscentHeight = (int) (-mEventPaint.ascent() + 0.5f);
-        mEventHeight = (int) (mEventPaint.descent() - mEventPaint.ascent() + 0.5f);
-
-        mEventExtrasPaint = new TextPaint();
-        mEventExtrasPaint.setFakeBoldText(false);
-        mEventExtrasPaint.setAntiAlias(true);
-        mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER);
-        mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT);
-        mEventExtrasPaint.setColor(mMonthEventExtraColor);
-        mEventExtrasPaint.setStyle(Style.FILL);
-        mEventExtrasPaint.setTextAlign(Align.LEFT);
-        mExtrasHeight = (int)(mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f);
-        mExtrasAscentHeight = (int)(-mEventExtrasPaint.ascent() + 0.5f);
-        mExtrasDescent = (int)(mEventExtrasPaint.descent() + 0.5f);
-
-        mEventDeclinedExtrasPaint = new TextPaint();
-        mEventDeclinedExtrasPaint.setFakeBoldText(false);
-        mEventDeclinedExtrasPaint.setAntiAlias(true);
-        mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER);
-        mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT);
-        mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor);
-        mEventDeclinedExtrasPaint.setStyle(Style.FILL);
-        mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT);
-
-        mWeekNumPaint = new Paint();
-        mWeekNumPaint.setFakeBoldText(false);
-        mWeekNumPaint.setAntiAlias(true);
-        mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM);
-        mWeekNumPaint.setColor(mWeekNumColor);
-        mWeekNumPaint.setStyle(Style.FILL);
-        mWeekNumPaint.setTextAlign(Align.RIGHT);
-
-        mWeekNumAscentHeight = (int) (-mWeekNumPaint.ascent() + 0.5f);
-
-        mDNAAllDayPaint = new Paint();
-        mDNATimePaint = new Paint();
-        mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor);
-        mDNATimePaint.setStyle(Style.FILL_AND_STROKE);
-        mDNATimePaint.setStrokeWidth(DNA_WIDTH);
-        mDNATimePaint.setAntiAlias(false);
-        mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor);
-        mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE);
-        mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH);
-        mDNAAllDayPaint.setAntiAlias(false);
-
-        mEventSquarePaint = new Paint();
-        mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER);
-        mEventSquarePaint.setAntiAlias(false);
-
-        if (DEBUG_LAYOUT) {
-            Log.d("EXTRA", "mScale=" + mScale);
-            Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint.ascent()
-                    + " descent=" + mMonthNumPaint.descent() + " int height=" + mMonthNumHeight);
-            Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint.ascent()
-                    + " descent=" + mEventPaint.descent() + " int height=" + mEventHeight
-                    + " int ascent=" + mEventAscentHeight);
-            Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent()
-                    + " descent=" + mEventExtrasPaint.descent() + " int height=" + mExtrasHeight);
-            Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent()
-                    + " descent=" + mWeekNumPaint.descent());
-        }
-    }
-
-    @Override
-    public void setWeekParams(HashMap<String, Integer> params, String tz) {
-        super.setWeekParams(params, tz);
-
-        if (params.containsKey(VIEW_PARAMS_ORIENTATION)) {
-            mOrientation = params.get(VIEW_PARAMS_ORIENTATION);
-        }
-
-        updateToday(tz);
-        mNumCells = mNumDays + 1;
-
-        if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) {
-            synchronized (mAnimatorListener) {
-                if (mTodayAnimator != null) {
-                    mTodayAnimator.removeAllListeners();
-                    mTodayAnimator.cancel();
-                }
-                mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
-                        Math.max(mAnimateTodayAlpha, 80), 255);
-                mTodayAnimator.setDuration(150);
-                mAnimatorListener.setAnimator(mTodayAnimator);
-                mAnimatorListener.setFadingIn(true);
-                mTodayAnimator.addListener(mAnimatorListener);
-                mAnimateToday = true;
-                mTodayAnimator.start();
-            }
-        }
-    }
-
-    /**
-     * @param tz
-     */
-    public boolean updateToday(String tz) {
-        mToday.timezone = tz;
-        mToday.setToNow();
-        mToday.normalize(true);
-        int julianToday = Time.getJulianDay(mToday.toMillis(false), mToday.gmtoff);
-        if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) {
-            mHasToday = true;
-            mTodayIndex = julianToday - mFirstJulianDay;
-        } else {
-            mHasToday = false;
-            mTodayIndex = -1;
-        }
-        return mHasToday;
-    }
-
-    public void setAnimateTodayAlpha(int alpha) {
-        mAnimateTodayAlpha = alpha;
-        invalidate();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        drawBackground(canvas);
-        drawWeekNums(canvas);
-        drawDaySeparators(canvas);
-        if (mHasToday && mAnimateToday) {
-            drawToday(canvas);
-        }
-        if (mShowDetailsInMonth) {
-            drawEvents(canvas);
-        } else {
-            if (mDna == null && mUnsortedEvents != null) {
-                createDna(mUnsortedEvents);
-            }
-            drawDNA(canvas);
-        }
-        drawClick(canvas);
-    }
-
-    protected void drawToday(Canvas canvas) {
-        r.top = DAY_SEPARATOR_INNER_WIDTH + (TODAY_HIGHLIGHT_WIDTH / 2);
-        r.bottom = mHeight - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f);
-        p.setStyle(Style.STROKE);
-        p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH);
-        r.left = computeDayLeftPosition(mTodayIndex) + (TODAY_HIGHLIGHT_WIDTH / 2);
-        r.right = computeDayLeftPosition(mTodayIndex + 1)
-                - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f);
-        p.setColor(mTodayAnimateColor | (mAnimateTodayAlpha << 24));
-        canvas.drawRect(r, p);
-        p.setStyle(Style.FILL);
-    }
-
-    // TODO move into SimpleWeekView
-    // Computes the x position for the left side of the given day
-    private int computeDayLeftPosition(int day) {
-        int effectiveWidth = mWidth;
-        int x = 0;
-        int xOffset = 0;
-        if (mShowWeekNum) {
-            xOffset = SPACING_WEEK_NUMBER + mPadding;
-            effectiveWidth -= xOffset;
-        }
-        x = day * effectiveWidth / mNumDays + xOffset;
-        return x;
-    }
-
-    @Override
-    protected void drawDaySeparators(Canvas canvas) {
-        float lines[] = new float[8 * 4];
-        int count = 6 * 4;
-        int wkNumOffset = 0;
-        int i = 0;
-        if (mShowWeekNum) {
-            // This adds the first line separating the week number
-            int xOffset = SPACING_WEEK_NUMBER + mPadding;
-            count += 4;
-            lines[i++] = xOffset;
-            lines[i++] = 0;
-            lines[i++] = xOffset;
-            lines[i++] = mHeight;
-            wkNumOffset++;
-        }
-        count += 4;
-        lines[i++] = 0;
-        lines[i++] = 0;
-        lines[i++] = mWidth;
-        lines[i++] = 0;
-        int y0 = 0;
-        int y1 = mHeight;
-
-        while (i < count) {
-            int x = computeDayLeftPosition(i / 4 - wkNumOffset);
-            lines[i++] = x;
-            lines[i++] = y0;
-            lines[i++] = x;
-            lines[i++] = y1;
-        }
-        p.setColor(mDaySeparatorInnerColor);
-        p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH);
-        canvas.drawLines(lines, 0, count, p);
-    }
-
-    @Override
-    protected void drawBackground(Canvas canvas) {
-        int i = 0;
-        int offset = 0;
-        r.top = DAY_SEPARATOR_INNER_WIDTH;
-        r.bottom = mHeight;
-        if (mShowWeekNum) {
-            i++;
-            offset++;
-        }
-        if (!mOddMonth[i]) {
-            while (++i < mOddMonth.length && !mOddMonth[i])
-                ;
-            r.right = computeDayLeftPosition(i - offset);
-            r.left = 0;
-            p.setColor(mMonthBGOtherColor);
-            canvas.drawRect(r, p);
-            // compute left edge for i, set up r, draw
-        } else if (!mOddMonth[(i = mOddMonth.length - 1)]) {
-            while (--i >= offset && !mOddMonth[i])
-                ;
-            i++;
-            // compute left edge for i, set up r, draw
-            r.right = mWidth;
-            r.left = computeDayLeftPosition(i - offset);
-            p.setColor(mMonthBGOtherColor);
-            canvas.drawRect(r, p);
-        }
-        if (mHasToday) {
-            p.setColor(mMonthBGTodayColor);
-            r.left = computeDayLeftPosition(mTodayIndex);
-            r.right = computeDayLeftPosition(mTodayIndex + 1);
-            canvas.drawRect(r, p);
-        }
-    }
-
-    // Draw the "clicked" color on the tapped day
-    private void drawClick(Canvas canvas) {
-        if (mClickedDayIndex != -1) {
-            int alpha = p.getAlpha();
-            p.setColor(mClickedDayColor);
-            p.setAlpha(mClickedAlpha);
-            r.left = computeDayLeftPosition(mClickedDayIndex);
-            r.right = computeDayLeftPosition(mClickedDayIndex + 1);
-            r.top = DAY_SEPARATOR_INNER_WIDTH;
-            r.bottom = mHeight;
-            canvas.drawRect(r, p);
-            p.setAlpha(alpha);
-        }
-    }
-
-    @Override
-    protected void drawWeekNums(Canvas canvas) {
-        int y;
-
-        int i = 0;
-        int offset = -1;
-        int todayIndex = mTodayIndex;
-        int x = 0;
-        int numCount = mNumDays;
-        if (mShowWeekNum) {
-            x = SIDE_PADDING_WEEK_NUMBER + mPadding;
-            y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER;
-            canvas.drawText(mDayNumbers[0], x, y, mWeekNumPaint);
-            numCount++;
-            i++;
-            todayIndex++;
-            offset++;
-
-        }
-
-        y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER;
-
-        boolean isFocusMonth = mFocusDay[i];
-        boolean isBold = false;
-        mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
-        for (; i < numCount; i++) {
-            if (mHasToday && todayIndex == i) {
-                mMonthNumPaint.setColor(mMonthNumTodayColor);
-                mMonthNumPaint.setFakeBoldText(isBold = true);
-                if (i + 1 < numCount) {
-                    // Make sure the color will be set back on the next
-                    // iteration
-                    isFocusMonth = !mFocusDay[i + 1];
-                }
-            } else if (mFocusDay[i] != isFocusMonth) {
-                isFocusMonth = mFocusDay[i];
-                mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
-            }
-            x = computeDayLeftPosition(i - offset) - (SIDE_PADDING_MONTH_NUMBER);
-            canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
-            if (isBold) {
-                mMonthNumPaint.setFakeBoldText(isBold = false);
-            }
-        }
-    }
-
-    protected void drawEvents(Canvas canvas) {
-        if (mEvents == null) {
-            return;
-        }
-
-        int day = -1;
-        for (ArrayList<Event> eventDay : mEvents) {
-            day++;
-            if (eventDay == null || eventDay.size() == 0) {
-                continue;
-            }
-            int ySquare;
-            int xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1;
-            int rightEdge = computeDayLeftPosition(day + 1);
-
-            if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
-                ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER;
-                rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1;
-            } else {
-                ySquare = EVENT_Y_OFFSET_LANDSCAPE;
-                rightEdge -= EVENT_X_OFFSET_LANDSCAPE;
-            }
-
-            // Determine if everything will fit when time ranges are shown.
-            boolean showTimes = true;
-            Iterator<Event> iter = eventDay.iterator();
-            int yTest = ySquare;
-            while (iter.hasNext()) {
-                Event event = iter.next();
-                int newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(),
-                        showTimes, /*doDraw*/ false);
-                if (newY == yTest) {
-                    showTimes = false;
-                    break;
-                }
-                yTest = newY;
-            }
-
-            int eventCount = 0;
-            iter = eventDay.iterator();
-            while (iter.hasNext()) {
-                Event event = iter.next();
-                int newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(),
-                        showTimes, /*doDraw*/ true);
-                if (newY == ySquare) {
-                    break;
-                }
-                eventCount++;
-                ySquare = newY;
-            }
-
-            int remaining = eventDay.size() - eventCount;
-            if (remaining > 0) {
-                drawMoreEvents(canvas, remaining, xSquare);
-            }
-        }
-    }
-
-    protected int addChipOutline(FloatRef lines, int count, int x, int y) {
-        lines.ensureSize(count + 16);
-        // top of box
-        lines.array[count++] = x;
-        lines.array[count++] = y;
-        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
-        lines.array[count++] = y;
-        // right side of box
-        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
-        lines.array[count++] = y;
-        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
-        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
-        // left side of box
-        lines.array[count++] = x;
-        lines.array[count++] = y;
-        lines.array[count++] = x;
-        lines.array[count++] = y + EVENT_SQUARE_WIDTH + 1;
-        // bottom of box
-        lines.array[count++] = x;
-        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
-        lines.array[count++] = x + EVENT_SQUARE_WIDTH + 1;
-        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
-
-        return count;
-    }
-
-    /**
-     * Attempts to draw the given event. Returns the y for the next event or the
-     * original y if the event will not fit. An event is considered to not fit
-     * if the event and its extras won't fit or if there are more events and the
-     * more events line would not fit after drawing this event.
-     *
-     * @param canvas the canvas to draw on
-     * @param event the event to draw
-     * @param x the top left corner for this event's color chip
-     * @param y the top left corner for this event's color chip
-     * @param rightEdge the rightmost point we're allowed to draw on (exclusive)
-     * @param moreEvents indicates whether additional events will follow this one
-     * @param showTimes if set, a second line with a time range will be displayed for non-all-day
-     *   events
-     * @param doDraw if set, do the actual drawing; otherwise this just computes the height
-     *   and returns
-     * @return the y for the next event or the original y if it won't fit
-     */
-    protected int drawEvent(Canvas canvas, Event event, int x, int y, int rightEdge,
-            boolean moreEvents, boolean showTimes, boolean doDraw) {
-        /*
-         * Vertical layout:
-         *   (top of box)
-         * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent
-         * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event
-         * c. [optional] Time range (mExtrasHeight)
-         * d. EVENT_LINE_PADDING
-         *
-         * Repeat (b,c,d) as needed and space allows.  If we have more events than fit, we need
-         * to leave room for something like "+2" at the bottom:
-         *
-         * e. "+ more" line (mExtrasHeight)
-         *
-         * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING)
-         *   (bottom of box)
-         */
-        final int BORDER_SPACE = EVENT_SQUARE_BORDER + 1;       // want a 1-pixel gap inside border
-        final int STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2;   // adjust bounds for stroke width
-        boolean allDay = event.allDay;
-        int eventRequiredSpace = mEventHeight;
-        if (allDay) {
-            // Add a few pixels for the box we draw around all-day events.
-            eventRequiredSpace += BORDER_SPACE * 2;
-        } else if (showTimes) {
-            // Need room for the "1pm - 2pm" line.
-            eventRequiredSpace += mExtrasHeight;
-        }
-        int reservedSpace = EVENT_BOTTOM_PADDING;   // leave a bit of room at the bottom
-        if (moreEvents) {
-            // More events follow.  Leave a bit of space between events.
-            eventRequiredSpace += EVENT_LINE_PADDING;
-
-            // Make sure we have room for the "+ more" line.  (The "+ more" line is expected
-            // to be <= the height of an event line, so we won't show "+1" when we could be
-            // showing the event.)
-            reservedSpace += mExtrasHeight;
-        }
-
-        if (y + eventRequiredSpace + reservedSpace > mHeight) {
-            // Not enough space, return original y
-            return y;
-        } else if (!doDraw) {
-            return y + eventRequiredSpace;
-        }
-
-        boolean isDeclined = event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED;
-        int color = event.color;
-        if (isDeclined) {
-            color = Utils.getDeclinedColorFromColor(color);
-        }
-
-        int textX, textY, textRightEdge;
-
-        if (allDay) {
-            // We shift the render offset "inward", because drawRect with a stroke width greater
-            // than 1 draws outside the specified bounds.  (We don't adjust the left edge, since
-            // we want to match the existing appearance of the "event square".)
-            r.left = x;
-            r.right = rightEdge - STROKE_WIDTH_ADJ;
-            r.top = y + STROKE_WIDTH_ADJ;
-            r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ;
-            textX = x + BORDER_SPACE;
-            textY = y + mEventAscentHeight + BORDER_SPACE;
-            textRightEdge = rightEdge - BORDER_SPACE;
-        } else {
-            r.left = x;
-            r.right = x + EVENT_SQUARE_WIDTH;
-            r.bottom = y + mEventAscentHeight;
-            r.top = r.bottom - EVENT_SQUARE_WIDTH;
-            textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING;
-            textY = y + mEventAscentHeight;
-            textRightEdge = rightEdge;
-        }
-
-        Style boxStyle = Style.STROKE;
-        boolean solidBackground = false;
-        if (event.selfAttendeeStatus != Attendees.ATTENDEE_STATUS_INVITED) {
-            boxStyle = Style.FILL_AND_STROKE;
-            if (allDay) {
-                solidBackground = true;
-            }
-        }
-        mEventSquarePaint.setStyle(boxStyle);
-        mEventSquarePaint.setColor(color);
-        canvas.drawRect(r, mEventSquarePaint);
-
-        float avail = textRightEdge - textX;
-        CharSequence text = TextUtils.ellipsize(
-                event.title, mEventPaint, avail, TextUtils.TruncateAt.END);
-        Paint textPaint;
-        if (solidBackground) {
-            // Text color needs to contrast with solid background.
-            textPaint = mSolidBackgroundEventPaint;
-        } else if (isDeclined) {
-            // Use "declined event" color.
-            textPaint = mDeclinedEventPaint;
-        } else if (allDay) {
-            // Text inside frame is same color as frame.
-            mFramedEventPaint.setColor(color);
-            textPaint = mFramedEventPaint;
-        } else {
-            // Use generic event text color.
-            textPaint = mEventPaint;
-        }
-        canvas.drawText(text.toString(), textX, textY, textPaint);
-        y += mEventHeight;
-        if (allDay) {
-            y += BORDER_SPACE * 2;
-        }
-
-        if (showTimes && !allDay) {
-            // show start/end time, e.g. "1pm - 2pm"
-            textY = y + mExtrasAscentHeight;
-            mStringBuilder.setLength(0);
-            text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis,
-                    event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
-                    Utils.getTimeZone(getContext(), null)).toString();
-            text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END);
-            canvas.drawText(text.toString(), textX, textY, isDeclined ? mEventDeclinedExtrasPaint
-                    : mEventExtrasPaint);
-            y += mExtrasHeight;
-        }
-
-        y += EVENT_LINE_PADDING;
-
-        return y;
-    }
-
-    protected void drawMoreEvents(Canvas canvas, int remainingEvents, int x) {
-        int y = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING);
-        String text = getContext().getResources().getQuantityString(
-                R.plurals.month_more_events, remainingEvents);
-        mEventExtrasPaint.setAntiAlias(true);
-        mEventExtrasPaint.setFakeBoldText(true);
-        canvas.drawText(String.format(text, remainingEvents), x, y, mEventExtrasPaint);
-        mEventExtrasPaint.setFakeBoldText(false);
-    }
-
-    /**
-     * Draws a line showing busy times in each day of week The method draws
-     * non-conflicting times in the event color and times with conflicting
-     * events in the dna conflict color defined in colors.
-     *
-     * @param canvas
-     */
-    protected void drawDNA(Canvas canvas) {
-        // Draw event and conflict times
-        if (mDna != null) {
-            for (Utils.DNAStrand strand : mDna.values()) {
-                if (strand.color == CONFLICT_COLOR || strand.points == null
-                        || strand.points.length == 0) {
-                    continue;
-                }
-                mDNATimePaint.setColor(strand.color);
-                canvas.drawLines(strand.points, mDNATimePaint);
-            }
-            // Draw black last to make sure it's on top
-            Utils.DNAStrand strand = mDna.get(CONFLICT_COLOR);
-            if (strand != null && strand.points != null && strand.points.length != 0) {
-                mDNATimePaint.setColor(strand.color);
-                canvas.drawLines(strand.points, mDNATimePaint);
-            }
-            if (mDayXs == null) {
-                return;
-            }
-            int numDays = mDayXs.length;
-            int xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2;
-            if (strand != null && strand.allDays != null && strand.allDays.length == numDays) {
-                for (int i = 0; i < numDays; i++) {
-                    // this adds at most 7 draws. We could sort it by color and
-                    // build an array instead but this is easier.
-                    if (strand.allDays[i] != 0) {
-                        mDNAAllDayPaint.setColor(strand.allDays[i]);
-                        canvas.drawLine(mDayXs[i] + xOffset, DNA_MARGIN, mDayXs[i] + xOffset,
-                                DNA_MARGIN + DNA_ALL_DAY_HEIGHT, mDNAAllDayPaint);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void updateSelectionPositions() {
-        if (mHasSelectedDay) {
-            int selectedPosition = mSelectedDay - mWeekStart;
-            if (selectedPosition < 0) {
-                selectedPosition += 7;
-            }
-            int effectiveWidth = mWidth - mPadding * 2;
-            effectiveWidth -= SPACING_WEEK_NUMBER;
-            mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding;
-            mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding;
-            mSelectedLeft += SPACING_WEEK_NUMBER;
-            mSelectedRight += SPACING_WEEK_NUMBER;
-        }
-    }
-
-    public int getDayIndexFromLocation(float x) {
-        int dayStart = mShowWeekNum ? SPACING_WEEK_NUMBER + mPadding : mPadding;
-        if (x < dayStart || x > mWidth - mPadding) {
-            return -1;
-        }
-        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
-        return ((int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)));
-    }
-
-    @Override
-    public Time getDayFromLocation(float x) {
-        int dayPosition = getDayIndexFromLocation(x);
-        if (dayPosition == -1) {
-            return null;
-        }
-        int day = mFirstJulianDay + dayPosition;
-
-        Time time = new Time(mTimeZone);
-        if (mWeek == 0) {
-            // This week is weird...
-            if (day < Time.EPOCH_JULIAN_DAY) {
-                day++;
-            } else if (day == Time.EPOCH_JULIAN_DAY) {
-                time.set(1, 0, 1970);
-                time.normalize(true);
-                return time;
-            }
-        }
-
-        time.setJulianDay(day);
-        return time;
-    }
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        Context context = getContext();
-        // only send accessibility events if accessibility and exploration are
-        // on.
-        AccessibilityManager am = (AccessibilityManager) context
-                .getSystemService(Service.ACCESSIBILITY_SERVICE);
-        if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
-            return super.onHoverEvent(event);
-        }
-        if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
-            Time hover = getDayFromLocation(event.getX());
-            if (hover != null
-                    && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) {
-                Long millis = hover.toMillis(true);
-                String date = Utils.formatDateRange(context, millis, millis,
-                        DateUtils.FORMAT_SHOW_DATE);
-                AccessibilityEvent accessEvent = AccessibilityEvent
-                        .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
-                accessEvent.getText().add(date);
-                if (mShowDetailsInMonth && mEvents != null) {
-                    int dayStart = SPACING_WEEK_NUMBER + mPadding;
-                    int dayPosition = (int) ((event.getX() - dayStart) * mNumDays / (mWidth
-                            - dayStart - mPadding));
-                    ArrayList<Event> events = mEvents.get(dayPosition);
-                    List<CharSequence> text = accessEvent.getText();
-                    for (Event e : events) {
-                        text.add(e.getTitleAndLocation() + ". ");
-                        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
-                        if (!e.allDay) {
-                            flags |= DateUtils.FORMAT_SHOW_TIME;
-                            if (DateFormat.is24HourFormat(context)) {
-                                flags |= DateUtils.FORMAT_24HOUR;
-                            }
-                        } else {
-                            flags |= DateUtils.FORMAT_UTC;
-                        }
-                        text.add(Utils.formatDateRange(context, e.startMillis, e.endMillis,
-                                flags) + ". ");
-                    }
-                }
-                sendAccessibilityEventUnchecked(accessEvent);
-                mLastHoverTime = hover;
-            }
-        }
-        return true;
-    }
-
-    public void setClickedDay(float xLocation) {
-        mClickedDayIndex = getDayIndexFromLocation(xLocation);
-        invalidate();
-    }
-    public void clearClickedDay() {
-        mClickedDayIndex = -1;
-        invalidate();
-    }
-}
diff --git a/src/com/android/calendar/month/MonthWeekEventsView.kt b/src/com/android/calendar/month/MonthWeekEventsView.kt
new file mode 100644
index 0000000..e4b1549
--- /dev/null
+++ b/src/com/android/calendar/month/MonthWeekEventsView.kt
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.Service
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.provider.CalendarContract.Attendees
+import android.text.TextPaint
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.MotionEvent
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import java.util.ArrayList
+import java.util.Arrays
+import java.util.Formatter
+import java.util.HashMap
+import java.util.Iterator
+import java.util.List
+import java.util.Locale
+
+class MonthWeekEventsView
+/**
+ * Shows up as an error if we don't include this.
+ */
+(context: Context) : SimpleWeekView(context) {
+    // Renamed to avoid override modifier and type mismatch error
+    protected val mTodayTime: Time = Time()
+    override protected var mHasToday = false
+    protected var mTodayIndex = -1
+    protected var mOrientation: Int = Configuration.ORIENTATION_LANDSCAPE
+    protected var mEvents: List<ArrayList<Event?>>? = null
+    protected var mUnsortedEvents: ArrayList<Event?>? = null
+    var mDna: HashMap<Int, Utils.DNAStrand>? = null
+
+    // This is for drawing the outlines around event chips and supports up to 10
+    // events being drawn on each day. The code will expand this if necessary.
+    protected var mEventOutlines: FloatRef = FloatRef(10 * 4 * 4 * 7)
+    protected var mMonthNamePaint: Paint? = null
+    protected var mEventPaint: TextPaint = TextPaint()
+    protected var mSolidBackgroundEventPaint: TextPaint? = null
+    protected var mFramedEventPaint: TextPaint? = null
+    protected var mDeclinedEventPaint: TextPaint? = null
+    protected var mEventExtrasPaint: TextPaint = TextPaint()
+    protected var mEventDeclinedExtrasPaint: TextPaint = TextPaint()
+    protected var mWeekNumPaint: Paint = Paint()
+    protected var mDNAAllDayPaint: Paint = Paint()
+    protected var mDNATimePaint: Paint = Paint()
+    protected var mEventSquarePaint: Paint = Paint()
+    protected var mTodayDrawable: Drawable? = null
+    protected var mMonthNumHeight = 0
+    protected var mMonthNumAscentHeight = 0
+    protected var mEventHeight = 0
+    protected var mEventAscentHeight = 0
+    protected var mExtrasHeight = 0
+    protected var mExtrasAscentHeight = 0
+    protected var mExtrasDescent = 0
+    protected var mWeekNumAscentHeight = 0
+    protected var mMonthBGColor = 0
+    protected var mMonthBGOtherColor = 0
+    protected var mMonthBGTodayColor = 0
+    protected var mMonthNumColor = 0
+    protected var mMonthNumOtherColor = 0
+    protected var mMonthNumTodayColor = 0
+    protected var mMonthNameColor = 0
+    protected var mMonthNameOtherColor = 0
+    protected var mMonthEventColor = 0
+    protected var mMonthDeclinedEventColor = 0
+    protected var mMonthDeclinedExtrasColor = 0
+    protected var mMonthEventExtraColor = 0
+    protected var mMonthEventOtherColor = 0
+    protected var mMonthEventExtraOtherColor = 0
+    protected var mMonthWeekNumColor = 0
+    protected var mMonthBusyBitsBgColor = 0
+    protected var mMonthBusyBitsBusyTimeColor = 0
+    protected var mMonthBusyBitsConflictTimeColor = 0
+    private var mClickedDayIndex = -1
+    private var mClickedDayColor = 0
+    protected var mEventChipOutlineColor = -0x1
+    protected var mDaySeparatorInnerColor = 0
+    protected var mTodayAnimateColor = 0
+    private var mAnimateToday = false
+    private var mAnimateTodayAlpha = 0
+    private var mTodayAnimator: ObjectAnimator? = null
+    private val mAnimatorListener: TodayAnimatorListener = TodayAnimatorListener()
+
+    internal inner class TodayAnimatorListener : AnimatorListenerAdapter() {
+        @Volatile
+        private var mAnimator: Animator? = null
+
+        @Volatile
+        private var mFadingIn = false
+        @Override
+        override fun onAnimationEnd(animation: Animator) {
+            synchronized(this) {
+                if (mAnimator !== animation) {
+                    animation.removeAllListeners()
+                    animation.cancel()
+                    return
+                }
+                if (mFadingIn) {
+                    if (mTodayAnimator != null) {
+                        mTodayAnimator?.removeAllListeners()
+                        mTodayAnimator?.cancel()
+                    }
+                    mTodayAnimator = ObjectAnimator.ofInt(this@MonthWeekEventsView,
+                            "animateTodayAlpha", 255, 0)
+                    mAnimator = mTodayAnimator
+                    mFadingIn = false
+                    mTodayAnimator?.addListener(this)
+                    mTodayAnimator?.setDuration(600)
+                    mTodayAnimator?.start()
+                } else {
+                    mAnimateToday = false
+                    mAnimateTodayAlpha = 0
+                    mAnimator?.removeAllListeners()
+                    mAnimator = null
+                    mTodayAnimator = null
+                    invalidate()
+                }
+            }
+        }
+
+        fun setAnimator(animation: Animator?) {
+            mAnimator = animation
+        }
+
+        fun setFadingIn(fadingIn: Boolean) {
+            mFadingIn = fadingIn
+        }
+    }
+
+    private var mDayXs: IntArray? = null
+
+    /**
+     * This provides a reference to a float array which allows for easy size
+     * checking and reallocation. Used for drawing lines.
+     */
+    inner class FloatRef(size: Int) {
+        var array: FloatArray
+        fun ensureSize(newSize: Int) {
+            if (newSize >= array.size) {
+                // Add enough space for 7 more boxes to be drawn
+                array = Arrays.copyOf(array, newSize + 16 * 7)
+            }
+        }
+
+        init {
+            array = FloatArray(size)
+        }
+    }
+
+    // Sets the list of events for this week. Takes a sorted list of arrays
+    // divided up by day for generating the large month version and the full
+    // arraylist sorted by start time to generate the dna version.
+    fun setEvents(sortedEvents: List<ArrayList<Event?>>?, unsortedEvents: ArrayList<Event?>?) {
+        setEvents(sortedEvents)
+        // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to
+        // generate dna bits before its width has been fixed.
+        createDna(unsortedEvents)
+    }
+
+    /**
+     * Sets up the dna bits for the view. This will return early if the view
+     * isn't in a state that will create a valid set of dna yet (such as the
+     * views width not being set correctly yet).
+     */
+    fun createDna(unsortedEvents: ArrayList<Event?>?) {
+        if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) {
+            // Stash the list of events for use when this view is ready, or
+            // just clear it if a null set has been passed to this view
+            mUnsortedEvents = unsortedEvents
+            mDna = null
+            return
+        } else {
+            // clear the cached set of events since we're ready to build it now
+            mUnsortedEvents = null
+        }
+        // Create the drawing coordinates for dna
+        if (!mShowDetailsInMonth) {
+            val numDays: Int = mEvents!!.size
+            var effectiveWidth: Int = mWidth - mPadding * 2
+            if (mShowWeekNum) {
+                effectiveWidth -= SPACING_WEEK_NUMBER
+            }
+            DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING
+            mDNAAllDayPaint?.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat())
+            mDayXs = IntArray(numDays)
+            for (day in 0 until numDays) {
+                mDayXs!![day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING
+            }
+            val top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1
+            val bottom: Int = mHeight - DNA_MARGIN
+            mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom,
+                    DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext())
+        }
+    }
+
+    fun setEvents(sortedEvents: List<ArrayList<Event?>>?) {
+        mEvents = sortedEvents
+        if (sortedEvents == null) {
+            return
+        }
+        if (sortedEvents.size !== mNumDays) {
+            if (Log.isLoggable(TAG, Log.ERROR)) {
+                Log.wtf(TAG, ("Events size must be same as days displayed: size="
+                        + sortedEvents.size) + " days=" + mNumDays)
+            }
+            mEvents = null
+            return
+        }
+    }
+
+    protected fun loadColors(context: Context) {
+        val res: Resources = context.getResources()
+        mMonthWeekNumColor = res.getColor(R.color.month_week_num_color)
+        mMonthNumColor = res.getColor(R.color.month_day_number)
+        mMonthNumOtherColor = res.getColor(R.color.month_day_number_other)
+        mMonthNumTodayColor = res.getColor(R.color.month_today_number)
+        mMonthNameColor = mMonthNumColor
+        mMonthNameOtherColor = mMonthNumOtherColor
+        mMonthEventColor = res.getColor(R.color.month_event_color)
+        mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color)
+        mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color)
+        mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color)
+        mMonthEventOtherColor = res.getColor(R.color.month_event_other_color)
+        mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color)
+        mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor)
+        mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor)
+        mMonthBGColor = res.getColor(R.color.month_bgcolor)
+        mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines)
+        mTodayAnimateColor = res.getColor(R.color.today_highlight_color)
+        mClickedDayColor = res.getColor(R.color.day_clicked_background_color)
+        mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light)
+    }
+
+    /**
+     * Sets up the text and style properties for painting. Override this if you
+     * want to use a different paint.
+     */
+    @Override
+    protected override fun initView() {
+        super.initView()
+        if (!mInitialized) {
+            val resources: Resources = getContext().getResources()
+            mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month)
+            TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title)
+            TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number)
+            SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin)
+            CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color)
+            EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color)
+            if (mScale != 1f) {
+                TOP_PADDING_MONTH_NUMBER *= mScale.toInt()
+                TOP_PADDING_WEEK_NUMBER *= mScale.toInt()
+                SIDE_PADDING_MONTH_NUMBER *= mScale.toInt()
+                SIDE_PADDING_WEEK_NUMBER *= mScale.toInt()
+                SPACING_WEEK_NUMBER *= mScale.toInt()
+                TEXT_SIZE_MONTH_NUMBER *= mScale.toInt()
+                TEXT_SIZE_EVENT *= mScale.toInt()
+                TEXT_SIZE_EVENT_TITLE *= mScale.toInt()
+                TEXT_SIZE_MORE_EVENTS *= mScale.toInt()
+                TEXT_SIZE_MONTH_NAME *= mScale.toInt()
+                TEXT_SIZE_WEEK_NUM *= mScale.toInt()
+                DAY_SEPARATOR_OUTER_WIDTH *= mScale.toInt()
+                DAY_SEPARATOR_INNER_WIDTH *= mScale.toInt()
+                DAY_SEPARATOR_VERTICAL_LENGTH *= mScale.toInt()
+                DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT *= mScale.toInt()
+                EVENT_X_OFFSET_LANDSCAPE *= mScale.toInt()
+                EVENT_Y_OFFSET_LANDSCAPE *= mScale.toInt()
+                EVENT_Y_OFFSET_PORTRAIT *= mScale.toInt()
+                EVENT_SQUARE_WIDTH *= mScale.toInt()
+                EVENT_SQUARE_BORDER *= mScale.toInt()
+                EVENT_LINE_PADDING *= mScale.toInt()
+                EVENT_BOTTOM_PADDING *= mScale.toInt()
+                EVENT_RIGHT_PADDING *= mScale.toInt()
+                DNA_MARGIN *= mScale.toInt()
+                DNA_WIDTH *= mScale.toInt()
+                DNA_ALL_DAY_HEIGHT *= mScale.toInt()
+                DNA_MIN_SEGMENT_HEIGHT *= mScale.toInt()
+                DNA_SIDE_PADDING *= mScale.toInt()
+                DEFAULT_EDGE_SPACING *= mScale.toInt()
+                DNA_ALL_DAY_WIDTH *= mScale.toInt()
+                TODAY_HIGHLIGHT_WIDTH *= mScale.toInt()
+            }
+            if (!mShowDetailsInMonth) {
+                TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN
+            }
+            mInitialized = true
+        }
+        mPadding = DEFAULT_EDGE_SPACING
+        loadColors(getContext())
+        // TODO modify paint properties depending on isMini
+        mMonthNumPaint = Paint()
+        mMonthNumPaint?.setFakeBoldText(false)
+        mMonthNumPaint?.setAntiAlias(true)
+        mMonthNumPaint?.setTextSize(TEXT_SIZE_MONTH_NUMBER.toFloat())
+        mMonthNumPaint?.setColor(mMonthNumColor)
+        mMonthNumPaint?.setStyle(Style.FILL)
+        mMonthNumPaint?.setTextAlign(Align.RIGHT)
+        mMonthNumPaint?.setTypeface(Typeface.DEFAULT)
+        mMonthNumAscentHeight = (-mMonthNumPaint!!.ascent() + 0.5f).toInt()
+        mMonthNumHeight = (mMonthNumPaint!!.descent() - mMonthNumPaint!!.ascent() + 0.5f).toInt()
+        mEventPaint = TextPaint()
+        mEventPaint?.setFakeBoldText(true)
+        mEventPaint?.setAntiAlias(true)
+        mEventPaint?.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat())
+        mEventPaint?.setColor(mMonthEventColor)
+        mSolidBackgroundEventPaint = TextPaint(mEventPaint)
+        mSolidBackgroundEventPaint?.setColor(EVENT_TEXT_COLOR)
+        mFramedEventPaint = TextPaint(mSolidBackgroundEventPaint)
+        mDeclinedEventPaint = TextPaint()
+        mDeclinedEventPaint?.setFakeBoldText(true)
+        mDeclinedEventPaint?.setAntiAlias(true)
+        mDeclinedEventPaint?.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat())
+        mDeclinedEventPaint?.setColor(mMonthDeclinedEventColor)
+        mEventAscentHeight = (-mEventPaint.ascent() + 0.5f).toInt()
+        mEventHeight = (mEventPaint.descent() - mEventPaint.ascent() + 0.5f).toInt()
+        mEventExtrasPaint = TextPaint()
+        mEventExtrasPaint?.setFakeBoldText(false)
+        mEventExtrasPaint?.setAntiAlias(true)
+        mEventExtrasPaint?.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+        mEventExtrasPaint?.setTextSize(TEXT_SIZE_EVENT.toFloat())
+        mEventExtrasPaint?.setColor(mMonthEventExtraColor)
+        mEventExtrasPaint?.setStyle(Style.FILL)
+        mEventExtrasPaint?.setTextAlign(Align.LEFT)
+        mExtrasHeight = (mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f).toInt()
+        mExtrasAscentHeight = (-mEventExtrasPaint.ascent() + 0.5f).toInt()
+        mExtrasDescent = (mEventExtrasPaint.descent() + 0.5f).toInt()
+        mEventDeclinedExtrasPaint = TextPaint()
+        mEventDeclinedExtrasPaint.setFakeBoldText(false)
+        mEventDeclinedExtrasPaint.setAntiAlias(true)
+        mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+        mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT.toFloat())
+        mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor)
+        mEventDeclinedExtrasPaint.setStyle(Style.FILL)
+        mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT)
+        mWeekNumPaint = Paint()
+        mWeekNumPaint.setFakeBoldText(false)
+        mWeekNumPaint.setAntiAlias(true)
+        mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM.toFloat())
+        mWeekNumPaint.setColor(mWeekNumColor)
+        mWeekNumPaint.setStyle(Style.FILL)
+        mWeekNumPaint.setTextAlign(Align.RIGHT)
+        mWeekNumAscentHeight = (-mWeekNumPaint.ascent() + 0.5f).toInt()
+        mDNAAllDayPaint = Paint()
+        mDNATimePaint = Paint()
+        mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor)
+        mDNATimePaint.setStyle(Style.FILL_AND_STROKE)
+        mDNATimePaint.setStrokeWidth(DNA_WIDTH.toFloat())
+        mDNATimePaint.setAntiAlias(false)
+        mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor)
+        mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE)
+        mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat())
+        mDNAAllDayPaint.setAntiAlias(false)
+        mEventSquarePaint = Paint()
+        mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat())
+        mEventSquarePaint.setAntiAlias(false)
+        if (DEBUG_LAYOUT) {
+            Log.d("EXTRA", "mScale=$mScale")
+            Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint?.ascent()
+                    ?.toString() + " descent=" + mMonthNumPaint?.descent()?.toString() +
+                    " int height=" + mMonthNumHeight)
+            Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint?.ascent()
+                    ?.toString() + " descent=" + mEventPaint.descent().toString() +
+                    " int height=" + mEventHeight
+                    .toString() + " int ascent=" + mEventAscentHeight)
+            Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent()
+                    .toString() + " descent=" + mEventExtrasPaint.descent().toString() +
+                    " int height=" + mExtrasHeight)
+            Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent()
+                    .toString() + " descent=" + mWeekNumPaint.descent())
+        }
+    }
+
+    @Override
+    override fun setWeekParams(params: HashMap<String?, Int?>, tz: String) {
+        super.setWeekParams(params, tz)
+        if (params.containsKey(VIEW_PARAMS_ORIENTATION)) {
+            mOrientation = params.get(VIEW_PARAMS_ORIENTATION) ?:
+                    Configuration.ORIENTATION_LANDSCAPE
+        }
+        updateToday(tz)
+        mNumCells = mNumDays + 1
+        if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) {
+            synchronized(mAnimatorListener) {
+                if (mTodayAnimator != null) {
+                    mTodayAnimator?.removeAllListeners()
+                    mTodayAnimator?.cancel()
+                }
+                mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha",
+                        Math.max(mAnimateTodayAlpha, 80), 255)
+                mTodayAnimator?.setDuration(150)
+                mAnimatorListener.setAnimator(mTodayAnimator)
+                mAnimatorListener.setFadingIn(true)
+                mTodayAnimator?.addListener(mAnimatorListener)
+                mAnimateToday = true
+                mTodayAnimator?.start()
+            }
+        }
+    }
+
+    /**
+     * @param tz
+     */
+    fun updateToday(tz: String): Boolean {
+        mTodayTime.timezone = tz
+        mTodayTime.setToNow()
+        mTodayTime.normalize(true)
+        val julianToday: Int = Time.getJulianDay(mTodayTime.toMillis(false), mTodayTime.gmtoff)
+        if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) {
+            mHasToday = true
+            mTodayIndex = julianToday - mFirstJulianDay
+        } else {
+            mHasToday = false
+            mTodayIndex = -1
+        }
+        return mHasToday
+    }
+
+    fun setAnimateTodayAlpha(alpha: Int) {
+        mAnimateTodayAlpha = alpha
+        invalidate()
+    }
+
+    @Override
+    protected override fun onDraw(canvas: Canvas) {
+        drawBackground(canvas)
+        drawWeekNums(canvas)
+        drawDaySeparators(canvas)
+        if (mHasToday && mAnimateToday) {
+            drawToday(canvas)
+        }
+        if (mShowDetailsInMonth) {
+            drawEvents(canvas)
+        } else {
+            if (mDna == null && mUnsortedEvents != null) {
+                createDna(mUnsortedEvents)
+            }
+            drawDNA(canvas)
+        }
+        drawClick(canvas)
+    }
+
+    protected fun drawToday(canvas: Canvas) {
+        r.top = DAY_SEPARATOR_INNER_WIDTH + TODAY_HIGHLIGHT_WIDTH / 2
+        r.bottom = mHeight - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt()
+        p.setStyle(Style.STROKE)
+        p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH.toFloat())
+        r.left = computeDayLeftPosition(mTodayIndex) + TODAY_HIGHLIGHT_WIDTH / 2
+        r.right = (computeDayLeftPosition(mTodayIndex + 1)
+                - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt())
+        p.setColor(mTodayAnimateColor or (mAnimateTodayAlpha shl 24))
+        canvas.drawRect(r, p)
+        p.setStyle(Style.FILL)
+    }
+
+    // TODO move into SimpleWeekView
+    // Computes the x position for the left side of the given day
+    private fun computeDayLeftPosition(day: Int): Int {
+        var effectiveWidth: Int = mWidth
+        var x = 0
+        var xOffset = 0
+        if (mShowWeekNum) {
+            xOffset = SPACING_WEEK_NUMBER + mPadding
+            effectiveWidth -= xOffset
+        }
+        x = day * effectiveWidth / mNumDays + xOffset
+        return x
+    }
+
+    @Override
+    protected override fun drawDaySeparators(canvas: Canvas) {
+        val lines = FloatArray(8 * 4)
+        var count = 6 * 4
+        var wkNumOffset = 0
+        var i = 0
+        if (mShowWeekNum) {
+            // This adds the first line separating the week number
+            val xOffset: Int = SPACING_WEEK_NUMBER + mPadding
+            count += 4
+            lines[i++] = xOffset.toFloat()
+            lines[i++] = 0f
+            lines[i++] = xOffset.toFloat()
+            lines[i++] = mHeight.toFloat()
+            wkNumOffset++
+        }
+        count += 4
+        lines[i++] = 0f
+        lines[i++] = 0f
+        lines[i++] = mWidth.toFloat()
+        lines[i++] = 0f
+        val y0 = 0
+        val y1: Int = mHeight
+        while (i < count) {
+            val x = computeDayLeftPosition(i / 4 - wkNumOffset)
+            lines[i++] = x.toFloat()
+            lines[i++] = y0.toFloat()
+            lines[i++] = x.toFloat()
+            lines[i++] = y1.toFloat()
+        }
+        p.setColor(mDaySeparatorInnerColor)
+        p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH.toFloat())
+        canvas.drawLines(lines, 0, count, p)
+    }
+
+    @Override
+    protected override fun drawBackground(canvas: Canvas) {
+        var i = 0
+        var offset = 0
+        r.top = DAY_SEPARATOR_INNER_WIDTH
+        r.bottom = mHeight
+        if (mShowWeekNum) {
+            i++
+            offset++
+        }
+        if (!mOddMonth!!.get(i)) {
+            while (++i < mOddMonth!!.size && !mOddMonth!!.get(i));
+            r.right = computeDayLeftPosition(i - offset)
+            r.left = 0
+            p.setColor(mMonthBGOtherColor)
+            canvas.drawRect(r, p)
+            // compute left edge for i, set up r, draw
+        } else if (!mOddMonth!!.get(mOddMonth!!.size - 1.also { i = it })) {
+            while (--i >= offset && !mOddMonth!!.get(i));
+            i++
+            // compute left edge for i, set up r, draw
+            r.right = mWidth
+            r.left = computeDayLeftPosition(i - offset)
+            p.setColor(mMonthBGOtherColor)
+            canvas.drawRect(r, p)
+        }
+        if (mHasToday) {
+            p.setColor(mMonthBGTodayColor)
+            r.left = computeDayLeftPosition(mTodayIndex)
+            r.right = computeDayLeftPosition(mTodayIndex + 1)
+            canvas.drawRect(r, p)
+        }
+    }
+
+    // Draw the "clicked" color on the tapped day
+    private fun drawClick(canvas: Canvas) {
+        if (mClickedDayIndex != -1) {
+            val alpha: Int = p.getAlpha()
+            p.setColor(mClickedDayColor)
+            p.setAlpha(mClickedAlpha)
+            r.left = computeDayLeftPosition(mClickedDayIndex)
+            r.right = computeDayLeftPosition(mClickedDayIndex + 1)
+            r.top = DAY_SEPARATOR_INNER_WIDTH
+            r.bottom = mHeight
+            canvas.drawRect(r, p)
+            p.setAlpha(alpha)
+        }
+    }
+
+    @Override
+    protected override fun drawWeekNums(canvas: Canvas) {
+        var y: Int
+        var i = 0
+        var offset = -1
+        var todayIndex = mTodayIndex
+        var x = 0
+        var numCount: Int = mNumDays
+        if (mShowWeekNum) {
+            x = SIDE_PADDING_WEEK_NUMBER + mPadding
+            y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER
+            canvas.drawText(mDayNumbers!!.get(0) as String, x.toFloat(), y.toFloat(), mWeekNumPaint)
+            numCount++
+            i++
+            todayIndex++
+            offset++
+        }
+        y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER
+        var isFocusMonth: Boolean = mFocusDay!!.get(i)
+        var isBold = false
+        mMonthNumPaint?.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor)
+        while (i < numCount) {
+            if (mHasToday && todayIndex == i) {
+                mMonthNumPaint?.setColor(mMonthNumTodayColor)
+                mMonthNumPaint?.setFakeBoldText(true.also { isBold = it })
+                if (i + 1 < numCount) {
+                    // Make sure the color will be set back on the next
+                    // iteration
+                    isFocusMonth = !mFocusDay!!.get(i + 1)
+                }
+            } else if (mFocusDay?.get(i) !== isFocusMonth) {
+                isFocusMonth = mFocusDay!!.get(i)
+                mMonthNumPaint?.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor)
+            }
+            x = computeDayLeftPosition(i - offset) - SIDE_PADDING_MONTH_NUMBER
+            canvas.drawText(mDayNumbers!!.get(i) as String, x.toFloat(), y.toFloat(),
+                    mMonthNumPaint as Paint)
+            if (isBold) {
+                mMonthNumPaint?.setFakeBoldText(false.also { isBold = it })
+            }
+            i++
+        }
+    }
+
+    protected fun drawEvents(canvas: Canvas) {
+        if (mEvents == null) {
+            return
+        }
+        var day = -1
+        for (eventDay in mEvents!!) {
+            day++
+            if (eventDay == null || eventDay.size === 0) {
+                continue
+            }
+            var ySquare: Int
+            val xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1
+            var rightEdge = computeDayLeftPosition(day + 1)
+            if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
+                ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER
+                rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1
+            } else {
+                ySquare = EVENT_Y_OFFSET_LANDSCAPE
+                rightEdge -= EVENT_X_OFFSET_LANDSCAPE
+            }
+
+            // Determine if everything will fit when time ranges are shown.
+            var showTimes = true
+            var iter: Iterator<Event> = eventDay.iterator() as Iterator<Event>
+            var yTest = ySquare
+            while (iter.hasNext()) {
+                val event: Event = iter.next()
+                val newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(),
+                        showTimes,  /*doDraw*/false)
+                if (newY == yTest) {
+                    showTimes = false
+                    break
+                }
+                yTest = newY
+            }
+            var eventCount = 0
+            iter = eventDay.iterator() as Iterator<Event>
+            while (iter.hasNext()) {
+                val event: Event = iter.next()
+                val newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(),
+                        showTimes,  /*doDraw*/true)
+                if (newY == ySquare) {
+                    break
+                }
+                eventCount++
+                ySquare = newY
+            }
+            val remaining: Int = eventDay.size- eventCount
+            if (remaining > 0) {
+                drawMoreEvents(canvas, remaining, xSquare)
+            }
+        }
+    }
+
+    protected fun addChipOutline(lines: FloatRef, count: Int, x: Int, y: Int): Int {
+        var count = count
+        lines.ensureSize(count + 16)
+        // top of box
+        lines.array[count++] = x.toFloat()
+        lines.array[count++] = y.toFloat()
+        lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+        lines.array[count++] = y.toFloat()
+        // right side of box
+        lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+        lines.array[count++] = y.toFloat()
+        lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat()
+        lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+        // left side of box
+        lines.array[count++] = x.toFloat()
+        lines.array[count++] = y.toFloat()
+        lines.array[count++] = x.toFloat()
+        lines.array[count++] = (y + EVENT_SQUARE_WIDTH + 1).toFloat()
+        // bottom of box
+        lines.array[count++] = x.toFloat()
+        lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+        lines.array[count++] = (x + EVENT_SQUARE_WIDTH + 1).toFloat()
+        lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat()
+        return count
+    }
+
+    /**
+     * Attempts to draw the given event. Returns the y for the next event or the
+     * original y if the event will not fit. An event is considered to not fit
+     * if the event and its extras won't fit or if there are more events and the
+     * more events line would not fit after drawing this event.
+     *
+     * @param canvas the canvas to draw on
+     * @param event the event to draw
+     * @param x the top left corner for this event's color chip
+     * @param y the top left corner for this event's color chip
+     * @param rightEdge the rightmost point we're allowed to draw on (exclusive)
+     * @param moreEvents indicates whether additional events will follow this one
+     * @param showTimes if set, a second line with a time range will be displayed for non-all-day
+     * events
+     * @param doDraw if set, do the actual drawing; otherwise this just computes the height
+     * and returns
+     * @return the y for the next event or the original y if it won't fit
+     */
+    protected fun drawEvent(canvas: Canvas, event: Event, x: Int, y: Int, rightEdge: Int,
+                            moreEvents: Boolean, showTimes: Boolean, doDraw: Boolean): Int {
+        /*
+         * Vertical layout:
+         *   (top of box)
+         * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent
+         * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event
+         * c. [optional] Time range (mExtrasHeight)
+         * d. EVENT_LINE_PADDING
+         *
+         * Repeat (b,c,d) as needed and space allows.  If we have more events than fit, we need
+         * to leave room for something like "+2" at the bottom:
+         *
+         * e. "+ more" line (mExtrasHeight)
+         *
+         * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING)
+         *   (bottom of box)
+         */
+        var y = y
+        val BORDER_SPACE = EVENT_SQUARE_BORDER + 1 // want a 1-pixel gap inside border
+        val STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2 // adjust bounds for stroke width
+        val allDay: Boolean = event.allDay
+        var eventRequiredSpace = mEventHeight
+        if (allDay) {
+            // Add a few pixels for the box we draw around all-day events.
+            eventRequiredSpace += BORDER_SPACE * 2
+        } else if (showTimes) {
+            // Need room for the "1pm - 2pm" line.
+            eventRequiredSpace += mExtrasHeight
+        }
+        var reservedSpace = EVENT_BOTTOM_PADDING // leave a bit of room at the bottom
+        if (moreEvents) {
+            // More events follow.  Leave a bit of space between events.
+            eventRequiredSpace += EVENT_LINE_PADDING
+
+            // Make sure we have room for the "+ more" line.  (The "+ more" line is expected
+            // to be <= the height of an event line, so we won't show "+1" when we could be
+            // showing the event.)
+            reservedSpace += mExtrasHeight
+        }
+        if (y + eventRequiredSpace + reservedSpace > mHeight) {
+            // Not enough space, return original y
+            return y
+        } else if (!doDraw) {
+            return y + eventRequiredSpace
+        }
+        val isDeclined = event.selfAttendeeStatus === Attendees.ATTENDEE_STATUS_DECLINED
+        var color: Int = event.color
+        if (isDeclined) {
+            color = Utils.getDeclinedColorFromColor(color)
+        }
+        val textX: Int
+        var textY: Int
+        val textRightEdge: Int
+        if (allDay) {
+            // We shift the render offset "inward", because drawRect with a stroke width greater
+            // than 1 draws outside the specified bounds.  (We don't adjust the left edge, since
+            // we want to match the existing appearance of the "event square".)
+            r.left = x
+            r.right = rightEdge - STROKE_WIDTH_ADJ
+            r.top = y + STROKE_WIDTH_ADJ
+            r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ
+            textX = x + BORDER_SPACE
+            textY = y + mEventAscentHeight + BORDER_SPACE
+            textRightEdge = rightEdge - BORDER_SPACE
+        } else {
+            r.left = x
+            r.right = x + EVENT_SQUARE_WIDTH
+            r.bottom = y + mEventAscentHeight
+            r.top = r.bottom - EVENT_SQUARE_WIDTH
+            textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING
+            textY = y + mEventAscentHeight
+            textRightEdge = rightEdge
+        }
+        var boxStyle: Style = Style.STROKE
+        var solidBackground = false
+        if (event.selfAttendeeStatus !== Attendees.ATTENDEE_STATUS_INVITED) {
+            boxStyle = Style.FILL_AND_STROKE
+            if (allDay) {
+                solidBackground = true
+            }
+        }
+        mEventSquarePaint.setStyle(boxStyle)
+        mEventSquarePaint.setColor(color)
+        canvas.drawRect(r, mEventSquarePaint)
+        val avail = (textRightEdge - textX).toFloat()
+        var text: CharSequence = TextUtils.ellipsize(
+                event.title, mEventPaint, avail, TextUtils.TruncateAt.END)
+        val textPaint: TextPaint?
+        textPaint = if (solidBackground) {
+            // Text color needs to contrast with solid background.
+            mSolidBackgroundEventPaint
+        } else if (isDeclined) {
+            // Use "declined event" color.
+            mDeclinedEventPaint
+        } else if (allDay) {
+            // Text inside frame is same color as frame.
+            mFramedEventPaint?.setColor(color)
+            mFramedEventPaint
+        } else {
+            // Use generic event text color.
+            mEventPaint
+        }
+        canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(), textPaint as Paint)
+        y += mEventHeight
+        if (allDay) {
+            y += BORDER_SPACE * 2
+        }
+        if (showTimes && !allDay) {
+            // show start/end time, e.g. "1pm - 2pm"
+            textY = y + mExtrasAscentHeight
+            mStringBuilder.setLength(0)
+            text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis,
+                    event.endMillis, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL,
+                    Utils.getTimeZone(getContext(), null)).toString()
+            text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END)
+            canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(),
+                    if (isDeclined) mEventDeclinedExtrasPaint else mEventExtrasPaint)
+            y += mExtrasHeight
+        }
+        y += EVENT_LINE_PADDING
+        return y
+    }
+
+    protected fun drawMoreEvents(canvas: Canvas, remainingEvents: Int, x: Int) {
+        val y: Int = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING)
+        val text: String = getContext().getResources().getQuantityString(
+                R.plurals.month_more_events, remainingEvents)
+        mEventExtrasPaint.setAntiAlias(true)
+        mEventExtrasPaint.setFakeBoldText(true)
+        canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(),
+                mEventExtrasPaint as Paint)
+        mEventExtrasPaint!!.setFakeBoldText(false)
+    }
+
+    /**
+     * Draws a line showing busy times in each day of week The method draws
+     * non-conflicting times in the event color and times with conflicting
+     * events in the dna conflict color defined in colors.
+     *
+     * @param canvas
+     */
+    protected fun drawDNA(canvas: Canvas) {
+        // Draw event and conflict times
+        if (mDna != null) {
+            for (strand in mDna!!.values) {
+                if (strand.color === CONFLICT_COLOR || strand.points == null ||
+                        (strand.points as FloatArray).size === 0) {
+                    continue
+                }
+                mDNATimePaint!!.setColor(strand.color)
+                canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint)
+            }
+            // Draw black last to make sure it's on top
+            val strand: Utils.DNAStrand? = mDna?.get(CONFLICT_COLOR)
+            if (strand != null && strand!!.points != null && strand!!.points?.size !== 0) {
+                mDNATimePaint!!.setColor(strand.color)
+                canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint)
+            }
+            if (mDayXs == null) {
+                return
+            }
+            val numDays = mDayXs!!.size
+            val xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2
+            if (strand != null && strand!!.allDays != null && strand!!.allDays?.size === numDays) {
+                for (i in 0 until numDays) {
+                    // this adds at most 7 draws. We could sort it by color and
+                    // build an array instead but this is easier.
+                    if (strand!!.allDays?.get(i) !== 0) {
+                        mDNAAllDayPaint!!.setColor(strand!!.allDays!!.get(i))
+                        canvas.drawLine(mDayXs!![i].toFloat() + xOffset.toFloat(),
+                                DNA_MARGIN.toFloat(), mDayXs!![i].toFloat() + xOffset.toFloat(),
+                                DNA_MARGIN.toFloat() + DNA_ALL_DAY_HEIGHT.toFloat(),
+                                mDNAAllDayPaint as Paint)
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected override fun updateSelectionPositions() {
+        if (mHasSelectedDay) {
+            var selectedPosition: Int = mSelectedDay - mWeekStart
+            if (selectedPosition < 0) {
+                selectedPosition += 7
+            }
+            var effectiveWidth: Int = mWidth - mPadding * 2
+            effectiveWidth -= SPACING_WEEK_NUMBER
+            mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding
+            mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding
+            mSelectedLeft += SPACING_WEEK_NUMBER
+            mSelectedRight += SPACING_WEEK_NUMBER
+        }
+    }
+
+    fun getDayIndexFromLocation(x: Float): Int {
+        val dayStart: Int = if (mShowWeekNum) SPACING_WEEK_NUMBER + mPadding else mPadding
+        return if (x < dayStart || x > mWidth - mPadding) {
+            -1
+        } else (((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)).toInt())
+        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+    }
+
+    @Override
+    override fun getDayFromLocation(x: Float): Time? {
+        val dayPosition = getDayIndexFromLocation(x)
+        if (dayPosition == -1) {
+            return null
+        }
+        var day: Int = mFirstJulianDay + dayPosition
+        val time = Time(mTimeZone)
+        if (mWeek === 0) {
+            // This week is weird...
+            if (day < Time.EPOCH_JULIAN_DAY) {
+                day++
+            } else if (day == Time.EPOCH_JULIAN_DAY) {
+                time.set(1, 0, 1970)
+                time.normalize(true)
+                return time
+            }
+        }
+        time.setJulianDay(day)
+        return time
+    }
+
+    @Override
+    override fun onHoverEvent(event: MotionEvent): Boolean {
+        val context: Context = getContext()
+        // only send accessibility events if accessibility and exploration are
+        // on.
+        val am: AccessibilityManager = context
+                .getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
+            return super.onHoverEvent(event)
+        }
+        if (event.getAction() !== MotionEvent.ACTION_HOVER_EXIT) {
+            val hover: Time? = getDayFromLocation(event.getX())
+            if (hover != null
+                    && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) !== 0)) {
+                val millis: Long = hover.toMillis(true)
+                val date: String = Utils!!.formatDateRange(context, millis, millis,
+                        DateUtils.FORMAT_SHOW_DATE) as String
+                val accessEvent: AccessibilityEvent = AccessibilityEvent
+                        .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
+                accessEvent.getText().add(date)
+                if (mShowDetailsInMonth && mEvents != null) {
+                    val dayStart: Int = SPACING_WEEK_NUMBER + mPadding
+                    val dayPosition = ((event.getX() - dayStart) * mNumDays / (mWidth
+                            - dayStart - mPadding)).toInt()
+                    val events: ArrayList<Event?> = mEvents!![dayPosition]
+                    val text: List<CharSequence> = accessEvent.getText() as List<CharSequence>
+                    for (e in events) {
+                        text.add(e!!.titleAndLocation.toString() + ". ")
+                        var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
+                        if (!e!!.allDay) {
+                            flags = flags or DateUtils.FORMAT_SHOW_TIME
+                            if (DateFormat.is24HourFormat(context)) {
+                                flags = flags or DateUtils.FORMAT_24HOUR
+                            }
+                        } else {
+                            flags = flags or DateUtils.FORMAT_UTC
+                        }
+                        text.add(Utils.formatDateRange(context, e!!.startMillis, e!!.endMillis,
+                                flags).toString() + ". ")
+                    }
+                }
+                sendAccessibilityEventUnchecked(accessEvent)
+                mLastHoverTime = hover
+            }
+        }
+        return true
+    }
+
+    fun setClickedDay(xLocation: Float) {
+        mClickedDayIndex = getDayIndexFromLocation(xLocation)
+        invalidate()
+    }
+
+    fun clearClickedDay() {
+        mClickedDayIndex = -1
+        invalidate()
+    }
+
+    companion object {
+        private const val TAG = "MonthView"
+        private const val DEBUG_LAYOUT = false
+        const val VIEW_PARAMS_ORIENTATION = "orientation"
+        const val VIEW_PARAMS_ANIMATE_TODAY = "animate_today"
+
+        /* NOTE: these are not constants, and may be multiplied by a scale factor */
+        private var TEXT_SIZE_MONTH_NUMBER = 32
+        private var TEXT_SIZE_EVENT = 12
+        private var TEXT_SIZE_EVENT_TITLE = 14
+        private var TEXT_SIZE_MORE_EVENTS = 12
+        private var TEXT_SIZE_MONTH_NAME = 14
+        private var TEXT_SIZE_WEEK_NUM = 12
+        private var DNA_MARGIN = 4
+        private var DNA_ALL_DAY_HEIGHT = 4
+        private var DNA_MIN_SEGMENT_HEIGHT = 4
+        private var DNA_WIDTH = 8
+        private var DNA_ALL_DAY_WIDTH = 32
+        private var DNA_SIDE_PADDING = 6
+        private var CONFLICT_COLOR: Int = Color.BLACK
+        private var EVENT_TEXT_COLOR: Int = Color.WHITE
+        private var DEFAULT_EDGE_SPACING = 0
+        private var SIDE_PADDING_MONTH_NUMBER = 4
+        private var TOP_PADDING_MONTH_NUMBER = 4
+        private var TOP_PADDING_WEEK_NUMBER = 4
+        private var SIDE_PADDING_WEEK_NUMBER = 20
+        private var DAY_SEPARATOR_OUTER_WIDTH = 0
+        private var DAY_SEPARATOR_INNER_WIDTH = 1
+        private var DAY_SEPARATOR_VERTICAL_LENGTH = 53
+        private var DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT = 64
+        private const val MIN_WEEK_WIDTH = 50
+        private var EVENT_X_OFFSET_LANDSCAPE = 38
+        private var EVENT_Y_OFFSET_LANDSCAPE = 8
+        private var EVENT_Y_OFFSET_PORTRAIT = 7
+        private var EVENT_SQUARE_WIDTH = 10
+        private var EVENT_SQUARE_BORDER = 2
+        private var EVENT_LINE_PADDING = 2
+        private var EVENT_RIGHT_PADDING = 4
+        private var EVENT_BOTTOM_PADDING = 3
+        private var TODAY_HIGHLIGHT_WIDTH = 2
+        private var SPACING_WEEK_NUMBER = 24
+        private var mInitialized = false
+        private var mShowDetailsInMonth = false
+        protected var mStringBuilder: StringBuilder = StringBuilder(50)
+
+        // TODO recreate formatter when locale changes
+        protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault())
+        private const val mClickedAlpha = 128
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.java b/src/com/android/calendar/month/SimpleDayPickerFragment.java
deleted file mode 100644
index 2efae6a..0000000
--- a/src/com/android/calendar/month/SimpleDayPickerFragment.java
+++ /dev/null
@@ -1,612 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.app.Activity;
-import android.app.ListFragment;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * <p>
- * This displays a titled list of weeks with selectable days. It can be
- * configured to display the week number, start the week on a given day, show a
- * reduced number of days, or display an arbitrary number of weeks at a time. By
- * overriding methods and changing variables this fragment can be customized to
- * easily display a month selection component in a given style.
- * </p>
- */
-public class SimpleDayPickerFragment extends ListFragment implements OnScrollListener {
-
-    private static final String TAG = "MonthFragment";
-    private static final String KEY_CURRENT_TIME = "current_time";
-
-    // Affects when the month selection will change while scrolling up
-    protected static final int SCROLL_HYST_WEEKS = 2;
-    // How long the GoTo fling animation should last
-    protected static final int GOTO_SCROLL_DURATION = 500;
-    // How long to wait after receiving an onScrollStateChanged notification
-    // before acting on it
-    protected static final int SCROLL_CHANGE_DELAY = 40;
-    // The number of days to display in each week
-    public static final int DAYS_PER_WEEK = 7;
-    // The size of the month name displayed above the week list
-    protected static final int MINI_MONTH_NAME_TEXT_SIZE = 18;
-    public static int LIST_TOP_OFFSET = -1;  // so that the top line will be under the separator
-    protected int WEEK_MIN_VISIBLE_HEIGHT = 12;
-    protected int BOTTOM_BUFFER = 20;
-    protected int mSaturdayColor = 0;
-    protected int mSundayColor = 0;
-    protected int mDayNameColor = 0;
-
-    // You can override these numbers to get a different appearance
-    protected int mNumWeeks = 6;
-    protected boolean mShowWeekNumber = false;
-    protected int mDaysPerWeek = 7;
-
-    // These affect the scroll speed and feel
-    protected float mFriction = 1.0f;
-
-    protected Context mContext;
-    protected Handler mHandler;
-
-    protected float mMinimumFlingVelocity;
-
-    // highlighted time
-    protected Time mSelectedDay = new Time();
-    protected SimpleWeeksAdapter mAdapter;
-    protected ListView mListView;
-    protected ViewGroup mDayNamesHeader;
-    protected String[] mDayLabels;
-
-    // disposable variable used for time calculations
-    protected Time mTempTime = new Time();
-
-    private static float mScale = 0;
-    // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
-    protected int mFirstDayOfWeek;
-    // The first day of the focus month
-    protected Time mFirstDayOfMonth = new Time();
-    // The first day that is visible in the view
-    protected Time mFirstVisibleDay = new Time();
-    // The name of the month to display
-    protected TextView mMonthName;
-    // The last name announced by accessibility
-    protected CharSequence mPrevMonthName;
-    // which month should be displayed/highlighted [0-11]
-    protected int mCurrentMonthDisplayed;
-    // used for tracking during a scroll
-    protected long mPreviousScrollPosition;
-    // used for tracking which direction the view is scrolling
-    protected boolean mIsScrollingUp = false;
-    // used for tracking what state listview is in
-    protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-    // used for tracking what state listview is in
-    protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
-    // This causes an update of the view at midnight
-    protected Runnable mTodayUpdater = new Runnable() {
-        @Override
-        public void run() {
-            Time midnight = new Time(mFirstVisibleDay.timezone);
-            midnight.setToNow();
-            long currentMillis = midnight.toMillis(true);
-
-            midnight.hour = 0;
-            midnight.minute = 0;
-            midnight.second = 0;
-            midnight.monthDay++;
-            long millisToMidnight = midnight.normalize(true) - currentMillis;
-            mHandler.postDelayed(this, millisToMidnight);
-
-            if (mAdapter != null) {
-                mAdapter.notifyDataSetChanged();
-            }
-        }
-    };
-
-    // This allows us to update our position when a day is tapped
-    protected DataSetObserver mObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            Time day = mAdapter.getSelectedDay();
-            if (day.year != mSelectedDay.year || day.yearDay != mSelectedDay.yearDay) {
-                goTo(day.toMillis(true), true, true, false);
-            }
-        }
-    };
-
-    public SimpleDayPickerFragment(long initialTime) {
-        goTo(initialTime, false, true, true);
-        mHandler = new Handler();
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mContext = activity;
-        String tz = Time.getCurrentTimezone();
-        ViewConfiguration viewConfig = ViewConfiguration.get(activity);
-        mMinimumFlingVelocity = viewConfig.getScaledMinimumFlingVelocity();
-
-        // Ensure we're in the correct time zone
-        mSelectedDay.switchTimezone(tz);
-        mSelectedDay.normalize(true);
-        mFirstDayOfMonth.timezone = tz;
-        mFirstDayOfMonth.normalize(true);
-        mFirstVisibleDay.timezone = tz;
-        mFirstVisibleDay.normalize(true);
-        mTempTime.timezone = tz;
-
-        Resources res = activity.getResources();
-        mSaturdayColor = res.getColor(R.color.month_saturday);
-        mSundayColor = res.getColor(R.color.month_sunday);
-        mDayNameColor = res.getColor(R.color.month_day_names_color);
-
-        // Adjust sizes for screen density
-        if (mScale == 0) {
-            mScale = activity.getResources().getDisplayMetrics().density;
-            if (mScale != 1) {
-                WEEK_MIN_VISIBLE_HEIGHT *= mScale;
-                BOTTOM_BUFFER *= mScale;
-                LIST_TOP_OFFSET *= mScale;
-            }
-        }
-        setUpAdapter();
-        setListAdapter(mAdapter);
-    }
-
-    /**
-     * Creates a new adapter if necessary and sets up its parameters. Override
-     * this method to provide a custom adapter.
-     */
-    protected void setUpAdapter() {
-        HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
-        weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
-                Time.getJulianDay(mSelectedDay.toMillis(false), mSelectedDay.gmtoff));
-        if (mAdapter == null) {
-            mAdapter = new SimpleWeeksAdapter(getActivity(), weekParams);
-            mAdapter.registerDataSetObserver(mObserver);
-        } else {
-            mAdapter.updateParams(weekParams);
-        }
-        // refresh the view with the new parameters
-        mAdapter.notifyDataSetChanged();
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        setUpListView();
-        setUpHeader();
-
-        mMonthName = (TextView) getView().findViewById(R.id.month_name);
-        SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
-        if (child == null) {
-            return;
-        }
-        int julianDay = child.getFirstJulianDay();
-        mFirstVisibleDay.setJulianDay(julianDay);
-        // set the title to the month of the second week
-        mTempTime.setJulianDay(julianDay + DAYS_PER_WEEK);
-        setMonthDisplayed(mTempTime, true);
-    }
-
-    /**
-     * Sets up the strings to be used by the header. Override this method to use
-     * different strings or modify the view params.
-     */
-    protected void setUpHeader() {
-        mDayLabels = new String[7];
-        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
-            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
-                    DateUtils.LENGTH_SHORTEST).toUpperCase();
-        }
-    }
-
-    /**
-     * Sets all the required fields for the list view. Override this method to
-     * set a different list view behavior.
-     */
-    protected void setUpListView() {
-        // Configure the listview
-        mListView = getListView();
-        // Transparent background on scroll
-        mListView.setCacheColorHint(0);
-        // No dividers
-        mListView.setDivider(null);
-        // Items are clickable
-        mListView.setItemsCanFocus(true);
-        // The thumb gets in the way, so disable it
-        mListView.setFastScrollEnabled(false);
-        mListView.setVerticalScrollBarEnabled(false);
-        mListView.setOnScrollListener(this);
-        mListView.setFadingEdgeLength(0);
-        // Make the scrolling behavior nicer
-        mListView.setFriction(ViewConfiguration.getScrollFriction() * mFriction);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        setUpAdapter();
-        doResumeUpdates();
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mHandler.removeCallbacks(mTodayUpdater);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true));
-    }
-
-    /**
-     * Updates the user preference fields. Override this to use a different
-     * preference space.
-     */
-    protected void doResumeUpdates() {
-        // Get default week start based on locale, subtracting one for use with android Time.
-        Calendar cal = Calendar.getInstance(Locale.getDefault());
-        mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
-
-        mShowWeekNumber = false;
-
-        updateHeader();
-        goTo(mSelectedDay.toMillis(true), false, false, false);
-        mAdapter.setSelectedDay(mSelectedDay);
-        mTodayUpdater.run();
-    }
-
-    /**
-     * Fixes the day names header to provide correct spacing and updates the
-     * label text. Override this to set up a custom header.
-     */
-    protected void updateHeader() {
-        TextView label = (TextView) mDayNamesHeader.findViewById(R.id.wk_label);
-        if (mShowWeekNumber) {
-            label.setVisibility(View.VISIBLE);
-        } else {
-            label.setVisibility(View.GONE);
-        }
-        int offset = mFirstDayOfWeek - 1;
-        for (int i = 1; i < 8; i++) {
-            label = (TextView) mDayNamesHeader.getChildAt(i);
-            if (i < mDaysPerWeek + 1) {
-                int position = (offset + i) % 7;
-                label.setText(mDayLabels[position]);
-                label.setVisibility(View.VISIBLE);
-                if (position == Time.SATURDAY) {
-                    label.setTextColor(mSaturdayColor);
-                } else if (position == Time.SUNDAY) {
-                    label.setTextColor(mSundayColor);
-                } else {
-                    label.setTextColor(mDayNameColor);
-                }
-            } else {
-                label.setVisibility(View.GONE);
-            }
-        }
-        mDayNamesHeader.invalidate();
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View v = inflater.inflate(R.layout.month_by_week,
-                container, false);
-        mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
-        return v;
-    }
-
-    /**
-     * Returns the UTC millis since epoch representation of the currently
-     * selected time.
-     *
-     * @return
-     */
-    public long getSelectedTime() {
-        return mSelectedDay.toMillis(true);
-    }
-
-    /**
-     * This moves to the specified time in the view. If the time is not already
-     * in range it will move the list so that the first of the month containing
-     * the time is at the top of the view. If the new time is already in view
-     * the list will not be scrolled unless forceScroll is true. This time may
-     * optionally be highlighted as selected as well.
-     *
-     * @param time The time to move to
-     * @param animate Whether to scroll to the given time or just redraw at the
-     *            new location
-     * @param setSelected Whether to set the given time as selected
-     * @param forceScroll Whether to recenter even if the time is already
-     *            visible
-     * @return Whether or not the view animated to the new location
-     */
-    public boolean goTo(long time, boolean animate, boolean setSelected, boolean forceScroll) {
-        if (time == -1) {
-            Log.e(TAG, "time is invalid");
-            return false;
-        }
-
-        // Set the selected day
-        if (setSelected) {
-            mSelectedDay.set(time);
-            mSelectedDay.normalize(true);
-        }
-
-        // If this view isn't returned yet we won't be able to load the lists
-        // current position, so return after setting the selected day.
-        if (!isResumed()) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "We're not visible yet");
-            }
-            return false;
-        }
-
-        mTempTime.set(time);
-        long millis = mTempTime.normalize(true);
-        // Get the week we're going to
-        // TODO push Util function into Calendar public api.
-        int position = Utils.getWeeksSinceEpochFromJulianDay(
-                Time.getJulianDay(millis, mTempTime.gmtoff), mFirstDayOfWeek);
-
-        View child;
-        int i = 0;
-        int top = 0;
-        // Find a child that's completely in the view
-        do {
-            child = mListView.getChildAt(i++);
-            if (child == null) {
-                break;
-            }
-            top = child.getTop();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "child at " + (i-1) + " has top " + top);
-            }
-        } while (top < 0);
-
-        // Compute the first and last position visible
-        int firstPosition;
-        if (child != null) {
-            firstPosition = mListView.getPositionForView(child);
-        } else {
-            firstPosition = 0;
-        }
-        int lastPosition = firstPosition + mNumWeeks - 1;
-        if (top > BOTTOM_BUFFER) {
-            lastPosition--;
-        }
-
-        if (setSelected) {
-            mAdapter.setSelectedDay(mSelectedDay);
-        }
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "GoTo position " + position);
-        }
-        // Check if the selected day is now outside of our visible range
-        // and if so scroll to the month that contains it
-        if (position < firstPosition || position > lastPosition || forceScroll) {
-            mFirstDayOfMonth.set(mTempTime);
-            mFirstDayOfMonth.monthDay = 1;
-            millis = mFirstDayOfMonth.normalize(true);
-            setMonthDisplayed(mFirstDayOfMonth, true);
-            position = Utils.getWeeksSinceEpochFromJulianDay(
-                    Time.getJulianDay(millis, mFirstDayOfMonth.gmtoff), mFirstDayOfWeek);
-
-            mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
-            if (animate) {
-                mListView.smoothScrollToPositionFromTop(
-                        position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
-                return true;
-            } else {
-                mListView.setSelectionFromTop(position, LIST_TOP_OFFSET);
-                // Perform any after scroll operations that are needed
-                onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
-            }
-        } else if (setSelected) {
-            // Otherwise just set the selection
-            setMonthDisplayed(mSelectedDay, true);
-        }
-        return false;
-    }
-
-     /**
-     * Updates the title and selected month if the view has moved to a new
-     * month.
-     */
-    @Override
-    public void onScroll(
-            AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-        SimpleWeekView child = (SimpleWeekView)view.getChildAt(0);
-        if (child == null) {
-            return;
-        }
-
-        // Figure out where we are
-        long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
-        mFirstVisibleDay.setJulianDay(child.getFirstJulianDay());
-
-        // If we have moved since our last call update the direction
-        if (currScroll < mPreviousScrollPosition) {
-            mIsScrollingUp = true;
-        } else if (currScroll > mPreviousScrollPosition) {
-            mIsScrollingUp = false;
-        } else {
-            return;
-        }
-
-        mPreviousScrollPosition = currScroll;
-        mPreviousScrollState = mCurrentScrollState;
-
-        updateMonthHighlight(mListView);
-    }
-
-    /**
-     * Figures out if the month being shown has changed and updates the
-     * highlight if needed
-     *
-     * @param view The ListView containing the weeks
-     */
-    private void updateMonthHighlight(AbsListView view) {
-        SimpleWeekView child = (SimpleWeekView) view.getChildAt(0);
-        if (child == null) {
-            return;
-        }
-
-        // Figure out where we are
-        int offset = child.getBottom() < WEEK_MIN_VISIBLE_HEIGHT ? 1 : 0;
-        // Use some hysteresis for checking which month to highlight. This
-        // causes the month to transition when two full weeks of a month are
-        // visible.
-        child = (SimpleWeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
-
-        if (child == null) {
-            return;
-        }
-
-        // Find out which month we're moving into
-        int month;
-        if (mIsScrollingUp) {
-            month = child.getFirstMonth();
-        } else {
-            month = child.getLastMonth();
-        }
-
-        // And how it relates to our current highlighted month
-        int monthDiff;
-        if (mCurrentMonthDisplayed == 11 && month == 0) {
-            monthDiff = 1;
-        } else if (mCurrentMonthDisplayed == 0 && month == 11) {
-            monthDiff = -1;
-        } else {
-            monthDiff = month - mCurrentMonthDisplayed;
-        }
-
-        // Only switch months if we're scrolling away from the currently
-        // selected month
-        if (monthDiff != 0) {
-            int julianDay = child.getFirstJulianDay();
-            if (mIsScrollingUp) {
-                // Takes the start of the week
-            } else {
-                // Takes the start of the following week
-                julianDay += DAYS_PER_WEEK;
-            }
-            mTempTime.setJulianDay(julianDay);
-            setMonthDisplayed(mTempTime, false);
-        }
-    }
-
-    /**
-     * Sets the month displayed at the top of this view based on time. Override
-     * to add custom events when the title is changed.
-     *
-     * @param time A day in the new focus month.
-     * @param updateHighlight TODO(epastern):
-     */
-    protected void setMonthDisplayed(Time time, boolean updateHighlight) {
-        CharSequence oldMonth = mMonthName.getText();
-        mMonthName.setText(Utils.formatMonthYear(mContext, time));
-        mMonthName.invalidate();
-        if (!TextUtils.equals(oldMonth, mMonthName.getText())) {
-            mMonthName.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-        }
-        mCurrentMonthDisplayed = time.month;
-        if (updateHighlight) {
-            mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
-        }
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-        // use a post to prevent re-entering onScrollStateChanged before it
-        // exits
-        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
-    }
-
-    protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
-
-    protected class ScrollStateRunnable implements Runnable {
-        private int mNewState;
-
-        /**
-         * Sets up the runnable with a short delay in case the scroll state
-         * immediately changes again.
-         *
-         * @param view The list view that changed state
-         * @param scrollState The new state it changed to
-         */
-        public void doScrollStateChange(AbsListView view, int scrollState) {
-            mHandler.removeCallbacks(this);
-            mNewState = scrollState;
-            mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
-        }
-
-        public void run() {
-            mCurrentScrollState = mNewState;
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG,
-                        "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
-            }
-            // Fix the position after a scroll or a fling ends
-            if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
-                    && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
-                mPreviousScrollState = mNewState;
-                mAdapter.updateFocusMonth(mCurrentMonthDisplayed);
-            } else {
-                mPreviousScrollState = mNewState;
-            }
-        }
-    }
-}
diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.kt b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
new file mode 100644
index 0000000..01fcbac
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.app.Activity
+import android.app.ListFragment
+import android.content.Context
+import android.content.res.Resources
+import android.database.DataSetObserver
+import android.os.Bundle
+import android.os.Handler
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+import android.widget.ListView
+import android.widget.TextView
+import java.util.Calendar
+import java.util.HashMap
+import java.util.Locale
+
+/**
+ *
+ *
+ * This displays a titled list of weeks with selectable days. It can be
+ * configured to display the week number, start the week on a given day, show a
+ * reduced number of days, or display an arbitrary number of weeks at a time. By
+ * overriding methods and changing variables this fragment can be customized to
+ * easily display a month selection component in a given style.
+ *
+ */
+open class SimpleDayPickerFragment(initialTime: Long) : ListFragment(), OnScrollListener {
+    protected var WEEK_MIN_VISIBLE_HEIGHT = 12
+    protected var BOTTOM_BUFFER = 20
+    protected var mSaturdayColor = 0
+    protected var mSundayColor = 0
+    protected var mDayNameColor = 0
+
+    // You can override these numbers to get a different appearance
+    @JvmField protected var mNumWeeks = 6
+    @JvmField protected var mShowWeekNumber = false
+    @JvmField protected var mDaysPerWeek = 7
+
+    // These affect the scroll speed and feel
+    protected var mFriction = 1.0f
+    @JvmField protected var mContext: Context? = null
+    @JvmField protected var mHandler: Handler = Handler()
+    protected var mMinimumFlingVelocity = 0f
+
+    // highlighted time
+    @JvmField protected var mSelectedDay: Time = Time()
+    @JvmField protected var mAdapter: SimpleWeeksAdapter? = null
+    @JvmField protected var mListView: ListView? = null
+    @JvmField protected var mDayNamesHeader: ViewGroup? = null
+    @JvmField protected var mDayLabels: Array<String?> = arrayOfNulls(7)
+
+    // disposable variable used for time calculations
+    @JvmField protected var mTempTime: Time = Time()
+
+    // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+    @JvmField protected var mFirstDayOfWeek = 0
+
+    // The first day of the focus month
+    @JvmField protected var mFirstDayOfMonth: Time = Time()
+
+    // The first day that is visible in the view
+    @JvmField protected var mFirstVisibleDay: Time = Time()
+
+    // The name of the month to display
+    protected var mMonthName: TextView? = null
+
+    // The last name announced by accessibility
+    protected var mPrevMonthName: CharSequence? = null
+
+    // which month should be displayed/highlighted [0-11]
+    protected var mCurrentMonthDisplayed = 0
+
+    // used for tracking during a scroll
+    protected var mPreviousScrollPosition: Long = 0
+
+    // used for tracking which direction the view is scrolling
+    protected var mIsScrollingUp = false
+
+    // used for tracking what state listview is in
+    protected var mPreviousScrollState: Int = OnScrollListener.SCROLL_STATE_IDLE
+
+    // used for tracking what state listview is in
+    protected var mCurrentScrollState: Int = OnScrollListener.SCROLL_STATE_IDLE
+
+    // This causes an update of the view at midnight
+    @JvmField protected var mTodayUpdater: Runnable = object : Runnable {
+        @Override
+        override fun run() {
+            val midnight = Time(mFirstVisibleDay.timezone)
+            midnight.setToNow()
+            val currentMillis: Long = midnight.toMillis(true)
+            midnight.hour = 0
+            midnight.minute = 0
+            midnight.second = 0
+            midnight.monthDay++
+            val millisToMidnight: Long = midnight.normalize(true) - currentMillis
+            mHandler?.postDelayed(this, millisToMidnight)
+            if (mAdapter != null) {
+                mAdapter?.notifyDataSetChanged()
+            }
+        }
+    }
+
+    // This allows us to update our position when a day is tapped
+    @JvmField protected var mObserver: DataSetObserver = object : DataSetObserver() {
+        @Override
+        override fun onChanged() {
+            val day: Time? = mAdapter!!.getSelectedDay()
+            if (day!!.year !== mSelectedDay!!.year || day!!.yearDay !== mSelectedDay.yearDay) {
+                goTo(day!!.toMillis(true), true, true, false)
+            }
+        }
+    }
+
+    @Override
+    override fun onAttach(activity: Activity) {
+        super.onAttach(activity)
+        mContext = activity
+        val tz: String = Time.getCurrentTimezone()
+        val viewConfig: ViewConfiguration = ViewConfiguration.get(activity)
+        mMinimumFlingVelocity = (viewConfig.getScaledMinimumFlingVelocity()).toFloat()
+
+        // Ensure we're in the correct time zone
+        mSelectedDay.switchTimezone(tz)
+        mSelectedDay.normalize(true)
+        mFirstDayOfMonth.timezone = tz
+        mFirstDayOfMonth.normalize(true)
+        mFirstVisibleDay.timezone = tz
+        mFirstVisibleDay.normalize(true)
+        mTempTime.timezone = tz
+        val res: Resources = activity.getResources()
+        mSaturdayColor = res.getColor(R.color.month_saturday)
+        mSundayColor = res.getColor(R.color.month_sunday)
+        mDayNameColor = res.getColor(R.color.month_day_names_color)
+
+        // Adjust sizes for screen density
+        if (mScale == 0f) {
+            mScale = activity.getResources().getDisplayMetrics().density
+            if (mScale != 1f) {
+                WEEK_MIN_VISIBLE_HEIGHT *= mScale.toInt()
+                BOTTOM_BUFFER *= mScale.toInt()
+                LIST_TOP_OFFSET *= mScale.toInt()
+            }
+        }
+        setUpAdapter()
+        setListAdapter(mAdapter)
+    }
+
+    /**
+     * Creates a new adapter if necessary and sets up its parameters. Override
+     * this method to provide a custom adapter.
+     */
+    protected open fun setUpAdapter() {
+        val weekParams = HashMap<String?, Int?>()
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks)
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, if (mShowWeekNumber) 1 else 0)
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek)
+        weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
+                Time.getJulianDay(mSelectedDay.toMillis(false), mSelectedDay.gmtoff))
+        if (mAdapter == null) {
+            mAdapter = SimpleWeeksAdapter(getActivity(), weekParams)
+            mAdapter?.registerDataSetObserver(mObserver)
+        } else {
+            mAdapter?.updateParams(weekParams)
+        }
+        // refresh the view with the new parameters
+        mAdapter?.notifyDataSetChanged()
+    }
+
+    @Override
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+    }
+
+    @Override
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        setUpListView()
+        setUpHeader()
+        mMonthName = getView()?.findViewById(R.id.month_name) as? TextView
+        val child = mListView?.getChildAt(0) as? SimpleWeekView
+        if (child == null) {
+            return
+        }
+        val julianDay: Int = child.getFirstJulianDay()
+        mFirstVisibleDay.setJulianDay(julianDay)
+        // set the title to the month of the second week
+        mTempTime.setJulianDay(julianDay + DAYS_PER_WEEK)
+        setMonthDisplayed(mTempTime, true)
+    }
+
+    /**
+     * Sets up the strings to be used by the header. Override this method to use
+     * different strings or modify the view params.
+     */
+    protected open fun setUpHeader() {
+        mDayLabels = arrayOfNulls(7)
+        for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
+                    DateUtils.LENGTH_SHORTEST).toUpperCase()
+        }
+    }
+
+    /**
+     * Sets all the required fields for the list view. Override this method to
+     * set a different list view behavior.
+     */
+    protected fun setUpListView() {
+        // Configure the listview
+        mListView = getListView()
+        // Transparent background on scroll
+        mListView?.setCacheColorHint(0)
+        // No dividers
+        mListView?.setDivider(null)
+        // Items are clickable
+        mListView?.setItemsCanFocus(true)
+        // The thumb gets in the way, so disable it
+        mListView?.setFastScrollEnabled(false)
+        mListView?.setVerticalScrollBarEnabled(false)
+        mListView?.setOnScrollListener(this)
+        mListView?.setFadingEdgeLength(0)
+        // Make the scrolling behavior nicer
+        mListView?.setFriction(ViewConfiguration.getScrollFriction() * mFriction)
+    }
+
+    @Override
+    override fun onResume() {
+        super.onResume()
+        setUpAdapter()
+        doResumeUpdates()
+    }
+
+    @Override
+    override fun onPause() {
+        super.onPause()
+        mHandler.removeCallbacks(mTodayUpdater)
+    }
+
+    @Override
+    override fun onSaveInstanceState(outState: Bundle) {
+        outState.putLong(KEY_CURRENT_TIME, mSelectedDay.toMillis(true))
+    }
+
+    /**
+     * Updates the user preference fields. Override this to use a different
+     * preference space.
+     */
+    protected open fun doResumeUpdates() {
+        // Get default week start based on locale, subtracting one for use with android Time.
+        val cal: Calendar = Calendar.getInstance(Locale.getDefault())
+        mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1
+        mShowWeekNumber = false
+        updateHeader()
+        goTo(mSelectedDay.toMillis(true), false, false, false)
+        mAdapter?.setSelectedDay(mSelectedDay)
+        mTodayUpdater.run()
+    }
+
+    /**
+     * Fixes the day names header to provide correct spacing and updates the
+     * label text. Override this to set up a custom header.
+     */
+    protected fun updateHeader() {
+        var label: TextView = mDayNamesHeader!!.findViewById(R.id.wk_label) as TextView
+        if (mShowWeekNumber) {
+            label.setVisibility(View.VISIBLE)
+        } else {
+            label.setVisibility(View.GONE)
+        }
+        val offset = mFirstDayOfWeek - 1
+        for (i in 1..7) {
+            label = mDayNamesHeader!!.getChildAt(i) as TextView
+            if (i < mDaysPerWeek + 1) {
+                val position = (offset + i) % 7
+                label.setText(mDayLabels[position])
+                label.setVisibility(View.VISIBLE)
+                if (position == Time.SATURDAY) {
+                    label.setTextColor(mSaturdayColor)
+                } else if (position == Time.SUNDAY) {
+                    label.setTextColor(mSundayColor)
+                } else {
+                    label.setTextColor(mDayNameColor)
+                }
+            } else {
+                label.setVisibility(View.GONE)
+            }
+        }
+        mDayNamesHeader?.invalidate()
+    }
+
+    @Override
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val v: View = inflater.inflate(R.layout.month_by_week,
+                container, false)
+        mDayNamesHeader = v.findViewById(R.id.day_names) as ViewGroup
+        return v
+    }
+
+    /**
+     * Returns the UTC millis since epoch representation of the currently
+     * selected time.
+     *
+     * @return
+     */
+    val selectedTime: Long
+        get() = mSelectedDay.toMillis(true)
+
+    /**
+     * This moves to the specified time in the view. If the time is not already
+     * in range it will move the list so that the first of the month containing
+     * the time is at the top of the view. If the new time is already in view
+     * the list will not be scrolled unless forceScroll is true. This time may
+     * optionally be highlighted as selected as well.
+     *
+     * @param time The time to move to
+     * @param animate Whether to scroll to the given time or just redraw at the
+     * new location
+     * @param setSelected Whether to set the given time as selected
+     * @param forceScroll Whether to recenter even if the time is already
+     * visible
+     * @return Whether or not the view animated to the new location
+     */
+    fun goTo(time: Long, animate: Boolean, setSelected: Boolean, forceScroll: Boolean): Boolean {
+        if (time == -1L) {
+            Log.e(TAG, "time is invalid")
+            return false
+        }
+
+        // Set the selected day
+        if (setSelected) {
+            mSelectedDay.set(time)
+            mSelectedDay.normalize(true)
+        }
+
+        // If this view isn't returned yet we won't be able to load the lists
+        // current position, so return after setting the selected day.
+        if (!isResumed()) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "We're not visible yet")
+            }
+            return false
+        }
+        mTempTime.set(time)
+        var millis: Long = mTempTime.normalize(true)
+        // Get the week we're going to
+        // TODO push Util function into Calendar public api.
+        var position: Int = Utils.getWeeksSinceEpochFromJulianDay(
+                Time.getJulianDay(millis, mTempTime.gmtoff), mFirstDayOfWeek)
+        var child: View?
+        var i = 0
+        var top = 0
+        // Find a child that's completely in the view
+        do {
+            child = mListView?.getChildAt(i++)
+            if (child == null) {
+                break
+            }
+            top = child.getTop()
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "child at " + (i - 1) + " has top " + top)
+            }
+        } while (top < 0)
+
+        // Compute the first and last position visible
+        val firstPosition: Int
+        firstPosition = if (child != null) {
+            mListView!!.getPositionForView(child)
+        } else {
+            0
+        }
+        var lastPosition = firstPosition + mNumWeeks - 1
+        if (top > BOTTOM_BUFFER) {
+            lastPosition--
+        }
+        if (setSelected) {
+            mAdapter?.setSelectedDay(mSelectedDay)
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "GoTo position $position")
+        }
+        // Check if the selected day is now outside of our visible range
+        // and if so scroll to the month that contains it
+        if (position < firstPosition || position > lastPosition || forceScroll) {
+            mFirstDayOfMonth.set(mTempTime)
+            mFirstDayOfMonth.monthDay = 1
+            millis = mFirstDayOfMonth.normalize(true)
+            setMonthDisplayed(mFirstDayOfMonth, true)
+            position = Utils.getWeeksSinceEpochFromJulianDay(
+                    Time.getJulianDay(millis, mFirstDayOfMonth.gmtoff), mFirstDayOfWeek)
+            mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING
+            if (animate) {
+                mListView?.smoothScrollToPositionFromTop(
+                        position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION)
+                return true
+            } else {
+                mListView?.setSelectionFromTop(position, LIST_TOP_OFFSET)
+                // Perform any after scroll operations that are needed
+                onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE)
+            }
+        } else if (setSelected) {
+            // Otherwise just set the selection
+            setMonthDisplayed(mSelectedDay, true)
+        }
+        return false
+    }
+
+    /**
+     * Updates the title and selected month if the view has moved to a new
+     * month.
+     */
+    @Override
+    override fun onScroll(
+        view: AbsListView,
+        firstVisibleItem: Int,
+        visibleItemCount: Int,
+        totalItemCount: Int
+    ) {
+        val child = view.getChildAt(0) as? SimpleWeekView
+        if (child == null) {
+            return
+        }
+
+        // Figure out where we are
+        val currScroll: Long = (view.getFirstVisiblePosition() * child.getHeight() -
+                                child.getBottom()).toLong()
+        mFirstVisibleDay.setJulianDay(child.getFirstJulianDay())
+
+        // If we have moved since our last call update the direction
+        mIsScrollingUp = if (currScroll < mPreviousScrollPosition) {
+            true
+        } else if (currScroll > mPreviousScrollPosition) {
+            false
+        } else {
+            return
+        }
+        mPreviousScrollPosition = currScroll
+        mPreviousScrollState = mCurrentScrollState
+        updateMonthHighlight(mListView as? AbsListView)
+    }
+
+    /**
+     * Figures out if the month being shown has changed and updates the
+     * highlight if needed
+     *
+     * @param view The ListView containing the weeks
+     */
+    private fun updateMonthHighlight(view: AbsListView?) {
+        var child = view?.getChildAt(0) as? SimpleWeekView
+        if (child == null) {
+            return
+        }
+
+        // Figure out where we are
+        val offset = if (child?.getBottom() < WEEK_MIN_VISIBLE_HEIGHT) 1 else 0
+        // Use some hysteresis for checking which month to highlight. This
+        // causes the month to transition when two full weeks of a month are
+        // visible.
+        child = view?.getChildAt(SCROLL_HYST_WEEKS + offset) as? SimpleWeekView
+        if (child == null) {
+            return
+        }
+
+        // Find out which month we're moving into
+        val month: Int
+        month = if (mIsScrollingUp) {
+            child?.getFirstMonth()
+        } else {
+            child?.getLastMonth()
+        }
+
+        // And how it relates to our current highlighted month
+        val monthDiff: Int
+        monthDiff = if (mCurrentMonthDisplayed == 11 && month == 0) {
+            1
+        } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+            -1
+        } else {
+            month - mCurrentMonthDisplayed
+        }
+
+        // Only switch months if we're scrolling away from the currently
+        // selected month
+        if (monthDiff != 0) {
+            var julianDay: Int = child.getFirstJulianDay()
+            if (mIsScrollingUp) {
+                // Takes the start of the week
+            } else {
+                // Takes the start of the following week
+                julianDay += DAYS_PER_WEEK
+            }
+            mTempTime.setJulianDay(julianDay)
+            setMonthDisplayed(mTempTime, false)
+        }
+    }
+
+    /**
+     * Sets the month displayed at the top of this view based on time. Override
+     * to add custom events when the title is changed.
+     *
+     * @param time A day in the new focus month.
+     * @param updateHighlight TODO(epastern):
+     */
+    protected open fun setMonthDisplayed(time: Time, updateHighlight: Boolean) {
+        val oldMonth: CharSequence = mMonthName!!.getText()
+        mMonthName?.setText(Utils.formatMonthYear(mContext, time))
+        mMonthName?.invalidate()
+        if (!TextUtils.equals(oldMonth, mMonthName?.getText())) {
+            mMonthName?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+        }
+        mCurrentMonthDisplayed = time.month
+        if (updateHighlight) {
+            mAdapter?.updateFocusMonth(mCurrentMonthDisplayed)
+        }
+    }
+
+    @Override
+    override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+        // use a post to prevent re-entering onScrollStateChanged before it
+        // exits
+        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState)
+    }
+
+    @JvmField protected var mScrollStateChangedRunnable: ScrollStateRunnable = ScrollStateRunnable()
+
+    protected inner class ScrollStateRunnable : Runnable {
+        private var mNewState = 0
+
+        /**
+         * Sets up the runnable with a short delay in case the scroll state
+         * immediately changes again.
+         *
+         * @param view The list view that changed state
+         * @param scrollState The new state it changed to
+         */
+        fun doScrollStateChange(view: AbsListView?, scrollState: Int) {
+            mHandler.removeCallbacks(this)
+            mNewState = scrollState
+            mHandler.postDelayed(this, SCROLL_CHANGE_DELAY.toLong())
+        }
+
+        override fun run() {
+            mCurrentScrollState = mNewState
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG,
+                        "new scroll state: $mNewState old state: $mPreviousScrollState")
+            }
+            // Fix the position after a scroll or a fling ends
+            if (mNewState == OnScrollListener.SCROLL_STATE_IDLE &&
+                    mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+                mPreviousScrollState = mNewState
+                mAdapter?.updateFocusMonth(mCurrentMonthDisplayed)
+            } else {
+                mPreviousScrollState = mNewState
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "MonthFragment"
+        private const val KEY_CURRENT_TIME = "current_time"
+
+        // Affects when the month selection will change while scrolling up
+        protected const val SCROLL_HYST_WEEKS = 2
+
+        // How long the GoTo fling animation should last
+        @JvmStatic protected val GOTO_SCROLL_DURATION = 500
+
+        // How long to wait after receiving an onScrollStateChanged notification
+        // before acting on it
+        protected const val SCROLL_CHANGE_DELAY = 40
+
+        // The number of days to display in each week
+        const val DAYS_PER_WEEK = 7
+
+        // The size of the month name displayed above the week list
+        protected const val MINI_MONTH_NAME_TEXT_SIZE = 18
+        var LIST_TOP_OFFSET = -1 // so that the top line will be under the separator
+        private var mScale = 0f
+    }
+
+    init {
+        goTo(initialTime, false, true, true)
+        mHandler = Handler()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeekView.java b/src/com/android/calendar/month/SimpleWeekView.java
deleted file mode 100644
index 4d0c09f..0000000
--- a/src/com/android/calendar/month/SimpleWeekView.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import java.security.InvalidParameterException;
-import java.util.HashMap;
-
-/**
- * <p>
- * This is a dynamic view for drawing a single week. It can be configured to
- * display the week number, start the week on a given day, or show a reduced
- * number of days. It is intended for use as a single view within a ListView.
- * See {@link SimpleWeeksAdapter} for usage.
- * </p>
- */
-public class SimpleWeekView extends View {
-    private static final String TAG = "MonthView";
-
-    /**
-     * These params can be passed into the view to control how it appears.
-     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
-     * values are unlikely to fit most layouts correctly.
-     */
-    /**
-     * This sets the height of this week in pixels
-     */
-    public static final String VIEW_PARAMS_HEIGHT = "height";
-    /**
-     * This specifies the position (or weeks since the epoch) of this week,
-     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
-     */
-    public static final String VIEW_PARAMS_WEEK = "week";
-    /**
-     * This sets one of the days in this view as selected {@link Time#SUNDAY}
-     * through {@link Time#SATURDAY}.
-     */
-    public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
-    /**
-     * Which day the week should start on. {@link Time#SUNDAY} through
-     * {@link Time#SATURDAY}.
-     */
-    public static final String VIEW_PARAMS_WEEK_START = "week_start";
-    /**
-     * How many days to display at a time. Days will be displayed starting with
-     * {@link #mWeekStart}.
-     */
-    public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
-    /**
-     * Which month is currently in focus, as defined by {@link Time#month}
-     * [0-11].
-     */
-    public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
-    /**
-     * If this month should display week numbers. false if 0, true otherwise.
-     */
-    public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
-
-    protected static int DEFAULT_HEIGHT = 32;
-    protected static int MIN_HEIGHT = 10;
-    protected static final int DEFAULT_SELECTED_DAY = -1;
-    protected static final int DEFAULT_WEEK_START = Time.SUNDAY;
-    protected static final int DEFAULT_NUM_DAYS = 7;
-    protected static final int DEFAULT_SHOW_WK_NUM = 0;
-    protected static final int DEFAULT_FOCUS_MONTH = -1;
-
-    protected static int DAY_SEPARATOR_WIDTH = 1;
-
-    protected static int MINI_DAY_NUMBER_TEXT_SIZE = 14;
-    protected static int MINI_WK_NUMBER_TEXT_SIZE = 12;
-    protected static int MINI_TODAY_NUMBER_TEXT_SIZE = 18;
-    protected static int MINI_TODAY_OUTLINE_WIDTH = 2;
-    protected static int WEEK_NUM_MARGIN_BOTTOM = 4;
-
-    // used for scaling to the device density
-    protected static float mScale = 0;
-
-    // affects the padding on the sides of this view
-    protected int mPadding = 0;
-
-    protected Rect r = new Rect();
-    protected Paint p = new Paint();
-    protected Paint mMonthNumPaint;
-    protected Drawable mSelectedDayLine;
-
-    // Cache the number strings so we don't have to recompute them each time
-    protected String[] mDayNumbers;
-    // Quick lookup for checking which days are in the focus month
-    protected boolean[] mFocusDay;
-    // Quick lookup for checking which days are in an odd month (to set a different background)
-    protected boolean[] mOddMonth;
-    // The Julian day of the first day displayed by this item
-    protected int mFirstJulianDay = -1;
-    // The month of the first day in this week
-    protected int mFirstMonth = -1;
-    // The month of the last day in this week
-    protected int mLastMonth = -1;
-    // The position of this week, equivalent to weeks since the week of Jan 1st,
-    // 1970
-    protected int mWeek = -1;
-    // Quick reference to the width of this view, matches parent
-    protected int mWidth;
-    // The height this view should draw at in pixels, set by height param
-    protected int mHeight = DEFAULT_HEIGHT;
-    // Whether the week number should be shown
-    protected boolean mShowWeekNum = false;
-    // If this view contains the selected day
-    protected boolean mHasSelectedDay = false;
-    // If this view contains the today
-    protected boolean mHasToday = false;
-    // Which day is selected [0-6] or -1 if no day is selected
-    protected int mSelectedDay = DEFAULT_SELECTED_DAY;
-    // Which day is today [0-6] or -1 if no day is today
-    protected int mToday = DEFAULT_SELECTED_DAY;
-    // Which day of the week to start on [0-6]
-    protected int mWeekStart = DEFAULT_WEEK_START;
-    // How many days to display
-    protected int mNumDays = DEFAULT_NUM_DAYS;
-    // The number of days + a spot for week number if it is displayed
-    protected int mNumCells = mNumDays;
-    // The left edge of the selected day
-    protected int mSelectedLeft = -1;
-    // The right edge of the selected day
-    protected int mSelectedRight = -1;
-    // The timezone to display times/dates in (used for determining when Today
-    // is)
-    protected String mTimeZone = Time.getCurrentTimezone();
-
-    protected int mBGColor;
-    protected int mSelectedWeekBGColor;
-    protected int mFocusMonthColor;
-    protected int mOtherMonthColor;
-    protected int mDaySeparatorColor;
-    protected int mTodayOutlineColor;
-    protected int mWeekNumColor;
-
-    public SimpleWeekView(Context context) {
-        super(context);
-
-        Resources res = context.getResources();
-
-        mBGColor = res.getColor(R.color.month_bgcolor);
-        mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor);
-        mFocusMonthColor = res.getColor(R.color.month_mini_day_number);
-        mOtherMonthColor = res.getColor(R.color.month_other_month_day_number);
-        mDaySeparatorColor = res.getColor(R.color.month_grid_lines);
-        mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color);
-        mWeekNumColor = res.getColor(R.color.month_week_num_color);
-        mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light);
-
-        if (mScale == 0) {
-            mScale = context.getResources().getDisplayMetrics().density;
-            if (mScale != 1) {
-                DEFAULT_HEIGHT *= mScale;
-                MIN_HEIGHT *= mScale;
-                MINI_DAY_NUMBER_TEXT_SIZE *= mScale;
-                MINI_TODAY_NUMBER_TEXT_SIZE *= mScale;
-                MINI_TODAY_OUTLINE_WIDTH *= mScale;
-                WEEK_NUM_MARGIN_BOTTOM *= mScale;
-                DAY_SEPARATOR_WIDTH *= mScale;
-                MINI_WK_NUMBER_TEXT_SIZE *= mScale;
-            }
-        }
-
-        // Sets up any standard paints that will be used
-        initView();
-    }
-
-    /**
-     * Sets all the parameters for displaying this week. The only required
-     * parameter is the week number. Other parameters have a default value and
-     * will only update if a new value is included, except for focus month,
-     * which will always default to no focus month if no value is passed in. See
-     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
-     *
-     * @param params A map of the new parameters, see
-     *            {@link #VIEW_PARAMS_HEIGHT}
-     * @param tz The time zone this view should reference times in
-     */
-    public void setWeekParams(HashMap<String, Integer> params, String tz) {
-        if (!params.containsKey(VIEW_PARAMS_WEEK)) {
-            throw new InvalidParameterException("You must specify the week number for this view");
-        }
-        setTag(params);
-        mTimeZone = tz;
-        // We keep the current value for any params not present
-        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
-            mHeight = params.get(VIEW_PARAMS_HEIGHT);
-            if (mHeight < MIN_HEIGHT) {
-                mHeight = MIN_HEIGHT;
-            }
-        }
-        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
-            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
-        }
-        mHasSelectedDay = mSelectedDay != -1;
-        if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
-            mNumDays = params.get(VIEW_PARAMS_NUM_DAYS);
-        }
-        if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
-            if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
-                mShowWeekNum = true;
-            } else {
-                mShowWeekNum = false;
-            }
-        }
-        mNumCells = mShowWeekNum ? mNumDays + 1 : mNumDays;
-
-        // Allocate space for caching the day numbers and focus values
-        mDayNumbers = new String[mNumCells];
-        mFocusDay = new boolean[mNumCells];
-        mOddMonth = new boolean[mNumCells];
-        mWeek = params.get(VIEW_PARAMS_WEEK);
-        int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek);
-        Time time = new Time(tz);
-        time.setJulianDay(julianMonday);
-
-        // If we're showing the week number calculate it based on Monday
-        int i = 0;
-        if (mShowWeekNum) {
-            mDayNumbers[0] = Integer.toString(time.getWeekNumber());
-            i++;
-        }
-
-        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
-            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
-        }
-
-        // Now adjust our starting day based on the start day of the week
-        // If the week is set to start on a Saturday the first week will be
-        // Dec 27th 1969 -Jan 2nd, 1970
-        if (time.weekDay != mWeekStart) {
-            int diff = time.weekDay - mWeekStart;
-            if (diff < 0) {
-                diff += 7;
-            }
-            time.monthDay -= diff;
-            time.normalize(true);
-        }
-
-        mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff);
-        mFirstMonth = time.month;
-
-        // Figure out what day today is
-        Time today = new Time(tz);
-        today.setToNow();
-        mHasToday = false;
-        mToday = -1;
-
-        int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? params.get(
-                VIEW_PARAMS_FOCUS_MONTH)
-                : DEFAULT_FOCUS_MONTH;
-
-        for (; i < mNumCells; i++) {
-            if (time.monthDay == 1) {
-                mFirstMonth = time.month;
-            }
-            mOddMonth [i] = (time.month %2) == 1;
-            if (time.month == focusMonth) {
-                mFocusDay[i] = true;
-            } else {
-                mFocusDay[i] = false;
-            }
-            if (time.year == today.year && time.yearDay == today.yearDay) {
-                mHasToday = true;
-                mToday = i;
-            }
-            mDayNumbers[i] = Integer.toString(time.monthDay++);
-            time.normalize(true);
-        }
-        // We do one extra add at the end of the loop, if that pushed us to a
-        // new month undo it
-        if (time.monthDay == 1) {
-            time.monthDay--;
-            time.normalize(true);
-        }
-        mLastMonth = time.month;
-
-        updateSelectionPositions();
-    }
-
-    /**
-     * Sets up the text and style properties for painting. Override this if you
-     * want to use a different paint.
-     */
-    protected void initView() {
-        p.setFakeBoldText(false);
-        p.setAntiAlias(true);
-        p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
-        p.setStyle(Style.FILL);
-
-        mMonthNumPaint = new Paint();
-        mMonthNumPaint.setFakeBoldText(true);
-        mMonthNumPaint.setAntiAlias(true);
-        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
-        mMonthNumPaint.setColor(mFocusMonthColor);
-        mMonthNumPaint.setStyle(Style.FILL);
-        mMonthNumPaint.setTextAlign(Align.CENTER);
-    }
-
-    /**
-     * Returns the month of the first day in this week
-     *
-     * @return The month the first day of this view is in
-     */
-    public int getFirstMonth() {
-        return mFirstMonth;
-    }
-
-    /**
-     * Returns the month of the last day in this week
-     *
-     * @return The month the last day of this view is in
-     */
-    public int getLastMonth() {
-        return mLastMonth;
-    }
-
-    /**
-     * Returns the julian day of the first day in this view.
-     *
-     * @return The julian day of the first day in the view.
-     */
-    public int getFirstJulianDay() {
-        return mFirstJulianDay;
-    }
-
-    /**
-     * Calculates the day that the given x position is in, accounting for week
-     * number. Returns a Time referencing that day or null if
-     *
-     * @param x The x position of the touch event
-     * @return A time object for the tapped day or null if the position wasn't
-     *         in a day
-     */
-    public Time getDayFromLocation(float x) {
-        int dayStart = mShowWeekNum ? (mWidth - mPadding * 2) / mNumCells + mPadding : mPadding;
-        if (x < dayStart || x > mWidth - mPadding) {
-            return null;
-        }
-        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
-        int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
-        int day = mFirstJulianDay + dayPosition;
-
-        Time time = new Time(mTimeZone);
-        if (mWeek == 0) {
-            // This week is weird...
-            if (day < Time.EPOCH_JULIAN_DAY) {
-                day++;
-            } else if (day == Time.EPOCH_JULIAN_DAY) {
-                time.set(1, 0, 1970);
-                time.normalize(true);
-                return time;
-            }
-        }
-
-        time.setJulianDay(day);
-        return time;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        drawBackground(canvas);
-        drawWeekNums(canvas);
-        drawDaySeparators(canvas);
-    }
-
-    /**
-     * This draws the selection highlight if a day is selected in this week.
-     * Override this method if you wish to have a different background drawn.
-     *
-     * @param canvas The canvas to draw on
-     */
-    protected void drawBackground(Canvas canvas) {
-        if (mHasSelectedDay) {
-            p.setColor(mSelectedWeekBGColor);
-            p.setStyle(Style.FILL);
-        } else {
-            return;
-        }
-        r.top = 1;
-        r.bottom = mHeight - 1;
-        r.left = mPadding;
-        r.right = mSelectedLeft;
-        canvas.drawRect(r, p);
-        r.left = mSelectedRight;
-        r.right = mWidth - mPadding;
-        canvas.drawRect(r, p);
-    }
-
-    /**
-     * Draws the week and month day numbers for this week. Override this method
-     * if you need different placement.
-     *
-     * @param canvas The canvas to draw on
-     */
-    protected void drawWeekNums(Canvas canvas) {
-        int y = ((mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH;
-        int nDays = mNumCells;
-
-        int i = 0;
-        int divisor = 2 * nDays;
-        if (mShowWeekNum) {
-            p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE);
-            p.setStyle(Style.FILL);
-            p.setTextAlign(Align.CENTER);
-            p.setAntiAlias(true);
-            p.setColor(mWeekNumColor);
-            int x = (mWidth - mPadding * 2) / divisor + mPadding;
-            canvas.drawText(mDayNumbers[0], x, y, p);
-            i++;
-        }
-
-        boolean isFocusMonth = mFocusDay[i];
-        mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
-        mMonthNumPaint.setFakeBoldText(false);
-        for (; i < nDays; i++) {
-            if (mFocusDay[i] != isFocusMonth) {
-                isFocusMonth = mFocusDay[i];
-                mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
-            }
-            if (mHasToday && mToday == i) {
-                mMonthNumPaint.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE);
-                mMonthNumPaint.setFakeBoldText(true);
-            }
-            int x = (2 * i + 1) * (mWidth - mPadding * 2) / (divisor) + mPadding;
-            canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
-            if (mHasToday && mToday == i) {
-                mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
-                mMonthNumPaint.setFakeBoldText(false);
-            }
-        }
-    }
-
-    /**
-     * Draws a horizontal line for separating the weeks. Override this method if
-     * you want custom separators.
-     *
-     * @param canvas The canvas to draw on
-     */
-    protected void drawDaySeparators(Canvas canvas) {
-        if (mHasSelectedDay) {
-            r.top = 1;
-            r.bottom = mHeight - 1;
-            r.left = mSelectedLeft + 1;
-            r.right = mSelectedRight - 1;
-            p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH);
-            p.setStyle(Style.STROKE);
-            p.setColor(mTodayOutlineColor);
-            canvas.drawRect(r, p);
-        }
-        if (mShowWeekNum) {
-            p.setColor(mDaySeparatorColor);
-            p.setStrokeWidth(DAY_SEPARATOR_WIDTH);
-
-            int x = (mWidth - mPadding * 2) / mNumCells + mPadding;
-            canvas.drawLine(x, 0, x, mHeight, p);
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        mWidth = w;
-        updateSelectionPositions();
-    }
-
-    /**
-     * This calculates the positions for the selected day lines.
-     */
-    protected void updateSelectionPositions() {
-        if (mHasSelectedDay) {
-            int selectedPosition = mSelectedDay - mWeekStart;
-            if (selectedPosition < 0) {
-                selectedPosition += 7;
-            }
-            if (mShowWeekNum) {
-                selectedPosition++;
-            }
-            mSelectedLeft = selectedPosition * (mWidth - mPadding * 2) / mNumCells
-                    + mPadding;
-            mSelectedRight = (selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells
-                    + mPadding;
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
-    }
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        Context context = getContext();
-        // only send accessibility events if accessibility and exploration are
-        // on.
-        AccessibilityManager am = (AccessibilityManager) context
-                .getSystemService(Service.ACCESSIBILITY_SERVICE);
-        if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
-            return super.onHoverEvent(event);
-        }
-        if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
-            Time hover = getDayFromLocation(event.getX());
-            if (hover != null
-                    && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) {
-                Long millis = hover.toMillis(true);
-                String date = Utils.formatDateRange(context, millis, millis,
-                        DateUtils.FORMAT_SHOW_DATE);
-                AccessibilityEvent accessEvent =
-                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
-                accessEvent.getText().add(date);
-                sendAccessibilityEventUnchecked(accessEvent);
-                mLastHoverTime = hover;
-            }
-        }
-        return true;
-    }
-
-    Time mLastHoverTime = null;
-}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeekView.kt b/src/com/android/calendar/month/SimpleWeekView.kt
new file mode 100644
index 0000000..4d1298d
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeekView.kt
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.app.Service
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Paint.Align
+import android.graphics.Paint.Style
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.view.MotionEvent
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import java.security.InvalidParameterException
+import java.util.HashMap
+
+/**
+ *
+ *
+ * This is a dynamic view for drawing a single week. It can be configured to
+ * display the week number, start the week on a given day, or show a reduced
+ * number of days. It is intended for use as a single view within a ListView.
+ * See [SimpleWeeksAdapter] for usage.
+ *
+ */
+open class SimpleWeekView(context: Context) : View(context) {
+    // affects the padding on the sides of this view
+    @JvmField protected var mPadding = 0
+    @JvmField protected var r: Rect = Rect()
+    @JvmField protected var p: Paint = Paint()
+    @JvmField protected var mMonthNumPaint: Paint = Paint()
+    @JvmField protected var mSelectedDayLine: Drawable
+
+    // Cache the number strings so we don't have to recompute them each time
+    @JvmField protected var mDayNumbers: Array<String?>? = null
+
+    // How many days to display
+    @JvmField protected var mNumDays = DEFAULT_NUM_DAYS
+
+    // The number of days + a spot for week number if it is displayed
+    @JvmField protected var mNumCells = mNumDays
+
+    // Quick lookup for checking which days are in the focus month
+    @JvmField protected var mFocusDay: BooleanArray = BooleanArray(mNumCells)
+
+    // Quick lookup for checking which days are in an odd month (to set a different background)
+    @JvmField protected var mOddMonth: BooleanArray = BooleanArray(mNumCells)
+
+    // The Julian day of the first day displayed by this item
+    @JvmField protected var mFirstJulianDay = -1
+
+    // The month of the first day in this week
+    @JvmField protected var firstMonth = -1
+
+    // The month of the last day in this week
+    @JvmField protected var lastMonth = -1
+
+    // The position of this week, equivalent to weeks since the week of Jan 1st,
+    // 1970
+    @JvmField var mWeek = -1
+
+    // Quick reference to the width of this view, matches parent
+    @JvmField protected var mWidth = 0
+
+    // The height this view should draw at in pixels, set by height param
+    @JvmField protected var mHeight = DEFAULT_HEIGHT
+
+    // Whether the week number should be shown
+    @JvmField protected var mShowWeekNum = false
+
+    // If this view contains the selected day
+    @JvmField protected var mHasSelectedDay = false
+
+    // If this view contains the today
+    open protected var mHasToday = false
+
+    // Which day is selected [0-6] or -1 if no day is selected
+    @JvmField protected var mSelectedDay = DEFAULT_SELECTED_DAY
+
+    // Which day is today [0-6] or -1 if no day is today
+    @JvmField protected var mToday: Int = DEFAULT_SELECTED_DAY
+
+    // Which day of the week to start on [0-6]
+    @JvmField protected var mWeekStart = DEFAULT_WEEK_START
+
+    // The left edge of the selected day
+    @JvmField protected var mSelectedLeft = -1
+
+    // The right edge of the selected day
+    @JvmField protected var mSelectedRight = -1
+
+    // The timezone to display times/dates in (used for determining when Today
+    // is)
+    @JvmField protected var mTimeZone: String = Time.getCurrentTimezone()
+    @JvmField protected var mBGColor: Int
+    @JvmField protected var mSelectedWeekBGColor: Int
+    @JvmField protected var mFocusMonthColor: Int
+    @JvmField protected var mOtherMonthColor: Int
+    @JvmField protected var mDaySeparatorColor: Int
+    @JvmField protected var mTodayOutlineColor: Int
+    @JvmField protected var mWeekNumColor: Int
+
+    /**
+     * Sets all the parameters for displaying this week. The only required
+     * parameter is the week number. Other parameters have a default value and
+     * will only update if a new value is included, except for focus month,
+     * which will always default to no focus month if no value is passed in. See
+     * [.VIEW_PARAMS_HEIGHT] for more info on parameters.
+     *
+     * @param params A map of the new parameters, see
+     * [.VIEW_PARAMS_HEIGHT]
+     * @param tz The time zone this view should reference times in
+     */
+    open fun setWeekParams(params: HashMap<String?, Int?>, tz: String) {
+        if (!params.containsKey(VIEW_PARAMS_WEEK)) {
+            throw InvalidParameterException("You must specify the week number for this view")
+        }
+        setTag(params)
+        mTimeZone = tz
+        // We keep the current value for any params not present
+        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+            mHeight = (params.get(VIEW_PARAMS_HEIGHT))!!.toInt()
+            if (mHeight < MIN_HEIGHT) {
+                mHeight = MIN_HEIGHT
+            }
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+            mSelectedDay = (params.get(VIEW_PARAMS_SELECTED_DAY))!!.toInt()
+        }
+        mHasSelectedDay = mSelectedDay != -1
+        if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
+            mNumDays = (params.get(VIEW_PARAMS_NUM_DAYS))!!.toInt()
+        }
+        if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
+            mShowWeekNum =
+                    if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
+                        true
+                    } else {
+                        false
+                    }
+        }
+        mNumCells = if (mShowWeekNum) mNumDays + 1 else mNumDays
+
+        // Allocate space for caching the day numbers and focus values
+        mDayNumbers = arrayOfNulls(mNumCells)
+        mFocusDay = BooleanArray(mNumCells)
+        mOddMonth = BooleanArray(mNumCells)
+        mWeek = (params.get(VIEW_PARAMS_WEEK))!!.toInt()
+        val julianMonday: Int = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek)
+        val time = Time(tz)
+        time.setJulianDay(julianMonday)
+
+        // If we're showing the week number calculate it based on Monday
+        var i = 0
+        if (mShowWeekNum) {
+            mDayNumbers!![0] = Integer.toString(time.getWeekNumber())
+            i++
+        }
+        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+            mWeekStart = (params.get(VIEW_PARAMS_WEEK_START))!!.toInt()
+        }
+
+        // Now adjust our starting day based on the start day of the week
+        // If the week is set to start on a Saturday the first week will be
+        // Dec 27th 1969 -Jan 2nd, 1970
+        if (time.weekDay !== mWeekStart) {
+            var diff: Int = time.weekDay - mWeekStart
+            if (diff < 0) {
+                diff += 7
+            }
+            time.monthDay -= diff
+            time.normalize(true)
+        }
+        mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff)
+        firstMonth = time.month
+
+        // Figure out what day today is
+        val today = Time(tz)
+        today.setToNow()
+        mHasToday = false
+        mToday = -1
+        val focusMonth = if (params.containsKey(VIEW_PARAMS_FOCUS_MONTH)) params.get(
+                VIEW_PARAMS_FOCUS_MONTH
+        ) else DEFAULT_FOCUS_MONTH
+        while (i < mNumCells) {
+            if (time.monthDay === 1) {
+                firstMonth = time.month
+            }
+            mOddMonth!![i] = time.month % 2 === 1
+            if (time.month === focusMonth) {
+                mFocusDay!![i] = true
+            } else {
+                mFocusDay!![i] = false
+            }
+            if (time.year === today.year && time.yearDay === today.yearDay) {
+                mHasToday = true
+                mToday = i
+            }
+            mDayNumbers!![i] = Integer.toString(time.monthDay++)
+            time.normalize(true)
+            i++
+        }
+        // We do one extra add at the end of the loop, if that pushed us to a
+        // new month undo it
+        if (time.monthDay === 1) {
+            time.monthDay--
+            time.normalize(true)
+        }
+        lastMonth = time.month
+        updateSelectionPositions()
+    }
+
+    /**
+     * Sets up the text and style properties for painting. Override this if you
+     * want to use a different paint.
+     */
+    protected open fun initView() {
+        p.setFakeBoldText(false)
+        p.setAntiAlias(true)
+        p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+        p.setStyle(Style.FILL)
+        mMonthNumPaint = Paint()
+        mMonthNumPaint?.setFakeBoldText(true)
+        mMonthNumPaint?.setAntiAlias(true)
+        mMonthNumPaint?.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+        mMonthNumPaint?.setColor(mFocusMonthColor)
+        mMonthNumPaint?.setStyle(Style.FILL)
+        mMonthNumPaint?.setTextAlign(Align.CENTER)
+    }
+
+    /**
+     * Returns the month of the first day in this week
+     *
+     * @return The month the first day of this view is in
+     */
+    fun getFirstMonth(): Int {
+        return firstMonth
+    }
+
+    /**
+     * Returns the month of the last day in this week
+     *
+     * @return The month the last day of this view is in
+     */
+    fun getLastMonth(): Int {
+        return lastMonth
+    }
+
+    /**
+     * Returns the julian day of the first day in this view.
+     *
+     * @return The julian day of the first day in the view.
+     */
+    fun getFirstJulianDay(): Int {
+        return mFirstJulianDay
+    }
+
+    /**
+     * Calculates the day that the given x position is in, accounting for week
+     * number. Returns a Time referencing that day or null if
+     *
+     * @param x The x position of the touch event
+     * @return A time object for the tapped day or null if the position wasn't
+     * in a day
+     */
+    open fun getDayFromLocation(x: Float): Time? {
+        val dayStart =
+                if (mShowWeekNum) (mWidth - mPadding * 2) / mNumCells + mPadding else mPadding
+        if (x < dayStart || x > mWidth - mPadding) {
+            return null
+        }
+        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+        val dayPosition = ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)).toInt()
+        var day = mFirstJulianDay + dayPosition
+        val time = Time(mTimeZone)
+        if (mWeek == 0) {
+            // This week is weird...
+            if (day < Time.EPOCH_JULIAN_DAY) {
+                day++
+            } else if (day == Time.EPOCH_JULIAN_DAY) {
+                time.set(1, 0, 1970)
+                time.normalize(true)
+                return time
+            }
+        }
+        time.setJulianDay(day)
+        return time
+    }
+
+    @Override
+    protected override fun onDraw(canvas: Canvas) {
+        drawBackground(canvas)
+        drawWeekNums(canvas)
+        drawDaySeparators(canvas)
+    }
+
+    /**
+     * This draws the selection highlight if a day is selected in this week.
+     * Override this method if you wish to have a different background drawn.
+     *
+     * @param canvas The canvas to draw on
+     */
+    protected open fun drawBackground(canvas: Canvas) {
+        if (mHasSelectedDay) {
+            p.setColor(mSelectedWeekBGColor)
+            p.setStyle(Style.FILL)
+        } else {
+            return
+        }
+        r.top = 1
+        r.bottom = mHeight - 1
+        r.left = mPadding
+        r.right = mSelectedLeft
+        canvas.drawRect(r, p)
+        r.left = mSelectedRight
+        r.right = mWidth - mPadding
+        canvas.drawRect(r, p)
+    }
+
+    /**
+     * Draws the week and month day numbers for this week. Override this method
+     * if you need different placement.
+     *
+     * @param canvas The canvas to draw on
+     */
+    protected open fun drawWeekNums(canvas: Canvas) {
+        val y = (mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH
+        val nDays = mNumCells
+        var i = 0
+        val divisor = 2 * nDays
+        if (mShowWeekNum) {
+            p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE.toFloat())
+            p.setStyle(Style.FILL)
+            p.setTextAlign(Align.CENTER)
+            p.setAntiAlias(true)
+            p.setColor(mWeekNumColor)
+            val x = (mWidth - mPadding * 2) / divisor + mPadding
+            canvas.drawText(mDayNumbers!![0] as String, x.toFloat(), y.toFloat(), p)
+            i++
+        }
+        var isFocusMonth = mFocusDay!![i]
+        mMonthNumPaint?.setColor(if (isFocusMonth) mFocusMonthColor else mOtherMonthColor)
+        mMonthNumPaint?.setFakeBoldText(false)
+        while (i < nDays) {
+            if (mFocusDay!![i] != isFocusMonth) {
+                isFocusMonth = mFocusDay!![i]
+                mMonthNumPaint?.setColor(if (isFocusMonth) mFocusMonthColor else mOtherMonthColor)
+            }
+            if (mHasToday && mToday == i) {
+                mMonthNumPaint?.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE.toFloat())
+                mMonthNumPaint?.setFakeBoldText(true)
+            }
+            val x = (2 * i + 1) * (mWidth - mPadding * 2) / divisor + mPadding
+            canvas.drawText(mDayNumbers!![i] as String, x.toFloat(), y.toFloat(),
+                    mMonthNumPaint as Paint)
+            if (mHasToday && mToday == i) {
+                mMonthNumPaint?.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE.toFloat())
+                mMonthNumPaint?.setFakeBoldText(false)
+            }
+            i++
+        }
+    }
+
+    /**
+     * Draws a horizontal line for separating the weeks. Override this method if
+     * you want custom separators.
+     *
+     * @param canvas The canvas to draw on
+     */
+    protected open fun drawDaySeparators(canvas: Canvas) {
+        if (mHasSelectedDay) {
+            r.top = 1
+            r.bottom = mHeight - 1
+            r.left = mSelectedLeft + 1
+            r.right = mSelectedRight - 1
+            p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH.toFloat())
+            p.setStyle(Style.STROKE)
+            p.setColor(mTodayOutlineColor)
+            canvas.drawRect(r, p)
+        }
+        if (mShowWeekNum) {
+            p.setColor(mDaySeparatorColor)
+            p.setStrokeWidth(DAY_SEPARATOR_WIDTH.toFloat())
+            val x = (mWidth - mPadding * 2) / mNumCells + mPadding
+            canvas.drawLine(x.toFloat(), 0f, x.toFloat(), mHeight.toFloat(), p)
+        }
+    }
+
+    @Override
+    protected override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        mWidth = w
+        updateSelectionPositions()
+    }
+
+    /**
+     * This calculates the positions for the selected day lines.
+     */
+    protected open fun updateSelectionPositions() {
+        if (mHasSelectedDay) {
+            var selectedPosition = mSelectedDay - mWeekStart
+            if (selectedPosition < 0) {
+                selectedPosition += 7
+            }
+            if (mShowWeekNum) {
+                selectedPosition++
+            }
+            mSelectedLeft = (selectedPosition * (mWidth - mPadding * 2) / mNumCells +
+                    mPadding)
+            mSelectedRight = ((selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells +
+                    mPadding)
+        }
+    }
+
+    @Override
+    protected override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight)
+    }
+
+    @Override
+    override fun onHoverEvent(event: MotionEvent): Boolean {
+        val context: Context = getContext()
+        // only send accessibility events if accessibility and exploration are
+        // on.
+        val am: AccessibilityManager = context
+                .getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
+        if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
+            return super.onHoverEvent(event)
+        }
+        if (event.getAction() !== MotionEvent.ACTION_HOVER_EXIT) {
+            val hover: Time? = getDayFromLocation(event.getX())
+            if (hover != null &&
+                    (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) !== 0)
+            ) {
+                val millis: Long = hover.toMillis(true)
+                val date: String? = Utils.formatDateRange(
+                        context, millis, millis,
+                        DateUtils.FORMAT_SHOW_DATE
+                )
+                val accessEvent: AccessibilityEvent =
+                        AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
+                accessEvent.getText().add(date)
+                sendAccessibilityEventUnchecked(accessEvent)
+                mLastHoverTime = hover
+            }
+        }
+        return true
+    }
+
+    @JvmField var mLastHoverTime: Time? = null
+
+    companion object {
+        private const val TAG = "MonthView"
+        /**
+         * These params can be passed into the view to control how it appears.
+         * [.VIEW_PARAMS_WEEK] is the only required field, though the default
+         * values are unlikely to fit most layouts correctly.
+         */
+        /**
+         * This sets the height of this week in pixels
+         */
+        const val VIEW_PARAMS_HEIGHT = "height"
+
+        /**
+         * This specifies the position (or weeks since the epoch) of this week,
+         * calculated using [Utils.getWeeksSinceEpochFromJulianDay]
+         */
+        const val VIEW_PARAMS_WEEK = "week"
+
+        /**
+         * This sets one of the days in this view as selected [Time.SUNDAY]
+         * through [Time.SATURDAY].
+         */
+        const val VIEW_PARAMS_SELECTED_DAY = "selected_day"
+
+        /**
+         * Which day the week should start on. [Time.SUNDAY] through
+         * [Time.SATURDAY].
+         */
+        const val VIEW_PARAMS_WEEK_START = "week_start"
+
+        /**
+         * How many days to display at a time. Days will be displayed starting with
+         * [.mWeekStart].
+         */
+        const val VIEW_PARAMS_NUM_DAYS = "num_days"
+
+        /**
+         * Which month is currently in focus, as defined by [Time.month]
+         * [0-11].
+         */
+        const val VIEW_PARAMS_FOCUS_MONTH = "focus_month"
+
+        /**
+         * If this month should display week numbers. false if 0, true otherwise.
+         */
+        const val VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"
+        protected var DEFAULT_HEIGHT = 32
+        protected var MIN_HEIGHT = 10
+        protected const val DEFAULT_SELECTED_DAY = -1
+        protected val DEFAULT_WEEK_START: Int = Time.SUNDAY
+        protected const val DEFAULT_NUM_DAYS = 7
+        protected const val DEFAULT_SHOW_WK_NUM = 0
+        protected const val DEFAULT_FOCUS_MONTH = -1
+        protected var DAY_SEPARATOR_WIDTH = 1
+        protected var MINI_DAY_NUMBER_TEXT_SIZE = 14
+        protected var MINI_WK_NUMBER_TEXT_SIZE = 12
+        protected var MINI_TODAY_NUMBER_TEXT_SIZE = 18
+        protected var MINI_TODAY_OUTLINE_WIDTH = 2
+        protected var WEEK_NUM_MARGIN_BOTTOM = 4
+
+        // used for scaling to the device density
+        @JvmStatic protected var mScale = 0f
+    }
+
+    init {
+        val res: Resources = context.getResources()
+        mBGColor = res.getColor(R.color.month_bgcolor)
+        mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor)
+        mFocusMonthColor = res.getColor(R.color.month_mini_day_number)
+        mOtherMonthColor = res.getColor(R.color.month_other_month_day_number)
+        mDaySeparatorColor = res.getColor(R.color.month_grid_lines)
+        mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color)
+        mWeekNumColor = res.getColor(R.color.month_week_num_color)
+        mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light)
+        if (mScale == 0f) {
+            mScale = context.getResources().getDisplayMetrics().density
+            if (mScale != 1f) {
+                DEFAULT_HEIGHT *= mScale.toInt()
+                MIN_HEIGHT *= mScale.toInt()
+                MINI_DAY_NUMBER_TEXT_SIZE *= mScale.toInt()
+                MINI_TODAY_NUMBER_TEXT_SIZE *= mScale.toInt()
+                MINI_TODAY_OUTLINE_WIDTH *= mScale.toInt()
+                WEEK_NUM_MARGIN_BOTTOM *= mScale.toInt()
+                DAY_SEPARATOR_WIDTH *= mScale.toInt()
+                MINI_WK_NUMBER_TEXT_SIZE *= mScale.toInt()
+            }
+        }
+
+        // Sets up any standard paints that will be used
+        initView()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/month/SimpleWeeksAdapter.java b/src/com/android/calendar/month/SimpleWeeksAdapter.java
deleted file mode 100644
index d29b262..0000000
--- a/src/com/android/calendar/month/SimpleWeeksAdapter.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.month;
-
-// TODO Remove calendar imports when the required methods have been
-// refactored into the public api
-import com.android.calendar.CalendarController;
-import com.android.calendar.Utils;
-
-import android.content.Context;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * <p>
- * This is a specialized adapter for creating a list of weeks with selectable
- * days. It can be configured to display the week number, start the week on a
- * given day, show a reduced number of days, or display an arbitrary number of
- * weeks at a time. See {@link SimpleDayPickerFragment} for usage.
- * </p>
- */
-public class SimpleWeeksAdapter extends BaseAdapter implements OnTouchListener {
-
-    private static final String TAG = "MonthByWeek";
-
-    /**
-     * The number of weeks to display at a time.
-     */
-    public static final String WEEK_PARAMS_NUM_WEEKS = "num_weeks";
-    /**
-     * Which month should be in focus currently.
-     */
-    public static final String WEEK_PARAMS_FOCUS_MONTH = "focus_month";
-    /**
-     * Whether the week number should be shown. Non-zero to show them.
-     */
-    public static final String WEEK_PARAMS_SHOW_WEEK = "week_numbers";
-    /**
-     * Which day the week should start on. {@link Time#SUNDAY} through
-     * {@link Time#SATURDAY}.
-     */
-    public static final String WEEK_PARAMS_WEEK_START = "week_start";
-    /**
-     * The Julian day to highlight as selected.
-     */
-    public static final String WEEK_PARAMS_JULIAN_DAY = "selected_day";
-    /**
-     * How many days of the week to display [1-7].
-     */
-    public static final String WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week";
-
-    protected static final int WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK
-            - CalendarController.MIN_CALENDAR_WEEK;
-    protected static int DEFAULT_NUM_WEEKS = 6;
-    protected static int DEFAULT_MONTH_FOCUS = 0;
-    protected static int DEFAULT_DAYS_PER_WEEK = 7;
-    protected static int DEFAULT_WEEK_HEIGHT = 32;
-    protected static int WEEK_7_OVERHANG_HEIGHT = 7;
-
-    protected static float mScale = 0;
-    protected Context mContext;
-    // The day to highlight as selected
-    protected Time mSelectedDay;
-    // The week since 1970 that the selected day is in
-    protected int mSelectedWeek;
-    // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
-    protected int mFirstDayOfWeek;
-    protected boolean mShowWeekNumber = false;
-    protected GestureDetector mGestureDetector;
-    protected int mNumWeeks = DEFAULT_NUM_WEEKS;
-    protected int mDaysPerWeek = DEFAULT_DAYS_PER_WEEK;
-    protected int mFocusMonth = DEFAULT_MONTH_FOCUS;
-
-    public SimpleWeeksAdapter(Context context, HashMap<String, Integer> params) {
-        mContext = context;
-
-        // Get default week start based on locale, subtracting one for use with android Time.
-        Calendar cal = Calendar.getInstance(Locale.getDefault());
-        mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
-
-        if (mScale == 0) {
-            mScale = context.getResources().getDisplayMetrics().density;
-            if (mScale != 1) {
-                WEEK_7_OVERHANG_HEIGHT *= mScale;
-            }
-        }
-        init();
-        updateParams(params);
-    }
-
-    /**
-     * Set up the gesture detector and selected time
-     */
-    protected void init() {
-        mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
-        mSelectedDay = new Time();
-        mSelectedDay.setToNow();
-    }
-
-    /**
-     * Parse the parameters and set any necessary fields. See
-     * {@link #WEEK_PARAMS_NUM_WEEKS} for parameter details.
-     *
-     * @param params A list of parameters for this adapter
-     */
-    public void updateParams(HashMap<String, Integer> params) {
-        if (params == null) {
-            Log.e(TAG, "WeekParameters are null! Cannot update adapter.");
-            return;
-        }
-        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
-            mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH);
-        }
-        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
-            mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS);
-        }
-        if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
-            mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) != 0;
-        }
-        if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
-            mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START);
-        }
-        if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
-            int julianDay = params.get(WEEK_PARAMS_JULIAN_DAY);
-            mSelectedDay.setJulianDay(julianDay);
-            mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek);
-        }
-        if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
-            mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK);
-        }
-        refresh();
-    }
-
-    /**
-     * Updates the selected day and related parameters.
-     *
-     * @param selectedTime The time to highlight
-     */
-    public void setSelectedDay(Time selectedTime) {
-        mSelectedDay.set(selectedTime);
-        long millis = mSelectedDay.normalize(true);
-        mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
-                Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
-        notifyDataSetChanged();
-    }
-
-    /**
-     * Returns the currently highlighted day
-     *
-     * @return
-     */
-    public Time getSelectedDay() {
-        return mSelectedDay;
-    }
-
-    /**
-     * updates any config options that may have changed and refreshes the view
-     */
-    protected void refresh() {
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public int getCount() {
-        return WEEK_COUNT;
-    }
-
-    @Override
-    public Object getItem(int position) {
-        return null;
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return position;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        SimpleWeekView v;
-        HashMap<String, Integer> drawingParams = null;
-        if (convertView != null) {
-            v = (SimpleWeekView) convertView;
-            // We store the drawing parameters in the view so it can be recycled
-            drawingParams = (HashMap<String, Integer>) v.getTag();
-        } else {
-            v = new SimpleWeekView(mContext);
-            // Set up the new view
-            LayoutParams params = new LayoutParams(
-                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-            v.setLayoutParams(params);
-            v.setClickable(true);
-            v.setOnTouchListener(this);
-        }
-        if (drawingParams == null) {
-            drawingParams = new HashMap<String, Integer>();
-        }
-        drawingParams.clear();
-
-        int selectedDay = -1;
-        if (mSelectedWeek == position) {
-            selectedDay = mSelectedDay.weekDay;
-        }
-
-        // pass in all the view parameters
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
-                (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
-        drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
-        v.setWeekParams(drawingParams, mSelectedDay.timezone);
-        v.invalidate();
-
-        return v;
-    }
-
-    /**
-     * Changes which month is in focus and updates the view.
-     *
-     * @param month The month to show as in focus [0-11]
-     */
-    public void updateFocusMonth(int month) {
-        mFocusMonth = month;
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (mGestureDetector.onTouchEvent(event)) {
-            SimpleWeekView view = (SimpleWeekView) v;
-            Time day = ((SimpleWeekView)v).getDayFromLocation(event.getX());
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Touched day at Row=" + view.mWeek + " day=" + day.toString());
-            }
-            if (day != null) {
-                onDayTapped(day);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Maintains the same hour/min/sec but moves the day to the tapped day.
-     *
-     * @param day The day that was tapped
-     */
-    protected void onDayTapped(Time day) {
-        day.hour = mSelectedDay.hour;
-        day.minute = mSelectedDay.minute;
-        day.second = mSelectedDay.second;
-        setSelectedDay(day);
-    }
-
-
-    /**
-     * This is here so we can identify single tap events and set the selected
-     * day correctly
-     */
-    protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
-        @Override
-        public boolean onSingleTapUp(MotionEvent e) {
-            return true;
-        }
-    }
-
-    ListView mListView;
-
-    public void setListView(ListView lv) {
-        mListView = lv;
-    }
-}
diff --git a/src/com/android/calendar/month/SimpleWeeksAdapter.kt b/src/com/android/calendar/month/SimpleWeeksAdapter.kt
new file mode 100644
index 0000000..67be4d8
--- /dev/null
+++ b/src/com/android/calendar/month/SimpleWeeksAdapter.kt
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+// TODO Remove calendar imports when the required methods have been
+// refactored into the public api
+import com.android.calendar.CalendarController
+import com.android.calendar.Utils
+import android.content.Context
+import android.text.format.Time
+import android.util.Log
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewGroup
+import android.widget.AbsListView.LayoutParams
+import android.widget.BaseAdapter
+import android.widget.ListView
+import java.util.Calendar
+import java.util.HashMap
+import java.util.Locale
+
+/**
+ *
+ *
+ * This is a specialized adapter for creating a list of weeks with selectable
+ * days. It can be configured to display the week number, start the week on a
+ * given day, show a reduced number of days, or display an arbitrary number of
+ * weeks at a time. See [SimpleDayPickerFragment] for usage.
+ *
+ */
+open class SimpleWeeksAdapter(context: Context, params: HashMap<String?, Int?>?) : BaseAdapter(),
+    OnTouchListener {
+    protected var mContext: Context
+
+    // The day to highlight as selected
+    protected var mSelectedDay: Time? = null
+
+    // The week since 1970 that the selected day is in
+    protected var mSelectedWeek = 0
+
+    // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
+    protected var mFirstDayOfWeek: Int
+    protected var mShowWeekNumber = false
+    protected var mGestureDetector: GestureDetector? = null
+    protected var mNumWeeks = DEFAULT_NUM_WEEKS
+    protected var mDaysPerWeek = DEFAULT_DAYS_PER_WEEK
+    protected var mFocusMonth = DEFAULT_MONTH_FOCUS
+
+    /**
+     * Set up the gesture detector and selected time
+     */
+    protected open fun init() {
+        mGestureDetector = GestureDetector(mContext, CalendarGestureListener())
+        mSelectedDay = Time()
+        mSelectedDay?.setToNow()
+    }
+
+    /**
+     * Parse the parameters and set any necessary fields. See
+     * [.WEEK_PARAMS_NUM_WEEKS] for parameter details.
+     *
+     * @param params A list of parameters for this adapter
+     */
+    fun updateParams(params: HashMap<String?, Int?>?) {
+        if (params == null) {
+            Log.e(TAG, "WeekParameters are null! Cannot update adapter.")
+            return
+        }
+        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
+            // Casting from Int? --> Int
+            mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH) as Int
+        }
+        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
+            // Casting from Int? --> Int
+            mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS) as Int
+        }
+        if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
+            // Casting from Int? --> Int
+            mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) as Int != 0
+        }
+        if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
+            // Casting from Int? --> Int
+            mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START) as Int
+        }
+        if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
+            // Casting from Int? --> Int
+            val julianDay: Int = params.get(WEEK_PARAMS_JULIAN_DAY) as Int
+            mSelectedDay?.setJulianDay(julianDay)
+            mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek)
+        }
+        if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
+            // Casting from Int? --> Int
+            mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK) as Int
+        }
+        refresh()
+    }
+
+    /**
+     * Updates the selected day and related parameters.
+     *
+     * @param selectedTime The time to highlight
+     */
+    open fun setSelectedDay(selectedTime: Time?) {
+        mSelectedDay?.set(selectedTime)
+        val millis: Long = mSelectedDay!!.normalize(true)
+        mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
+            Time.getJulianDay(millis, mSelectedDay!!.gmtoff), mFirstDayOfWeek
+        )
+        notifyDataSetChanged()
+    }
+
+    /**
+     * Returns the currently highlighted day
+     *
+     * @return
+     */
+    fun getSelectedDay(): Time? {
+        return mSelectedDay
+    }
+
+    /**
+     * updates any config options that may have changed and refreshes the view
+     */
+    internal open fun refresh() {
+        notifyDataSetChanged()
+    }
+
+    @Override
+    override fun getCount(): Int {
+        return WEEK_COUNT
+    }
+
+    @Override
+    override fun getItem(position: Int): Any? {
+        return null
+    }
+
+    @Override
+    override fun getItemId(position: Int): Long {
+        return position.toLong()
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+        val v: SimpleWeekView
+        var drawingParams: HashMap<String?, Int?>? = null
+        if (convertView != null) {
+            v = convertView as SimpleWeekView
+            // We store the drawing parameters in the view so it can be recycled
+            drawingParams = v.getTag() as HashMap<String?, Int?>
+        } else {
+            v = SimpleWeekView(mContext)
+            // Set up the new view
+            val params = LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
+            )
+            v.setLayoutParams(params)
+            v.setClickable(true)
+            v.setOnTouchListener(this)
+        }
+        if (drawingParams == null) {
+            drawingParams = HashMap<String?, Int?>()
+        }
+        drawingParams.clear()
+        var selectedDay = -1
+        if (mSelectedWeek == position) {
+            selectedDay = mSelectedDay!!.weekDay
+        }
+
+        // pass in all the view parameters
+        drawingParams.put(
+            SimpleWeekView.VIEW_PARAMS_HEIGHT,
+            (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks
+        )
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, if (mShowWeekNumber) 1 else 0)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position)
+        drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth)
+        v.setWeekParams(drawingParams, mSelectedDay!!.timezone)
+        v.invalidate()
+        return v
+    }
+
+    /**
+     * Changes which month is in focus and updates the view.
+     *
+     * @param month The month to show as in focus [0-11]
+     */
+    fun updateFocusMonth(month: Int) {
+        mFocusMonth = month
+        notifyDataSetChanged()
+    }
+
+    @Override
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        if (mGestureDetector!!.onTouchEvent(event)) {
+            val view: SimpleWeekView = v as SimpleWeekView
+            val day: Time? = (v as SimpleWeekView).getDayFromLocation(event.getX())
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Touched day at Row=" + view.mWeek.toString() + " day=" +
+                    day?.toString())
+            }
+            if (day != null) {
+                onDayTapped(day)
+            }
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Maintains the same hour/min/sec but moves the day to the tapped day.
+     *
+     * @param day The day that was tapped
+     */
+    protected open fun onDayTapped(day: Time) {
+        day.hour = mSelectedDay!!.hour
+        day.minute = mSelectedDay!!.minute
+        day.second = mSelectedDay!!.second
+        setSelectedDay(day)
+    }
+
+    /**
+     * This is here so we can identify single tap events and set the selected
+     * day correctly
+     */
+    protected inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
+        @Override
+        override fun onSingleTapUp(e: MotionEvent?): Boolean {
+            return true
+        }
+    }
+
+    var mListView: ListView? = null
+    fun setListView(lv: ListView?) {
+        mListView = lv
+    }
+
+    companion object {
+        private const val TAG = "MonthByWeek"
+
+        /**
+         * The number of weeks to display at a time.
+         */
+        const val WEEK_PARAMS_NUM_WEEKS = "num_weeks"
+
+        /**
+         * Which month should be in focus currently.
+         */
+        const val WEEK_PARAMS_FOCUS_MONTH = "focus_month"
+
+        /**
+         * Whether the week number should be shown. Non-zero to show them.
+         */
+        const val WEEK_PARAMS_SHOW_WEEK = "week_numbers"
+
+        /**
+         * Which day the week should start on. [Time.SUNDAY] through
+         * [Time.SATURDAY].
+         */
+        const val WEEK_PARAMS_WEEK_START = "week_start"
+
+        /**
+         * The Julian day to highlight as selected.
+         */
+        const val WEEK_PARAMS_JULIAN_DAY = "selected_day"
+
+        /**
+         * How many days of the week to display [1-7].
+         */
+        const val WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week"
+        protected const val WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK -
+            CalendarController.MIN_CALENDAR_WEEK
+        protected var DEFAULT_NUM_WEEKS = 6
+        protected var DEFAULT_MONTH_FOCUS = 0
+        protected var DEFAULT_DAYS_PER_WEEK = 7
+        protected var DEFAULT_WEEK_HEIGHT = 32
+        protected var WEEK_7_OVERHANG_HEIGHT = 7
+        protected var mScale = 0f
+    }
+
+    init {
+        mContext = context
+
+        // Get default week start based on locale, subtracting one for use with android Time.
+        val cal: Calendar = Calendar.getInstance(Locale.getDefault())
+        mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1
+        if (mScale == 0f) {
+            mScale = context.getResources().getDisplayMetrics().density
+            if (mScale != 1f) {
+                WEEK_7_OVERHANG_HEIGHT *= mScale.toInt()
+            }
+        }
+        init()
+        updateParams(params)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.java b/src/com/android/calendar/widget/CalendarAppWidgetModel.java
deleted file mode 100644
index a989e18..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetModel.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.widget;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.TimeZone;
-
-class CalendarAppWidgetModel {
-    private static final String TAG = CalendarAppWidgetModel.class.getSimpleName();
-    private static final boolean LOGD = false;
-
-    private String mHomeTZName;
-    private boolean mShowTZ;
-    /**
-     * {@link RowInfo} is a class that represents a single row in the widget. It
-     * is actually only a pointer to either a {@link DayInfo} or an
-     * {@link EventInfo} instance, since a row in the widget might be either a
-     * day header or an event.
-     */
-    static class RowInfo {
-        static final int TYPE_DAY = 0;
-        static final int TYPE_MEETING = 1;
-
-        /**
-         *  mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
-         */
-        final int mType;
-
-        /**
-         * If mType is TYPE_DAY, then mData is the index into day infos.
-         * Otherwise mType is TYPE_MEETING and mData is the index into event
-         * infos.
-         */
-        final int mIndex;
-
-        RowInfo(int type, int index) {
-            mType = type;
-            mIndex = index;
-        }
-    }
-
-    /**
-     * {@link EventInfo} is a class that represents an event in the widget. It
-     * contains all of the data necessary to display that event, including the
-     * properly localized strings and visibility settings.
-     */
-    static class EventInfo {
-        int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
-        String when;
-        int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
-        String where;
-        int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
-        String title;
-        int selfAttendeeStatus;
-
-        long id;
-        long start;
-        long end;
-        boolean allDay;
-        int color;
-
-        public EventInfo() {
-            visibWhen = View.GONE;
-            visibWhere = View.GONE;
-            visibTitle = View.GONE;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            builder.append("EventInfo [visibTitle=");
-            builder.append(visibTitle);
-            builder.append(", title=");
-            builder.append(title);
-            builder.append(", visibWhen=");
-            builder.append(visibWhen);
-            builder.append(", id=");
-            builder.append(id);
-            builder.append(", when=");
-            builder.append(when);
-            builder.append(", visibWhere=");
-            builder.append(visibWhere);
-            builder.append(", where=");
-            builder.append(where);
-            builder.append(", color=");
-            builder.append(String.format("0x%x", color));
-            builder.append(", selfAttendeeStatus=");
-            builder.append(selfAttendeeStatus);
-            builder.append("]");
-            return builder.toString();
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + (allDay ? 1231 : 1237);
-            result = prime * result + (int) (id ^ (id >>> 32));
-            result = prime * result + (int) (end ^ (end >>> 32));
-            result = prime * result + (int) (start ^ (start >>> 32));
-            result = prime * result + ((title == null) ? 0 : title.hashCode());
-            result = prime * result + visibTitle;
-            result = prime * result + visibWhen;
-            result = prime * result + visibWhere;
-            result = prime * result + ((when == null) ? 0 : when.hashCode());
-            result = prime * result + ((where == null) ? 0 : where.hashCode());
-            result = prime * result + color;
-            result = prime * result + selfAttendeeStatus;
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            EventInfo other = (EventInfo) obj;
-            if (id != other.id)
-                return false;
-            if (allDay != other.allDay)
-                return false;
-            if (end != other.end)
-                return false;
-            if (start != other.start)
-                return false;
-            if (title == null) {
-                if (other.title != null)
-                    return false;
-            } else if (!title.equals(other.title))
-                return false;
-            if (visibTitle != other.visibTitle)
-                return false;
-            if (visibWhen != other.visibWhen)
-                return false;
-            if (visibWhere != other.visibWhere)
-                return false;
-            if (when == null) {
-                if (other.when != null)
-                    return false;
-            } else if (!when.equals(other.when)) {
-                return false;
-            }
-            if (where == null) {
-                if (other.where != null)
-                    return false;
-            } else if (!where.equals(other.where)) {
-                return false;
-            }
-            if (color != other.color) {
-                return false;
-            }
-            if (selfAttendeeStatus != other.selfAttendeeStatus) {
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * {@link DayInfo} is a class that represents a day header in the widget. It
-     * contains all of the data necessary to display that day header, including
-     * the properly localized string.
-     */
-    static class DayInfo {
-
-        /** The Julian day */
-        final int mJulianDay;
-
-        /** The string representation of this day header, to be displayed */
-        final String mDayLabel;
-
-        DayInfo(int julianDay, String label) {
-            mJulianDay = julianDay;
-            mDayLabel = label;
-        }
-
-        @Override
-        public String toString() {
-            return mDayLabel;
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode());
-            result = prime * result + mJulianDay;
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            DayInfo other = (DayInfo) obj;
-            if (mDayLabel == null) {
-                if (other.mDayLabel != null)
-                    return false;
-            } else if (!mDayLabel.equals(other.mDayLabel))
-                return false;
-            if (mJulianDay != other.mJulianDay)
-                return false;
-            return true;
-        }
-
-    }
-
-    final List<RowInfo> mRowInfos;
-    final List<EventInfo> mEventInfos;
-    final List<DayInfo> mDayInfos;
-    final Context mContext;
-    final long mNow;
-    final int mTodayJulianDay;
-    final int mMaxJulianDay;
-
-    public CalendarAppWidgetModel(Context context, String timeZone) {
-        mNow = System.currentTimeMillis();
-        Time time = new Time(timeZone);
-        time.setToNow(); // This is needed for gmtoff to be set
-        mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff);
-        mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1;
-        mEventInfos = new ArrayList<EventInfo>(50);
-        mRowInfos = new ArrayList<RowInfo>(50);
-        mDayInfos = new ArrayList<DayInfo>(8);
-        mContext = context;
-    }
-
-    public void buildFromCursor(Cursor cursor, String timeZone) {
-        final Time recycle = new Time(timeZone);
-        final ArrayList<LinkedList<RowInfo>> mBuckets =
-                new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS);
-        for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) {
-            mBuckets.add(new LinkedList<RowInfo>());
-        }
-        recycle.setToNow();
-        mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone());
-        if (mShowTZ) {
-            mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0,
-                    TimeZone.SHORT);
-        }
-
-        cursor.moveToPosition(-1);
-        String tz = Utils.getTimeZone(mContext, null);
-        while (cursor.moveToNext()) {
-            final int rowId = cursor.getPosition();
-            final long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID);
-            final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0;
-            long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN);
-            long end = cursor.getLong(CalendarAppWidgetService.INDEX_END);
-            final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE);
-            final String location =
-                    cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION);
-            // we don't compute these ourselves because it seems to produce the
-            // wrong endDay for all day events
-            final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY);
-            final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY);
-            final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR);
-            final int selfStatus = cursor
-                    .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS);
-
-            // Adjust all-day times into local timezone
-            if (allDay) {
-                start = Utils.convertAlldayUtcToLocal(recycle, start, tz);
-                end = Utils.convertAlldayUtcToLocal(recycle, end, tz);
-            }
-
-            if (LOGD) {
-                Log.d(TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start
-                        + " end:" + end + " eventId:" + eventId);
-            }
-
-            // we might get some extra events when querying, in order to
-            // deal with all-day events
-            if (end < mNow) {
-                continue;
-            }
-
-            int i = mEventInfos.size();
-            mEventInfos.add(populateEventInfo(eventId, allDay, start, end, startDay, endDay, title,
-                    location, color, selfStatus));
-            // populate the day buckets that this event falls into
-            int from = Math.max(startDay, mTodayJulianDay);
-            int to = Math.min(endDay, mMaxJulianDay);
-            for (int day = from; day <= to; day++) {
-                LinkedList<RowInfo> bucket = mBuckets.get(day - mTodayJulianDay);
-                RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i);
-                if (allDay) {
-                    bucket.addFirst(rowInfo);
-                } else {
-                    bucket.add(rowInfo);
-                }
-            }
-        }
-
-        int day = mTodayJulianDay;
-        int count = 0;
-        for (LinkedList<RowInfo> bucket : mBuckets) {
-            if (!bucket.isEmpty()) {
-                // We don't show day header in today
-                if (day != mTodayJulianDay) {
-                    final DayInfo dayInfo = populateDayInfo(day, recycle);
-                    // Add the day header
-                    final int dayIndex = mDayInfos.size();
-                    mDayInfos.add(dayInfo);
-                    mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex));
-                }
-
-                // Add the event row infos
-                mRowInfos.addAll(bucket);
-                count += bucket.size();
-            }
-            day++;
-            if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
-                break;
-            }
-        }
-    }
-
-    private EventInfo populateEventInfo(long eventId, boolean allDay, long start, long end,
-            int startDay, int endDay, String title, String location, int color, int selfStatus) {
-        EventInfo eventInfo = new EventInfo();
-
-        // Compute a human-readable string for the start time of the event
-        StringBuilder whenString = new StringBuilder();
-        int visibWhen;
-        int flags = DateUtils.FORMAT_ABBREV_ALL;
-        visibWhen = View.VISIBLE;
-        if (allDay) {
-            flags |= DateUtils.FORMAT_SHOW_DATE;
-            whenString.append(Utils.formatDateRange(mContext, start, end, flags));
-        } else {
-            flags |= DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(mContext)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            if (endDay > startDay) {
-                flags |= DateUtils.FORMAT_SHOW_DATE;
-            }
-            whenString.append(Utils.formatDateRange(mContext, start, end, flags));
-
-            if (mShowTZ) {
-                whenString.append(" ").append(mHomeTZName);
-            }
-        }
-        eventInfo.id = eventId;
-        eventInfo.start = start;
-        eventInfo.end = end;
-        eventInfo.allDay = allDay;
-        eventInfo.when = whenString.toString();
-        eventInfo.visibWhen = visibWhen;
-        eventInfo.color = color;
-        eventInfo.selfAttendeeStatus = selfStatus;
-
-        // What
-        if (TextUtils.isEmpty(title)) {
-            eventInfo.title = mContext.getString(R.string.no_title_label);
-        } else {
-            eventInfo.title = title;
-        }
-        eventInfo.visibTitle = View.VISIBLE;
-
-        // Where
-        if (!TextUtils.isEmpty(location)) {
-            eventInfo.visibWhere = View.VISIBLE;
-            eventInfo.where = location;
-        } else {
-            eventInfo.visibWhere = View.GONE;
-        }
-        return eventInfo;
-    }
-
-    private DayInfo populateDayInfo(int julianDay, Time recycle) {
-        long millis = recycle.setJulianDay(julianDay);
-        int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE;
-
-        String label;
-        if (julianDay == mTodayJulianDay + 1) {
-            label = mContext.getString(R.string.agenda_tomorrow,
-                    Utils.formatDateRange(mContext, millis, millis, flags).toString());
-        } else {
-            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
-            label = Utils.formatDateRange(mContext, millis, millis, flags);
-        }
-        return new DayInfo(julianDay, label);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("\nCalendarAppWidgetModel [eventInfos=");
-        builder.append(mEventInfos);
-        builder.append("]");
-        return builder.toString();
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.kt b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
new file mode 100644
index 0000000..440d178
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.content.Context
+import android.database.Cursor
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.View
+import java.util.ArrayList
+import java.util.LinkedList
+import java.util.TimeZone
+
+internal class CalendarAppWidgetModel(context: Context, timeZone: String?) {
+    private var mHomeTZName: String? = null
+    private var mShowTZ = false
+
+    /**
+     * [RowInfo] is a class that represents a single row in the widget. It
+     * is actually only a pointer to either a [DayInfo] or an
+     * [EventInfo] instance, since a row in the widget might be either a
+     * day header or an event.
+     */
+    internal class RowInfo(
+        /**
+         * mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
+         */
+        @JvmField val mType: Int,
+        /**
+         * If mType is TYPE_DAY, then mData is the index into day infos.
+         * Otherwise mType is TYPE_MEETING and mData is the index into event
+         * infos.
+         */
+        @JvmField val mIndex: Int
+    ) {
+        companion object {
+            const val TYPE_DAY = 0
+            const val TYPE_MEETING = 1
+        }
+    }
+
+    /**
+     * [EventInfo] is a class that represents an event in the widget. It
+     * contains all of the data necessary to display that event, including the
+     * properly localized strings and visibility settings.
+     */
+    internal class EventInfo {
+        // Visibility value for When textview (View.GONE or View.VISIBLE)
+        @JvmField var visibWhen: Int
+        @JvmField var `when`: String? = null
+        // Visibility value for Where textview (View.GONE or View.VISIBLE)
+        @JvmField var visibWhere: Int
+        @JvmField var where: String? = null
+        // Visibility value for Title textview (View.GONE or View.VISIBLE)
+        @JvmField var visibTitle: Int
+        @JvmField var title: String? = null
+        @JvmField var selfAttendeeStatus = 0
+        @JvmField var id: Long = 0
+        @JvmField var start: Long = 0
+        @JvmField var end: Long = 0
+        @JvmField var allDay = false
+        @JvmField var color = 0
+
+        @Override
+        override fun toString(): String {
+            val builder = StringBuilder()
+            builder.append("EventInfo [visibTitle=")
+            builder.append(visibTitle)
+            builder.append(", title=")
+            builder.append(title)
+            builder.append(", visibWhen=")
+            builder.append(visibWhen)
+            builder.append(", id=")
+            builder.append(id)
+            builder.append(", when=")
+            builder.append(`when`)
+            builder.append(", visibWhere=")
+            builder.append(visibWhere)
+            builder.append(", where=")
+            builder.append(where)
+            builder.append(", color=")
+            builder.append(String.format("0x%x", color))
+            builder.append(", selfAttendeeStatus=")
+            builder.append(selfAttendeeStatus)
+            builder.append("]")
+            return builder.toString()
+        }
+
+        @Override
+        override fun hashCode(): Int {
+            val prime = 31
+            var result = 1
+            result = prime * result + if (allDay) 1231 else 1237
+            result = prime * result + (id xor (id ushr 32)).toInt()
+            result = prime * result + (end xor (end ushr 32)).toInt()
+            result = prime * result + (start xor (start ushr 32)).toInt()
+            result = prime * result + if (title == null) 0 else title!!.hashCode()
+            result = prime * result + visibTitle
+            result = prime * result + visibWhen
+            result = prime * result + visibWhere
+            result = prime * result + if (`when` == null) 0 else `when`!!.hashCode()
+            result = prime * result + if (where == null) 0 else where!!.hashCode()
+            result = prime * result + color
+            result = prime * result + selfAttendeeStatus
+            return result
+        }
+
+        @Override
+        override fun equals(obj: Any?): Boolean {
+            if (this == obj) return true
+            if (obj == null) return false
+            if (this::class != obj::class) return false
+            val other = obj as EventInfo
+            if (id != other.id) return false
+            if (allDay != other.allDay) return false
+            if (end != other.end) return false
+            if (start != other.start) return false
+            if (title == null) {
+                if (other.title != null) return false
+            } else if (!title!!.equals(other.title)) return false
+            if (visibTitle != other.visibTitle) return false
+            if (visibWhen != other.visibWhen) return false
+            if (visibWhere != other.visibWhere) return false
+            if (`when` == null) {
+                if (other.`when` != null) return false
+            } else if (!`when`!!.equals(other.`when`)) {
+                return false
+            }
+            if (where == null) {
+                if (other.where != null) return false
+            } else if (!where!!.equals(other.where)) {
+                return false
+            }
+            if (color != other.color) {
+                return false
+            }
+            return if (selfAttendeeStatus != other.selfAttendeeStatus) {
+                false
+            } else true
+        }
+
+        init {
+            visibWhen = View.GONE
+            visibWhere = View.GONE
+            visibTitle = View.GONE
+        }
+    }
+
+    /**
+     * [DayInfo] is a class that represents a day header in the widget. It
+     * contains all of the data necessary to display that day header, including
+     * the properly localized string.
+     */
+    internal class DayInfo(
+        /** The Julian day  */
+        @JvmField var mJulianDay: Int,
+        /** The string representation of this day header, to be displayed  */
+        @JvmField var mDayLabel: String? = null
+    ) {
+        @Override
+        override fun toString(): String {
+            return mDayLabel as String
+        }
+
+        @Override
+        override fun hashCode(): Int {
+            val prime = 31
+            var result = 1
+            result = prime * result + (mDayLabel?.hashCode() ?: 0)
+            result = prime * result + mJulianDay
+            return result
+        }
+
+        @Override
+        override fun equals(obj: Any?): Boolean {
+            if (this == obj) return true
+            if (obj == null) return false
+            if (this::class !== obj::class) return false
+            val other = obj as DayInfo
+            if (mDayLabel == null) {
+                if (other.mDayLabel != null) return false
+            } else if (!mDayLabel.equals(other.mDayLabel)) return false
+            return if (mJulianDay != other.mJulianDay) false else true
+        }
+    }
+
+    @JvmField val mRowInfos: ArrayList<RowInfo>
+    @JvmField val mEventInfos: ArrayList<EventInfo>
+    @JvmField val mDayInfos: ArrayList<DayInfo>
+    @JvmField val mContext: Context?
+    @JvmField val mNow: Long
+    @JvmField val mTodayJulianDay: Int
+    @JvmField val mMaxJulianDay: Int
+    fun buildFromCursor(cursor: Cursor, timeZone: String?) {
+        val recycle = Time(timeZone)
+        val mBuckets: ArrayList<LinkedList<RowInfo>> =
+            ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS)
+        for (i in 0 until CalendarAppWidgetService.MAX_DAYS) {
+            mBuckets.add(LinkedList<RowInfo>())
+        }
+        recycle.setToNow()
+        mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone())
+        if (mShowTZ) {
+            mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(
+                recycle.isDst !== 0,
+                TimeZone.SHORT
+            )
+        }
+        cursor.moveToPosition(-1)
+        val tz = Utils.getTimeZone(mContext, null)
+        while (cursor.moveToNext()) {
+            val rowId: Int = cursor.getPosition()
+            val eventId: Long = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID)
+            val allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) !== 0
+            var start: Long = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN)
+            var end: Long = cursor.getLong(CalendarAppWidgetService.INDEX_END)
+            val title: String = cursor.getString(CalendarAppWidgetService.INDEX_TITLE)
+            val location: String = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION)
+            // we don't compute these ourselves because it seems to produce the
+            // wrong endDay for all day events
+            val startDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY)
+            val endDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY)
+            val color: Int = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR)
+            val selfStatus: Int = cursor
+                .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS)
+
+            // Adjust all-day times into local timezone
+            if (allDay) {
+                start = Utils.convertAlldayUtcToLocal(recycle, start, tz as String)
+                end = Utils.convertAlldayUtcToLocal(recycle, end, tz as String)
+            }
+            if (LOGD) {
+                Log.d(
+                    TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start +
+                        " end:" + end + " eventId:" + eventId
+                )
+            }
+
+            // we might get some extra events when querying, in order to
+            // deal with all-day events
+            if (end < mNow) {
+                continue
+            }
+            val i: Int = mEventInfos.size
+            mEventInfos.add(
+                populateEventInfo(
+                    eventId, allDay, start, end, startDay, endDay, title,
+                    location, color, selfStatus
+                )
+            )
+            // populate the day buckets that this event falls into
+            val from: Int = Math.max(startDay, mTodayJulianDay)
+            val to: Int = Math.min(endDay, mMaxJulianDay)
+            for (day in from..to) {
+                val bucket: LinkedList<RowInfo> = mBuckets.get(day - mTodayJulianDay)
+                val rowInfo = RowInfo(RowInfo.TYPE_MEETING, i)
+                if (allDay) {
+                    bucket.addFirst(rowInfo)
+                } else {
+                    bucket.add(rowInfo)
+                }
+            }
+        }
+        var day = mTodayJulianDay
+        var count = 0
+        for (bucket in mBuckets) {
+            if (!bucket.isEmpty()) {
+                // We don't show day header in today
+                if (day != mTodayJulianDay) {
+                    val dayInfo = populateDayInfo(day, recycle)
+                    // Add the day header
+                    val dayIndex: Int = mDayInfos.size
+                    mDayInfos.add(dayInfo as CalendarAppWidgetModel.DayInfo)
+                    mRowInfos.add(RowInfo(RowInfo.TYPE_DAY, dayIndex))
+                }
+
+                // Add the event row infos
+                mRowInfos.addAll(bucket)
+                count += bucket.size
+            }
+            day++
+            if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
+                break
+            }
+        }
+    }
+
+    private fun populateEventInfo(
+        eventId: Long,
+        allDay: Boolean,
+        start: Long,
+        end: Long,
+        startDay: Int,
+        endDay: Int,
+        title: String,
+        location: String,
+        color: Int,
+        selfStatus: Int
+    ): EventInfo {
+        val eventInfo = EventInfo()
+
+        // Compute a human-readable string for the start time of the event
+        val whenString = StringBuilder()
+        val visibWhen: Int
+        var flags: Int = DateUtils.FORMAT_ABBREV_ALL
+        visibWhen = View.VISIBLE
+        if (allDay) {
+            flags = flags or DateUtils.FORMAT_SHOW_DATE
+            whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+        } else {
+            flags = flags or DateUtils.FORMAT_SHOW_TIME
+            if (DateFormat.is24HourFormat(mContext)) {
+                flags = flags or DateUtils.FORMAT_24HOUR
+            }
+            if (endDay > startDay) {
+                flags = flags or DateUtils.FORMAT_SHOW_DATE
+            }
+            whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+            if (mShowTZ) {
+                whenString.append(" ").append(mHomeTZName)
+            }
+        }
+        eventInfo.id = eventId
+        eventInfo.start = start
+        eventInfo.end = end
+        eventInfo.allDay = allDay
+        eventInfo.`when` = whenString.toString()
+        eventInfo.visibWhen = visibWhen
+        eventInfo.color = color
+        eventInfo.selfAttendeeStatus = selfStatus
+
+        // What
+        if (TextUtils.isEmpty(title)) {
+            eventInfo.title = mContext?.getString(R.string.no_title_label)
+        } else {
+            eventInfo.title = title
+        }
+        eventInfo.visibTitle = View.VISIBLE
+
+        // Where
+        if (!TextUtils.isEmpty(location)) {
+            eventInfo.visibWhere = View.VISIBLE
+            eventInfo.where = location
+        } else {
+            eventInfo.visibWhere = View.GONE
+        }
+        return eventInfo
+    }
+
+    private fun populateDayInfo(julianDay: Int, recycle: Time?): DayInfo? {
+        val millis: Long = recycle?.setJulianDay(julianDay) as Long
+        var flags: Int = DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
+        val label: String?
+        if (julianDay == mTodayJulianDay + 1) {
+            label = mContext?.getString(
+                R.string.agenda_tomorrow,
+                Utils.formatDateRange(mContext, millis, millis, flags).toString()
+            )
+        } else {
+            flags = flags or DateUtils.FORMAT_SHOW_WEEKDAY
+            label = Utils.formatDateRange(mContext, millis, millis, flags)
+        }
+        return DayInfo(julianDay, label as String)
+    }
+
+    @Override
+    override fun toString(): String {
+        val builder = StringBuilder()
+        builder.append("\nCalendarAppWidgetModel [eventInfos=")
+        builder.append(mEventInfos)
+        builder.append("]")
+        return builder.toString()
+    }
+
+    companion object {
+        private val TAG: String = CalendarAppWidgetModel::class.java.getSimpleName()
+        private const val LOGD = false
+    }
+
+    init {
+        mNow = System.currentTimeMillis()
+        val time = Time(timeZone)
+        time.setToNow() // This is needed for gmtoff to be set
+        mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff)
+        mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1
+        mEventInfos = ArrayList<EventInfo>(50)
+        mRowInfos = ArrayList<RowInfo>(50)
+        mDayInfos = ArrayList<DayInfo>(8)
+        mContext = context
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
deleted file mode 100644
index 3a69efd..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.widget;
-
-import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.widget.RemoteViews;
-
-import com.android.calendar.AllInOneActivity;
-import com.android.calendar.EventInfoActivity;
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-
-/**
- * Simple widget to show next upcoming calendar event.
- */
-public class CalendarAppWidgetProvider extends AppWidgetProvider {
-    static final String TAG = "CalendarAppWidgetProvider";
-    static final boolean LOGD = false;
-
-    // TODO Move these to Calendar.java
-    static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS";
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        // Handle calendar-specific updates ourselves because they might be
-        // coming in without extras, which AppWidgetProvider then blocks.
-        final String action = intent.getAction();
-        if (LOGD)
-            Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString());
-        if (Utils.getWidgetUpdateAction(context).equals(action)) {
-            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
-            performUpdate(context, appWidgetManager,
-                    appWidgetManager.getAppWidgetIds(getComponentName(context)),
-                    null /* no eventIds */);
-        } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED)
-                || action.equals(Intent.ACTION_TIME_CHANGED)
-                || action.equals(Intent.ACTION_TIMEZONE_CHANGED)
-                || action.equals(Intent.ACTION_DATE_CHANGED)
-                || action.equals(Utils.getWidgetScheduledUpdateAction(context)))) {
-            Intent service = new Intent(context, CalendarAppWidgetService.class);
-            context.startService(service);
-        } else {
-            super.onReceive(context, intent);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onDisabled(Context context) {
-        // Unsubscribe from all AlarmManager updates
-        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        PendingIntent pendingUpdate = getUpdateIntent(context);
-        am.cancel(pendingUpdate);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
-        performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */);
-    }
-
-
-    /**
-     * Build {@link ComponentName} describing this specific
-     * {@link AppWidgetProvider}
-     */
-    static ComponentName getComponentName(Context context) {
-        return new ComponentName(context, CalendarAppWidgetProvider.class);
-    }
-
-    /**
-     * Process and push out an update for the given appWidgetIds. This call
-     * actually fires an intent to start {@link CalendarAppWidgetService} as a
-     * background service which handles the actual update, to prevent ANR'ing
-     * during database queries.
-     *
-     * @param context Context to use when starting {@link CalendarAppWidgetService}.
-     * @param appWidgetIds List of specific appWidgetIds to update, or null for
-     *            all.
-     * @param changedEventIds Specific events known to be changed. If present,
-     *            we use it to decide if an update is necessary.
-     */
-    private void performUpdate(Context context,
-            AppWidgetManager appWidgetManager, int[] appWidgetIds,
-            long[] changedEventIds) {
-        // Launch over to service so it can perform update
-        for (int appWidgetId : appWidgetIds) {
-            if (LOGD) Log.d(TAG, "Building widget update...");
-            Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
-            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
-            if (changedEventIds != null) {
-                updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
-            }
-            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
-
-            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
-            // Calendar header
-            Time time = new Time(Utils.getTimeZone(context, null));
-            time.setToNow();
-            long millis = time.toMillis(true);
-            final String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1,
-                    DateUtils.LENGTH_MEDIUM);
-            final String date = Utils.formatDateRange(context, millis, millis,
-                    DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
-                            | DateUtils.FORMAT_NO_YEAR);
-            views.setTextViewText(R.id.day_of_week, dayOfWeek);
-            views.setTextViewText(R.id.date, date);
-            // Attach to list of events
-            views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent);
-            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list);
-
-
-            // Launch calendar app when the user taps on the header
-            final Intent launchCalendarIntent = new Intent(Intent.ACTION_VIEW);
-            launchCalendarIntent.setClass(context, AllInOneActivity.class);
-            launchCalendarIntent
-                    .setData(Uri.parse("content://com.android.calendar/time/" + millis));
-            final PendingIntent launchCalendarPendingIntent = PendingIntent.getActivity(
-                    context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */);
-            views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent);
-
-            // Each list item will call setOnClickExtra() to let the list know
-            // which item
-            // is selected by a user.
-            final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context);
-            views.setPendingIntentTemplate(R.id.events_list, updateEventIntent);
-
-            appWidgetManager.updateAppWidget(appWidgetId, views);
-        }
-    }
-
-    /**
-     * Build the {@link PendingIntent} used to trigger an update of all calendar
-     * widgets. Uses {@link Utils#getWidgetScheduledUpdateAction(Context)} to
-     * directly target all widgets instead of using
-     * {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
-     *
-     * @param context Context to use when building broadcast.
-     */
-    static PendingIntent getUpdateIntent(Context context) {
-        Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(context));
-        intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE);
-        return PendingIntent.getBroadcast(context, 0 /* no requestCode */, intent,
-                0 /* no flags */);
-    }
-
-    /**
-     * Build a {@link PendingIntent} to launch the Calendar app. This should be used
-     * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}.
-     */
-    static PendingIntent getLaunchPendingIntentTemplate(Context context) {
-        Intent launchIntent = new Intent();
-        launchIntent.setAction(Intent.ACTION_VIEW);
-        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
-                Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            launchIntent.setClass(context, AllInOneActivity.class);
-            return PendingIntent.getActivity(context, 0 /* no requestCode */, launchIntent,
-                    PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-
-    /**
-     * Build an {@link Intent} available as FillInIntent to launch the Calendar app.
-     * This should be used in combination with
-     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
-     * If the go to time is 0, then calendar will be launched without a starting time.
-     *
-     * @param goToTime time that calendar should take the user to, or 0 to
-     *            indicate no specific start time.
-     */
-    static Intent getLaunchFillInIntent(Context context, long id, long start, long end,
-            boolean allDay) {
-        final Intent fillInIntent = new Intent();
-        String dataString = "content://com.android.calendar/events";
-        if (id != 0) {
-            fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true);
-            fillInIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
-            Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-
-            dataString += "/" + id;
-            // If we have an event id - start the event info activity
-            fillInIntent.setClass(context, EventInfoActivity.class);
-        } else {
-            // If we do not have an event id - start AllInOne
-            fillInIntent.setClass(context, AllInOneActivity.class);
-        }
-        Uri data = Uri.parse(dataString);
-        fillInIntent.setData(data);
-        fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start);
-        fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end);
-        fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay);
-
-        return fillInIntent;
-    }
-}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt b/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt
new file mode 100644
index 0000000..b3539f2
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetProvider.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY
+import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
+import android.provider.CalendarContract.EXTRA_EVENT_END_TIME
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.CalendarContract
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.widget.RemoteViews
+import com.android.calendar.AllInOneActivity
+import com.android.calendar.EventInfoActivity
+import com.android.calendar.R
+import com.android.calendar.Utils
+
+/**
+ * Simple widget to show next upcoming calendar event.
+ */
+class CalendarAppWidgetProvider : AppWidgetProvider() {
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    override fun onReceive(context: Context?, intent: Intent?) {
+        // Handle calendar-specific updates ourselves because they might be
+        // coming in without extras, which AppWidgetProvider then blocks.
+        val action: String? = intent?.getAction()
+        if (LOGD) Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString())
+        if (Utils.getWidgetUpdateAction(context as Context).equals(action)) {
+            val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
+            performUpdate(
+                context as Context, appWidgetManager,
+                appWidgetManager.getAppWidgetIds(getComponentName(context)),
+                null /* no eventIds */
+            )
+        } else if (action != null && (action.equals(Intent.ACTION_PROVIDER_CHANGED) ||
+            action.equals(Intent.ACTION_TIME_CHANGED) ||
+            action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
+            action.equals(Intent.ACTION_DATE_CHANGED) ||
+            action.equals(Utils.getWidgetScheduledUpdateAction(context as Context)))
+        ) {
+            val service = Intent(context, CalendarAppWidgetService::class.java)
+            context?.startService(service)
+        } else {
+            super.onReceive(context, intent)
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    override fun onDisabled(context: Context) {
+        // Unsubscribe from all AlarmManager updates
+        val am: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+        val pendingUpdate: PendingIntent = getUpdateIntent(context)
+        am.cancel(pendingUpdate)
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    override fun onUpdate(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetIds: IntArray
+    ) {
+        performUpdate(context, appWidgetManager,
+            appWidgetIds, null /* no eventIds */)
+    }
+
+    /**
+     * Process and push out an update for the given appWidgetIds. This call
+     * actually fires an intent to start [CalendarAppWidgetService] as a
+     * background service which handles the actual update, to prevent ANR'ing
+     * during database queries.
+     *
+     * @param context Context to use when starting [CalendarAppWidgetService].
+     * @param appWidgetIds List of specific appWidgetIds to update, or null for
+     * all.
+     * @param changedEventIds Specific events known to be changed. If present,
+     * we use it to decide if an update is necessary.
+     */
+    private fun performUpdate(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetIds: IntArray,
+        changedEventIds: LongArray?
+    ) {
+        // Launch over to service so it can perform update
+        for (appWidgetId in appWidgetIds) {
+            if (LOGD) Log.d(TAG, "Building widget update...")
+            val updateIntent = Intent(context, CalendarAppWidgetService::class.java)
+            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+            if (changedEventIds != null) {
+                updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds)
+            }
+            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)))
+            val views = RemoteViews(context.getPackageName(), R.layout.appwidget)
+            // Calendar header
+            val time = Time(Utils.getTimeZone(context, null))
+            time.setToNow()
+            val millis: Long = time.toMillis(true)
+            val dayOfWeek: String = DateUtils.getDayOfWeekString(
+                time.weekDay + 1,
+                DateUtils.LENGTH_MEDIUM
+            )
+            val date: String? = Utils.formatDateRange(
+                context, millis, millis,
+                DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
+                or DateUtils.FORMAT_NO_YEAR
+            )
+            views.setTextViewText(R.id.day_of_week, dayOfWeek)
+            views.setTextViewText(R.id.date, date)
+            // Attach to list of events
+            views.setRemoteAdapter(appWidgetId, R.id.events_list, updateIntent)
+            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list)
+
+            // Launch calendar app when the user taps on the header
+            val launchCalendarIntent = Intent(Intent.ACTION_VIEW)
+            launchCalendarIntent.setClass(context, AllInOneActivity::class.java)
+            launchCalendarIntent
+                .setData(Uri.parse("content://com.android.calendar/time/$millis"))
+            val launchCalendarPendingIntent: PendingIntent = PendingIntent.getActivity(
+                context, 0 /* no requestCode */, launchCalendarIntent, 0 /* no flags */
+            )
+            views.setOnClickPendingIntent(R.id.header, launchCalendarPendingIntent)
+
+            // Each list item will call setOnClickExtra() to let the list know
+            // which item
+            // is selected by a user.
+            val updateEventIntent: PendingIntent = getLaunchPendingIntentTemplate(context)
+            views.setPendingIntentTemplate(R.id.events_list, updateEventIntent)
+            appWidgetManager.updateAppWidget(appWidgetId, views)
+        }
+    }
+
+    companion object {
+        const val TAG = "CalendarAppWidgetProvider"
+        const val LOGD = false
+
+        // TODO Move these to Calendar.java
+        const val EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS"
+
+        /**
+         * Build [ComponentName] describing this specific
+         * [AppWidgetProvider]
+         */
+        @JvmStatic fun getComponentName(context: Context?): ComponentName {
+            return ComponentName(context as Context, CalendarAppWidgetProvider::class.java)
+        }
+
+        /**
+         * Build the [PendingIntent] used to trigger an update of all calendar
+         * widgets. Uses [Utils.getWidgetScheduledUpdateAction] to
+         * directly target all widgets instead of using
+         * [AppWidgetManager.EXTRA_APPWIDGET_IDS].
+         *
+         * @param context Context to use when building broadcast.
+         */
+        @JvmStatic fun getUpdateIntent(context: Context?): PendingIntent {
+            val intent = Intent(Utils.getWidgetScheduledUpdateAction(context as Context))
+            intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE)
+            return PendingIntent.getBroadcast(
+                context, 0 /* no requestCode */, intent,
+                0 /* no flags */
+            )
+        }
+
+        /**
+         * Build a [PendingIntent] to launch the Calendar app. This should be used
+         * in combination with [RemoteViews.setPendingIntentTemplate].
+         */
+        @JvmStatic fun getLaunchPendingIntentTemplate(context: Context?): PendingIntent {
+            val launchIntent = Intent()
+            launchIntent.setAction(Intent.ACTION_VIEW)
+            launchIntent.setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                Intent.FLAG_ACTIVITY_TASK_ON_HOME
+            )
+            launchIntent.setClass(context as Context, AllInOneActivity::class.java)
+            return PendingIntent.getActivity(
+                context, 0 /* no requestCode */, launchIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT
+            )
+        }
+
+        /**
+         * Build an [Intent] available as FillInIntent to launch the Calendar app.
+         * This should be used in combination with
+         * [RemoteViews.setOnClickFillInIntent].
+         * If the go to time is 0, then calendar will be launched without a starting time.
+         *
+         * @param goToTime time that calendar should take the user to, or 0 to
+         * indicate no specific start time.
+         */
+        @JvmStatic fun getLaunchFillInIntent(
+            context: Context?,
+            id: Long,
+            start: Long,
+            end: Long,
+            allDay: Boolean
+        ): Intent {
+            val fillInIntent = Intent()
+            var dataString = "content://com.android.calendar/events"
+            if (id != 0L) {
+                fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true)
+                fillInIntent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                    Intent.FLAG_ACTIVITY_TASK_ON_HOME
+                )
+                dataString += "/$id"
+                // If we have an event id - start the event info activity
+                fillInIntent.setClass(context as Context, EventInfoActivity::class.java)
+            } else {
+                // If we do not have an event id - start AllInOne
+                fillInIntent.setClass(context as Context, AllInOneActivity::class.java)
+            }
+            val data: Uri = Uri.parse(dataString)
+            fillInIntent.setData(data)
+            fillInIntent.putExtra(EXTRA_EVENT_BEGIN_TIME, start)
+            fillInIntent.putExtra(EXTRA_EVENT_END_TIME, end)
+            fillInIntent.putExtra(EXTRA_EVENT_ALL_DAY, allDay)
+            return fillInIntent
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.java b/src/com/android/calendar/widget/CalendarAppWidgetService.java
deleted file mode 100644
index ec702c7..0000000
--- a/src/com/android/calendar/widget/CalendarAppWidgetService.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.widget;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Instances;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import com.android.calendar.R;
-import com.android.calendar.Utils;
-import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
-import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
-import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicInteger;
-
-
-public class CalendarAppWidgetService extends RemoteViewsService {
-    private static final String TAG = "CalendarWidget";
-
-    static final int EVENT_MIN_COUNT = 20;
-    static final int EVENT_MAX_COUNT = 100;
-    // Minimum delay between queries on the database for widget updates in ms
-    static final int WIDGET_UPDATE_THROTTLE = 500;
-
-    private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
-            + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
-            + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
-
-    private static final String EVENT_SELECTION = Calendars.VISIBLE + "=1";
-    private static final String EVENT_SELECTION_HIDE_DECLINED = Calendars.VISIBLE + "=1 AND "
-            + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
-
-    static final String[] EVENT_PROJECTION = new String[] {
-        Instances.ALL_DAY,
-        Instances.BEGIN,
-        Instances.END,
-        Instances.TITLE,
-        Instances.EVENT_LOCATION,
-        Instances.EVENT_ID,
-        Instances.START_DAY,
-        Instances.END_DAY,
-        Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR.
-        Instances.SELF_ATTENDEE_STATUS,
-    };
-
-    static final int INDEX_ALL_DAY = 0;
-    static final int INDEX_BEGIN = 1;
-    static final int INDEX_END = 2;
-    static final int INDEX_TITLE = 3;
-    static final int INDEX_EVENT_LOCATION = 4;
-    static final int INDEX_EVENT_ID = 5;
-    static final int INDEX_START_DAY = 6;
-    static final int INDEX_END_DAY = 7;
-    static final int INDEX_COLOR = 8;
-    static final int INDEX_SELF_ATTENDEE_STATUS = 9;
-
-    static {
-        if (!Utils.isJellybeanOrLater()) {
-            EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR;
-        }
-    }
-    static final int MAX_DAYS = 7;
-
-    private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
-
-    /**
-     * Update interval used when no next-update calculated, or bad trigger time in past.
-     * Unit: milliseconds.
-     */
-    private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
-
-    @Override
-    public RemoteViewsFactory onGetViewFactory(Intent intent) {
-        return new CalendarFactory(getApplicationContext(), intent);
-    }
-
-    public static class CalendarFactory extends BroadcastReceiver implements
-            RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> {
-        private static final boolean LOGD = false;
-
-        // Suppress unnecessary logging about update time. Need to be static as this object is
-        // re-instanciated frequently.
-        // TODO: It seems loadData() is called via onCreate() four times, which should mean
-        // unnecessary CalendarFactory object is created and dropped. It is not efficient.
-        private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS;
-
-        private Context mContext;
-        private Resources mResources;
-        private static CalendarAppWidgetModel mModel;
-        private static Object mLock = new Object();
-        private static volatile int mSerialNum = 0;
-        private int mLastSerialNum = -1;
-        private CursorLoader mLoader;
-        private final Handler mHandler = new Handler();
-        private static final AtomicInteger currentVersion = new AtomicInteger(0);
-        private final ExecutorService executor = Executors.newSingleThreadExecutor();
-        private int mAppWidgetId;
-        private int mDeclinedColor;
-        private int mStandardColor;
-        private int mAllDayColor;
-
-        private final Runnable mTimezoneChanged = new Runnable() {
-            @Override
-            public void run() {
-                if (mLoader != null) {
-                    mLoader.forceLoad();
-                }
-            }
-        };
-
-        private Runnable createUpdateLoaderRunnable(final String selection,
-                final PendingResult result, final int version) {
-            return new Runnable() {
-                @Override
-                public void run() {
-                    // If there is a newer load request in the queue, skip loading.
-                    if (mLoader != null && version >= currentVersion.get()) {
-                        Uri uri = createLoaderUri();
-                        mLoader.setUri(uri);
-                        mLoader.setSelection(selection);
-                        synchronized (mLock) {
-                            mLastSerialNum = ++mSerialNum;
-                        }
-                        mLoader.forceLoad();
-                    }
-                    result.finish();
-                }
-            };
-        }
-
-        protected CalendarFactory(Context context, Intent intent) {
-            mContext = context;
-            mResources = context.getResources();
-            mAppWidgetId = intent.getIntExtra(
-                    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
-
-            mDeclinedColor = mResources.getColor(R.color.appwidget_item_declined_color);
-            mStandardColor = mResources.getColor(R.color.appwidget_item_standard_color);
-            mAllDayColor = mResources.getColor(R.color.appwidget_item_allday_color);
-        }
-
-        public CalendarFactory() {
-            // This is being created as part of onReceive
-
-        }
-
-        @Override
-        public void onCreate() {
-            String selection = queryForSelection();
-            initLoader(selection);
-        }
-
-        @Override
-        public void onDataSetChanged() {
-        }
-
-        @Override
-        public void onDestroy() {
-            if (mLoader != null) {
-                mLoader.reset();
-            }
-        }
-
-        @Override
-        public RemoteViews getLoadingView() {
-            RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                    R.layout.appwidget_loading);
-            return views;
-        }
-
-        @Override
-        public RemoteViews getViewAt(int position) {
-            // we use getCount here so that it doesn't return null when empty
-            if (position < 0 || position >= getCount()) {
-                return null;
-            }
-
-            if (mModel == null) {
-                RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                        R.layout.appwidget_loading);
-                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
-                        0, 0, false);
-                views.setOnClickFillInIntent(R.id.appwidget_loading, intent);
-                return views;
-
-            }
-            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
-                RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                        R.layout.appwidget_no_events);
-                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
-                        0, 0, false);
-                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
-                return views;
-            }
-
-            RowInfo rowInfo = mModel.mRowInfos.get(position);
-            if (rowInfo.mType == RowInfo.TYPE_DAY) {
-                RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                        R.layout.appwidget_day);
-                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
-                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
-                return views;
-            } else {
-                RemoteViews views;
-                final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
-                if (eventInfo.allDay) {
-                    views = new RemoteViews(mContext.getPackageName(),
-                            R.layout.widget_all_day_item);
-                } else {
-                    views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
-                }
-                int displayColor = Utils.getDisplayColorFromColor(eventInfo.color);
-
-                final long now = System.currentTimeMillis();
-                if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) {
-                    views.setInt(R.id.widget_row, "setBackgroundResource",
-                            R.drawable.agenda_item_bg_secondary);
-                } else {
-                    views.setInt(R.id.widget_row, "setBackgroundResource",
-                            R.drawable.agenda_item_bg_primary);
-                }
-
-                if (!eventInfo.allDay) {
-                    updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when);
-                    updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where);
-                }
-                updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title);
-
-                views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE);
-
-                int selfAttendeeStatus = eventInfo.selfAttendeeStatus;
-                if (eventInfo.allDay) {
-                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
-                        views.setInt(R.id.agenda_item_color, "setImageResource",
-                                R.drawable.widget_chip_not_responded_bg);
-                        views.setInt(R.id.title, "setTextColor", displayColor);
-                    } else {
-                        views.setInt(R.id.agenda_item_color, "setImageResource",
-                                R.drawable.widget_chip_responded_bg);
-                        views.setInt(R.id.title, "setTextColor", mAllDayColor);
-                    }
-                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
-                        // 40% opacity
-                        views.setInt(R.id.agenda_item_color, "setColorFilter",
-                                Utils.getDeclinedColorFromColor(displayColor));
-                    } else {
-                        views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
-                    }
-                } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
-                    views.setInt(R.id.title, "setTextColor", mDeclinedColor);
-                    views.setInt(R.id.when, "setTextColor", mDeclinedColor);
-                    views.setInt(R.id.where, "setTextColor", mDeclinedColor);
-                    views.setInt(R.id.agenda_item_color, "setImageResource",
-                            R.drawable.widget_chip_responded_bg);
-                    // 40% opacity
-                    views.setInt(R.id.agenda_item_color, "setColorFilter",
-                            Utils.getDeclinedColorFromColor(displayColor));
-                } else {
-                    views.setInt(R.id.title, "setTextColor", mStandardColor);
-                    views.setInt(R.id.when, "setTextColor", mStandardColor);
-                    views.setInt(R.id.where, "setTextColor", mStandardColor);
-                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
-                        views.setInt(R.id.agenda_item_color, "setImageResource",
-                                R.drawable.widget_chip_not_responded_bg);
-                    } else {
-                        views.setInt(R.id.agenda_item_color, "setImageResource",
-                                R.drawable.widget_chip_responded_bg);
-                    }
-                    views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
-                }
-
-                long start = eventInfo.start;
-                long end = eventInfo.end;
-                // An element in ListView.
-                if (eventInfo.allDay) {
-                    String tz = Utils.getTimeZone(mContext, null);
-                    Time recycle = new Time();
-                    start = Utils.convertAlldayLocalToUTC(recycle, start, tz);
-                    end = Utils.convertAlldayLocalToUTC(recycle, end, tz);
-                }
-                final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent(
-                        mContext, eventInfo.id, start, end, eventInfo.allDay);
-                views.setOnClickFillInIntent(R.id.widget_row, fillInIntent);
-                return views;
-            }
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return 5;
-        }
-
-        @Override
-        public int getCount() {
-            // if there are no events, we still return 1 to represent the "no
-            // events" view
-            if (mModel == null) {
-                return 1;
-            }
-            return Math.max(1, mModel.mRowInfos.size());
-        }
-
-        @Override
-        public long getItemId(int position) {
-            if (mModel == null ||  mModel.mRowInfos.isEmpty() || position >= getCount()) {
-                return 0;
-            }
-            RowInfo rowInfo = mModel.mRowInfos.get(position);
-            if (rowInfo.mType == RowInfo.TYPE_DAY) {
-                return rowInfo.mIndex;
-            }
-            EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
-            long prime = 31;
-            long result = 1;
-            result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32));
-            result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32));
-            return result;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return true;
-        }
-
-        /**
-         * Query across all calendars for upcoming event instances from now
-         * until some time in the future. Widen the time range that we query by
-         * one day on each end so that we can catch all-day events. All-day
-         * events are stored starting at midnight in UTC but should be included
-         * in the list of events starting at midnight local time. This may fetch
-         * more events than we actually want, so we filter them out later.
-         *
-         * @param selection The selection string for the loader to filter the query with.
-         */
-        public void initLoader(String selection) {
-            if (LOGD)
-                Log.d(TAG, "Querying for widget events...");
-
-            // Search for events from now until some time in the future
-            Uri uri = createLoaderUri();
-            mLoader = new CursorLoader(mContext, uri, EVENT_PROJECTION, selection, null,
-                    EVENT_SORT_ORDER);
-            mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE);
-            synchronized (mLock) {
-                mLastSerialNum = ++mSerialNum;
-            }
-            mLoader.registerListener(mAppWidgetId, this);
-            mLoader.startLoading();
-
-        }
-
-        /**
-         * This gets the selection string for the loader.  This ends up doing a query in the
-         * shared preferences.
-         */
-        private String queryForSelection() {
-            return Utils.getHideDeclinedEvents(mContext) ? EVENT_SELECTION_HIDE_DECLINED
-                    : EVENT_SELECTION;
-        }
-
-        /**
-         * @return The uri for the loader
-         */
-        private Uri createLoaderUri() {
-            long now = System.currentTimeMillis();
-            // Add a day on either side to catch all-day events
-            long begin = now - DateUtils.DAY_IN_MILLIS;
-            long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS;
-
-            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end);
-            return uri;
-        }
-
-        /* @VisibleForTesting */
-        protected static CalendarAppWidgetModel buildAppWidgetModel(
-                Context context, Cursor cursor, String timeZone) {
-            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone);
-            model.buildFromCursor(cursor, timeZone);
-            return model;
-        }
-
-        /**
-         * Calculates and returns the next time we should push widget updates.
-         */
-        private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) {
-            // Make sure an update happens at midnight or earlier
-            long minUpdateTime = getNextMidnightTimeMillis(timeZone);
-            for (EventInfo event : model.mEventInfos) {
-                final long start;
-                final long end;
-                start = event.start;
-                end = event.end;
-
-                // We want to update widget when we enter/exit time range of an event.
-                if (now < start) {
-                    minUpdateTime = Math.min(minUpdateTime, start);
-                } else if (now < end) {
-                    minUpdateTime = Math.min(minUpdateTime, end);
-                }
-            }
-            return minUpdateTime;
-        }
-
-        private static long getNextMidnightTimeMillis(String timezone) {
-            Time time = new Time();
-            time.setToNow();
-            time.monthDay++;
-            time.hour = 0;
-            time.minute = 0;
-            time.second = 0;
-            long midnightDeviceTz = time.normalize(true);
-
-            time.timezone = timezone;
-            time.setToNow();
-            time.monthDay++;
-            time.hour = 0;
-            time.minute = 0;
-            time.second = 0;
-            long midnightHomeTz = time.normalize(true);
-
-            return Math.min(midnightDeviceTz, midnightHomeTz);
-        }
-
-        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
-            views.setViewVisibility(id, visibility);
-            if (visibility == View.VISIBLE) {
-                views.setTextViewText(id, string);
-            }
-        }
-
-        /*
-         * (non-Javadoc)
-         * @see
-         * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
-         * .content.Loader, java.lang.Object)
-         */
-        @Override
-        public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
-            if (cursor == null) {
-                return;
-            }
-            // If a newer update has happened since we started clean up and
-            // return
-            synchronized (mLock) {
-                if (cursor.isClosed()) {
-                    Log.wtf(TAG, "Got a closed cursor from onLoadComplete");
-                    return;
-                }
-
-                if (mLastSerialNum != mSerialNum) {
-                    return;
-                }
-
-                final long now = System.currentTimeMillis();
-                String tz = Utils.getTimeZone(mContext, mTimezoneChanged);
-
-                // Copy it to a local static cursor.
-                MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
-                try {
-                    mModel = buildAppWidgetModel(mContext, matrixCursor, tz);
-                } finally {
-                    if (matrixCursor != null) {
-                        matrixCursor.close();
-                    }
-
-                    if (cursor != null) {
-                        cursor.close();
-                    }
-                }
-
-                // Schedule an alarm to wake ourselves up for the next update.
-                // We also cancel
-                // all existing wake-ups because PendingIntents don't match
-                // against extras.
-                long triggerTime = calculateUpdateTime(mModel, now, tz);
-
-                // If no next-update calculated, or bad trigger time in past,
-                // schedule
-                // update about six hours from now.
-                if (triggerTime < now) {
-                    Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now));
-                    triggerTime = now + UPDATE_TIME_NO_EVENTS;
-                }
-
-                final AlarmManager alertManager = (AlarmManager) mContext
-                        .getSystemService(Context.ALARM_SERVICE);
-                final PendingIntent pendingUpdate = CalendarAppWidgetProvider
-                        .getUpdateIntent(mContext);
-
-                alertManager.cancel(pendingUpdate);
-                alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate);
-                Time time = new Time(Utils.getTimeZone(mContext, null));
-                time.setToNow();
-
-                if (time.normalize(true) != sLastUpdateTime) {
-                    Time time2 = new Time(Utils.getTimeZone(mContext, null));
-                    time2.set(sLastUpdateTime);
-                    time2.normalize(true);
-                    if (time.year != time2.year || time.yearDay != time2.yearDay) {
-                        final Intent updateIntent = new Intent(
-                                Utils.getWidgetUpdateAction(mContext));
-                        mContext.sendBroadcast(updateIntent);
-                    }
-
-                    sLastUpdateTime = time.toMillis(true);
-                }
-
-                AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
-                if (widgetManager == null) {
-                    return;
-                }
-                if (mAppWidgetId == -1) {
-                    int[] ids = widgetManager.getAppWidgetIds(CalendarAppWidgetProvider
-                            .getComponentName(mContext));
-
-                    widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list);
-                } else {
-                    widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list);
-                }
-            }
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (LOGD)
-                Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString());
-            mContext = context;
-
-            // We cannot do any queries from the UI thread, so push the 'selection' query
-            // to a background thread.  However the implementation of the latter query
-            // (cursor loading) uses CursorLoader which must be initiated from the UI thread,
-            // so there is some convoluted handshaking here.
-            //
-            // Note that as currently implemented, this must run in a single threaded executor
-            // or else the loads may be run out of order.
-            //
-            // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously
-            // in the background thread.  All the handshaking going on here between the UI and
-            // background thread with using goAsync, mHandler, and CursorLoader is confusing.
-            final PendingResult result = goAsync();
-            executor.submit(new Runnable() {
-                @Override
-                public void run() {
-                    // We always complete queryForSelection() even if the load task ends up being
-                    // canceled because of a more recent one.  Optimizing this to allow
-                    // canceling would require keeping track of all the PendingResults
-                    // (from goAsync) to abort them.  Defer this until it becomes a problem.
-                    final String selection = queryForSelection();
-
-                    if (mLoader == null) {
-                        mAppWidgetId = -1;
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                initLoader(selection);
-                                result.finish();
-                            }
-                        });
-                    } else {
-                        mHandler.post(createUpdateLoaderRunnable(selection, result,
-                                currentVersion.incrementAndGet()));
-                    }
-                }
-            });
-        }
-    }
-
-    /**
-     * Format given time for debugging output.
-     *
-     * @param unixTime Target time to report.
-     * @param now Current system time from {@link System#currentTimeMillis()}
-     *            for calculating time difference.
-     */
-    static String formatDebugTime(long unixTime, long now) {
-        Time time = new Time();
-        time.set(unixTime);
-
-        long delta = unixTime - now;
-        if (delta > DateUtils.MINUTE_IN_MILLIS) {
-            delta /= DateUtils.MINUTE_IN_MILLIS;
-            return String.format("[%d] %s (%+d mins)", unixTime,
-                    time.format("%H:%M:%S"), delta);
-        } else {
-            delta /= DateUtils.SECOND_IN_MILLIS;
-            return String.format("[%d] %s (%+d secs)", unixTime,
-                    time.format("%H:%M:%S"), delta);
-        }
-    }
-}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.kt b/src/com/android/calendar/widget/CalendarAppWidgetService.kt
new file mode 100644
index 0000000..114fdf1
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetService.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.CursorLoader
+import android.content.Intent
+import android.content.Loader
+import android.content.res.Resources
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.os.Handler
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Instances
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.View
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import com.android.calendar.R
+import com.android.calendar.Utils
+import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo
+import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo
+import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.atomic.AtomicInteger
+
+class CalendarAppWidgetService : RemoteViewsService() {
+    companion object {
+        private const val TAG = "CalendarWidget"
+        const val EVENT_MIN_COUNT = 20
+        const val EVENT_MAX_COUNT = 100
+
+        // Minimum delay between queries on the database for widget updates in ms
+        const val WIDGET_UPDATE_THROTTLE = 500
+        private val EVENT_SORT_ORDER: String = (Instances.START_DAY.toString() + " ASC, " +
+            Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " +
+            Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT)
+        private val EVENT_SELECTION: String = Calendars.VISIBLE.toString() + "=1"
+        private val EVENT_SELECTION_HIDE_DECLINED: String =
+            (Calendars.VISIBLE.toString() + "=1 AND " +
+                Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED)
+        @JvmField
+        val EVENT_PROJECTION = arrayOf<String>(
+            Instances.ALL_DAY,
+            Instances.BEGIN,
+            Instances.END,
+            Instances.TITLE,
+            Instances.EVENT_LOCATION,
+            Instances.EVENT_ID,
+            Instances.START_DAY,
+            Instances.END_DAY,
+            Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR.
+            Instances.SELF_ATTENDEE_STATUS
+        )
+        const val INDEX_ALL_DAY = 0
+        const val INDEX_BEGIN = 1
+        const val INDEX_END = 2
+        const val INDEX_TITLE = 3
+        const val INDEX_EVENT_LOCATION = 4
+        const val INDEX_EVENT_ID = 5
+        const val INDEX_START_DAY = 6
+        const val INDEX_END_DAY = 7
+        const val INDEX_COLOR = 8
+        const val INDEX_SELF_ATTENDEE_STATUS = 9
+        const val MAX_DAYS = 7
+        private val SEARCH_DURATION: Long = MAX_DAYS * DateUtils.DAY_IN_MILLIS
+
+        /**
+         * Update interval used when no next-update calculated, or bad trigger time in past.
+         * Unit: milliseconds.
+         */
+        private val UPDATE_TIME_NO_EVENTS: Long = DateUtils.HOUR_IN_MILLIS * 6
+
+        /**
+         * Format given time for debugging output.
+         *
+         * @param unixTime Target time to report.
+         * @param now Current system time from [System.currentTimeMillis]
+         * for calculating time difference.
+         */
+        fun formatDebugTime(unixTime: Long, now: Long): String {
+            val time = Time()
+            time.set(unixTime)
+            var delta = unixTime - now
+            return if (delta > DateUtils.MINUTE_IN_MILLIS) {
+                delta /= DateUtils.MINUTE_IN_MILLIS
+                String.format(
+                    "[%d] %s (%+d mins)", unixTime,
+                    time.format("%H:%M:%S"), delta
+                )
+            } else {
+                delta /= DateUtils.SECOND_IN_MILLIS
+                String.format(
+                    "[%d] %s (%+d secs)", unixTime,
+                    time.format("%H:%M:%S"), delta
+                )
+            }
+        }
+
+        init {
+            if (!Utils.isJellybeanOrLater()) {
+                EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR
+            }
+        }
+    }
+
+    @Override
+    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
+        return CalendarFactory(getApplicationContext(), intent)
+    }
+
+    class CalendarFactory : BroadcastReceiver, RemoteViewsService.RemoteViewsFactory,
+                            Loader.OnLoadCompleteListener<Cursor?> {
+        private var mContext: Context? = null
+        private var mResources: Resources? = null
+        private var mLastSerialNum = -1
+        private var mLoader: CursorLoader? = null
+        private val mHandler: Handler = Handler()
+        private val executor: ExecutorService = Executors.newSingleThreadExecutor()
+        private var mAppWidgetId = 0
+        private var mDeclinedColor = 0
+        private var mStandardColor = 0
+        private var mAllDayColor = 0
+        private val mTimezoneChanged: Runnable = object : Runnable {
+            @Override
+            override fun run() {
+                if (mLoader != null) {
+                    mLoader?.forceLoad()
+                }
+            }
+        }
+
+        private fun createUpdateLoaderRunnable(
+            selection: String,
+            result: PendingResult,
+            version: Int
+        ): Runnable {
+            return object : Runnable {
+                @Override
+                override fun run() {
+                    // If there is a newer load request in the queue, skip loading.
+                    if (mLoader != null && version >= currentVersion.get()) {
+                        val uri: Uri = createLoaderUri()
+                        mLoader?.setUri(uri)
+                        mLoader?.setSelection(selection)
+                        synchronized(mLock) { mLastSerialNum = ++mSerialNum }
+                        mLoader?.forceLoad()
+                    }
+                    result.finish()
+                }
+            }
+        }
+
+        constructor(context: Context, intent: Intent) {
+            mContext = context
+            mResources = context.getResources()
+            mAppWidgetId = intent.getIntExtra(
+                AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID
+            )
+            mDeclinedColor = mResources?.getColor(R.color.appwidget_item_declined_color) as Int
+            mStandardColor = mResources?.getColor(R.color.appwidget_item_standard_color) as Int
+            mAllDayColor = mResources?.getColor(R.color.appwidget_item_allday_color) as Int
+        }
+
+        constructor() {
+            // This is being created as part of onReceive
+        }
+
+        @Override
+        override fun onCreate() {
+            val selection = queryForSelection()
+            initLoader(selection)
+        }
+
+        @Override
+        override fun onDataSetChanged() {
+        }
+
+        @Override
+        override fun onDestroy() {
+            if (mLoader != null) {
+                mLoader?.reset()
+            }
+        }
+
+        @Override
+        override fun getLoadingView(): RemoteViews {
+            val views = RemoteViews(mContext?.getPackageName(), R.layout.appwidget_loading)
+            return views
+        }
+
+        @Override
+        override fun getViewAt(position: Int): RemoteViews? {
+            // we use getCount here so that it doesn't return null when empty
+            if (position < 0 || position >= getCount()) {
+                return null
+            }
+            if (mModel == null) {
+                val views = RemoteViews(
+                    mContext?.getPackageName(),
+                    R.layout.appwidget_loading
+                )
+                val intent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+                    mContext,
+                    0,
+                    0,
+                    0,
+                    false
+                )
+                views.setOnClickFillInIntent(R.id.appwidget_loading, intent)
+                return views
+            }
+            if (mModel!!.mEventInfos!!.isEmpty() || mModel!!.mRowInfos!!.isEmpty()) {
+                val views = RemoteViews(
+                    mContext?.getPackageName(),
+                    R.layout.appwidget_no_events
+                )
+                val intent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+                    mContext,
+                    0,
+                    0,
+                    0,
+                    false
+                )
+                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent)
+                return views
+            }
+            val rowInfo: RowInfo? = mModel?.mRowInfos?.get(position)
+            return if (rowInfo!!.mType == RowInfo!!.TYPE_DAY) {
+                val views = RemoteViews(
+                    mContext?.getPackageName(),
+                    R.layout.appwidget_day
+                )
+                val dayInfo: DayInfo? = mModel?.mDayInfos?.get(rowInfo!!.mIndex)
+                updateTextView(views, R.id.date, View.VISIBLE, dayInfo!!.mDayLabel)
+                views
+            } else {
+                val views: RemoteViews?
+                val eventInfo: EventInfo? = mModel?.mEventInfos?.get(rowInfo.mIndex)
+                if (eventInfo!!.allDay) {
+                    views = RemoteViews(
+                        mContext?.getPackageName(),
+                        R.layout.widget_all_day_item
+                    )
+                } else {
+                    views = RemoteViews(mContext?.getPackageName(), R.layout.widget_item)
+                }
+                val displayColor: Int = Utils.getDisplayColorFromColor(eventInfo!!.color)
+                val now: Long = System.currentTimeMillis()
+                if (!eventInfo!!.allDay && eventInfo!!.start <= now && now <= eventInfo!!.end) {
+                    views?.setInt(
+                        R.id.widget_row, "setBackgroundResource",
+                        R.drawable.agenda_item_bg_secondary
+                    )
+                } else {
+                    views?.setInt(
+                        R.id.widget_row, "setBackgroundResource",
+                        R.drawable.agenda_item_bg_primary
+                    )
+                }
+                if (!eventInfo?.allDay) {
+                    updateTextView(views, R.id.`when`, eventInfo?.visibWhen
+                        as Int, eventInfo?.`when`)
+                    updateTextView(views, R.id.where, eventInfo?.visibWhere
+                        as Int, eventInfo?.where)
+                }
+                updateTextView(views, R.id.title, eventInfo?.visibTitle as Int, eventInfo?.title)
+                views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE)
+                val selfAttendeeStatus: Int = eventInfo?.selfAttendeeStatus as Int
+                if (eventInfo!!.allDay) {
+                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
+                        views?.setInt(
+                            R.id.agenda_item_color, "setImageResource",
+                            R.drawable.widget_chip_not_responded_bg
+                        )
+                        views?.setInt(R.id.title, "setTextColor", displayColor)
+                    } else {
+                        views?.setInt(
+                            R.id.agenda_item_color, "setImageResource",
+                            R.drawable.widget_chip_responded_bg
+                        )
+                        views?.setInt(R.id.title, "setTextColor", mAllDayColor)
+                    }
+                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
+                        // 40% opacity
+                        views?.setInt(
+                            R.id.agenda_item_color, "setColorFilter",
+                            Utils.getDeclinedColorFromColor(displayColor)
+                        )
+                    } else {
+                        views?.setInt(R.id.agenda_item_color, "setColorFilter", displayColor)
+                    }
+                } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
+                    views?.setInt(R.id.title, "setTextColor", mDeclinedColor)
+                    views?.setInt(R.id.`when`, "setTextColor", mDeclinedColor)
+                    views?.setInt(R.id.where, "setTextColor", mDeclinedColor)
+                    views?.setInt(
+                        R.id.agenda_item_color, "setImageResource",
+                        R.drawable.widget_chip_responded_bg
+                    )
+                    // 40% opacity
+                    views?.setInt(
+                        R.id.agenda_item_color, "setColorFilter",
+                        Utils.getDeclinedColorFromColor(displayColor)
+                    )
+                } else {
+                    views?.setInt(R.id.title, "setTextColor", mStandardColor)
+                    views?.setInt(R.id.`when`, "setTextColor", mStandardColor)
+                    views?.setInt(R.id.where, "setTextColor", mStandardColor)
+                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
+                        views?.setInt(
+                            R.id.agenda_item_color, "setImageResource",
+                            R.drawable.widget_chip_not_responded_bg
+                        )
+                    } else {
+                        views?.setInt(
+                            R.id.agenda_item_color, "setImageResource",
+                            R.drawable.widget_chip_responded_bg
+                        )
+                    }
+                    views?.setInt(R.id.agenda_item_color, "setColorFilter", displayColor)
+                }
+                var start: Long = eventInfo?.start as Long
+                var end: Long = eventInfo?.end as Long
+                // An element in ListView.
+                if (eventInfo!!.allDay) {
+                    val tz: String? = Utils.getTimeZone(mContext, null)
+                    val recycle = Time()
+                    start = Utils.convertAlldayLocalToUTC(recycle, start, tz as String)
+                    end = Utils.convertAlldayLocalToUTC(recycle, end, tz as String)
+                }
+                val fillInIntent: Intent = CalendarAppWidgetProvider.getLaunchFillInIntent(
+                    mContext, eventInfo?.id, start, end, eventInfo?.allDay
+                )
+                views.setOnClickFillInIntent(R.id.widget_row, fillInIntent)
+                views
+            }
+        }
+
+        @Override
+        override fun getViewTypeCount(): Int {
+            return 5
+        }
+
+        @Override
+        override fun getCount(): Int {
+            // if there are no events, we still return 1 to represent the "no
+            // events" view
+            if (mModel == null) {
+                return 1
+            }
+            return Math.max(1, mModel?.mRowInfos?.size as Int)
+        }
+
+        @Override
+        override fun getItemId(position: Int): Long {
+            if (mModel == null || mModel?.mRowInfos?.isEmpty() as Boolean ||
+                position >= getCount()) {
+                return 0
+            }
+            val rowInfo: RowInfo = mModel?.mRowInfos?.get(position) as RowInfo
+            if (rowInfo.mType == RowInfo.TYPE_DAY) {
+                return rowInfo.mIndex.toLong()
+            }
+            val eventInfo: EventInfo = mModel?.mEventInfos?.get(rowInfo.mIndex) as EventInfo
+            val prime: Long = 31
+            var result: Long = 1
+            result = prime * result + (eventInfo.id xor (eventInfo.id ushr 32)) as Int
+            result = prime * result + (eventInfo.start xor (eventInfo.start ushr 32)) as Int
+            return result
+        }
+
+        @Override
+        override fun hasStableIds(): Boolean {
+            return true
+        }
+
+        /**
+         * Query across all calendars for upcoming event instances from now
+         * until some time in the future. Widen the time range that we query by
+         * one day on each end so that we can catch all-day events. All-day
+         * events are stored starting at midnight in UTC but should be included
+         * in the list of events starting at midnight local time. This may fetch
+         * more events than we actually want, so we filter them out later.
+         *
+         * @param selection The selection string for the loader to filter the query with.
+         */
+        fun initLoader(selection: String?) {
+            if (LOGD) Log.d(TAG, "Querying for widget events...")
+
+            // Search for events from now until some time in the future
+            val uri: Uri = createLoaderUri()
+            mLoader = CursorLoader(
+                mContext, uri, EVENT_PROJECTION, selection, null,
+                EVENT_SORT_ORDER
+            )
+            mLoader?.setUpdateThrottle(WIDGET_UPDATE_THROTTLE.toLong())
+            synchronized(mLock) { mLastSerialNum = ++mSerialNum }
+            mLoader?.registerListener(mAppWidgetId, this)
+            mLoader?.startLoading()
+        }
+
+        /**
+         * This gets the selection string for the loader.  This ends up doing a query in the
+         * shared preferences.
+         */
+        private fun queryForSelection(): String {
+            return if (Utils.getHideDeclinedEvents(mContext)) EVENT_SELECTION_HIDE_DECLINED
+            else EVENT_SELECTION
+        }
+
+        /**
+         * @return The uri for the loader
+         */
+        private fun createLoaderUri(): Uri {
+            val now: Long = System.currentTimeMillis()
+            // Add a day on either side to catch all-day events
+            val begin: Long = now - DateUtils.DAY_IN_MILLIS
+            val end: Long =
+                now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS
+            return Uri.withAppendedPath(
+                Instances.CONTENT_URI,
+                begin.toString() + "/" + end
+            )
+        }
+
+        /**
+         * Calculates and returns the next time we should push widget updates.
+         */
+        private fun calculateUpdateTime(
+            model: CalendarAppWidgetModel,
+            now: Long,
+            timeZone: String
+        ): Long {
+            // Make sure an update happens at midnight or earlier
+            var minUpdateTime = getNextMidnightTimeMillis(timeZone)
+            for (event in model.mEventInfos) {
+                val start: Long
+                val end: Long
+                start = event.start
+                end = event.end
+
+                // We want to update widget when we enter/exit time range of an event.
+                if (now < start) {
+                    minUpdateTime = Math.min(minUpdateTime, start)
+                } else if (now < end) {
+                    minUpdateTime = Math.min(minUpdateTime, end)
+                }
+            }
+            return minUpdateTime
+        }
+
+        /*
+         * (non-Javadoc)
+         * @see
+         * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
+         * .content.Loader, java.lang.Object)
+         */
+        @Override
+        override fun onLoadComplete(loader: Loader<Cursor?>?, cursor: Cursor?) {
+            if (cursor == null) {
+                return
+            }
+            // If a newer update has happened since we started clean up and
+            // return
+            synchronized(mLock) {
+                if (cursor.isClosed()) {
+                    Log.wtf(TAG, "Got a closed cursor from onLoadComplete")
+                    return
+                }
+                if (mLastSerialNum != mSerialNum) {
+                    return
+                }
+                val now: Long = System.currentTimeMillis()
+                val tz: String? = Utils.getTimeZone(mContext, mTimezoneChanged)
+
+                // Copy it to a local static cursor.
+                val matrixCursor: MatrixCursor? = Utils.matrixCursorFromCursor(cursor)
+                try {
+                    mModel = buildAppWidgetModel(mContext, matrixCursor, tz)
+                } finally {
+                    if (matrixCursor != null) {
+                        matrixCursor?.close()
+                    }
+                    if (cursor != null) {
+                        cursor?.close()
+                    }
+                }
+
+                // Schedule an alarm to wake ourselves up for the next update.
+                // We also cancel
+                // all existing wake-ups because PendingIntents don't match
+                // against extras.
+                var triggerTime = calculateUpdateTime(mModel as CalendarAppWidgetModel,
+                    now, tz as String)
+
+                // If no next-update calculated, or bad trigger time in past,
+                // schedule
+                // update about six hours from now.
+                if (triggerTime < now) {
+                    Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now))
+                    triggerTime = now + UPDATE_TIME_NO_EVENTS
+                }
+                val alertManager: AlarmManager = mContext
+                    ?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+                val pendingUpdate: PendingIntent = CalendarAppWidgetProvider
+                    .getUpdateIntent(mContext)
+                alertManager.cancel(pendingUpdate)
+                alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate)
+                val time = Time(Utils.getTimeZone(mContext, null))
+                time.setToNow()
+                if (time.normalize(true) !== sLastUpdateTime) {
+                    val time2 = Time(Utils.getTimeZone(mContext, null))
+                    time2.set(sLastUpdateTime)
+                    time2.normalize(true)
+                    if (time.year !== time2.year || time.yearDay !== time2.yearDay) {
+                        val updateIntent = Intent(
+                            Utils.getWidgetUpdateAction(mContext as Context)
+                        )
+                        mContext?.sendBroadcast(updateIntent)
+                    }
+                    sLastUpdateTime = time.toMillis(true)
+                }
+                val widgetManager: AppWidgetManager = AppWidgetManager.getInstance(mContext)
+                if (widgetManager == null) {
+                    return
+                }
+                if (mAppWidgetId == -1) {
+                    val ids: IntArray = widgetManager.getAppWidgetIds(
+                        CalendarAppWidgetProvider
+                            .getComponentName(mContext)
+                    )
+                    widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list)
+                } else {
+                    widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list)
+                }
+            }
+        }
+
+        @Override
+        override fun onReceive(context: Context?, intent: Intent) {
+            if (LOGD) Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString())
+            mContext = context
+
+            // We cannot do any queries from the UI thread, so push the 'selection' query
+            // to a background thread.  However the implementation of the latter query
+            // (cursor loading) uses CursorLoader which must be initiated from the UI thread,
+            // so there is some convoluted handshaking here.
+            //
+            // Note that as currently implemented, this must run in a single threaded executor
+            // or else the loads may be run out of order.
+            //
+            // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously
+            // in the background thread.  All the handshaking going on here between the UI and
+            // background thread with using goAsync, mHandler, and CursorLoader is confusing.
+            val result: PendingResult = goAsync()
+            executor.submit(object : Runnable {
+                @Override
+                override fun run() {
+                    // We always complete queryForSelection() even if the load task ends up being
+                    // canceled because of a more recent one.  Optimizing this to allow
+                    // canceling would require keeping track of all the PendingResults
+                    // (from goAsync) to abort them.  Defer this until it becomes a problem.
+                    val selection = queryForSelection()
+                    if (mLoader == null) {
+                        mAppWidgetId = -1
+                        mHandler.post(object : Runnable {
+                            @Override
+                            override fun run() {
+                                initLoader(selection)
+                                result.finish()
+                            }
+                        })
+                    } else {
+                        mHandler.post(
+                            createUpdateLoaderRunnable(
+                                selection, result,
+                                currentVersion.incrementAndGet()
+                            )
+                        )
+                    }
+                }
+            })
+        }
+
+        internal companion object {
+            private const val LOGD = false
+
+            // Suppress unnecessary logging about update time. Need to be static as this object is
+            // re-instantiated frequently.
+            // TODO: It seems loadData() is called via onCreate() four times, which should mean
+            // unnecessary CalendarFactory object is created and dropped. It is not efficient.
+            private var sLastUpdateTime = UPDATE_TIME_NO_EVENTS
+            private var mModel: CalendarAppWidgetModel? = null
+            private val mLock: Object = Object()
+
+            @Volatile
+            private var mSerialNum = 0
+            private val currentVersion: AtomicInteger = AtomicInteger(0)
+
+            /* @VisibleForTesting */
+            @JvmStatic protected fun buildAppWidgetModel(
+                context: Context?,
+                cursor: Cursor?,
+                timeZone: String?
+            ): CalendarAppWidgetModel {
+                val model = CalendarAppWidgetModel(context as Context, timeZone)
+                model.buildFromCursor(cursor as Cursor, timeZone)
+                return model
+            }
+
+            @JvmStatic private fun getNextMidnightTimeMillis(timezone: String): Long {
+                val time = Time()
+                time.setToNow()
+                time.monthDay++
+                time.hour = 0
+                time.minute = 0
+                time.second = 0
+                val midnightDeviceTz: Long = time.normalize(true)
+                time.timezone = timezone
+                time.setToNow()
+                time.monthDay++
+                time.hour = 0
+                time.minute = 0
+                time.second = 0
+                val midnightHomeTz: Long = time.normalize(true)
+                return Math.min(midnightDeviceTz, midnightHomeTz)
+            }
+
+            @JvmStatic fun updateTextView(
+                views: RemoteViews,
+                id: Int,
+                visibility: Int,
+                string: String?
+            ) {
+                views.setViewVisibility(id, visibility)
+                if (visibility == View.VISIBLE) {
+                    views.setTextViewText(id, string)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file