Merge changes from topic "EventInfoFragment" am: bf857d93f1 am: 919c7ffa6a am: f3b41359b1

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

Change-Id: Ia566f5d424115dacbf7f332b1afef7413a47d413
diff --git a/Android.bp b/Android.bp
index fd318cc..ae0a360 100644
--- a/Android.bp
+++ b/Android.bp
@@ -37,6 +37,7 @@
     "src/**/calendar/DayView.java",
     "src/**/calendar/Event.java",
     "src/**/calendar/EventInfoActivity.java",
+    "src/**/calendar/EventInfoFragment.java",
     "src/**/calendar/StickyHeaderListView.java",
     "src/**/calendar/Utils.java",
 ]
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