Merge changes from topic "MonthByWeek/SimpleWeeks" am: 68ba903864

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

Change-Id: I86aa3e4835b6c9c3853fb92ca864eae5f1429c0d
diff --git a/Android.bp b/Android.bp
index ae0a360..41d2527 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,7 +27,9 @@
     "src/**/calendar/alerts/InitAlarmsService.java",
     "src/**/calendar/alerts/NotificationMgr.java",
     "src/**/calendar/alerts/QuickResponseActivity.java",
+    "src/**/calendar/month/MonthByWeekAdapter.java",
     "src/**/calendar/month/MonthByWeekFragment.java",
+    "src/**/calendar/month/SimpleWeeksAdapter.java",
     "src/**/calendar/widget/CalendarAppWidgetModel.java",
     "src/**/calendar/widget/CalendarAppWidgetProvider.java",
     "src/**/calendar/widget/CalendarAppWidgetService.java",
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.kt b/src/com/android/calendar/month/MonthByWeekFragment.kt
index 7c1eec9..9fe9fe4 100644
--- a/src/com/android/calendar/month/MonthByWeekFragment.kt
+++ b/src/com/android/calendar/month/MonthByWeekFragment.kt
@@ -53,8 +53,8 @@
 import java.util.HashMap
 
 class MonthByWeekFragment @JvmOverloads constructor(
-        initialTime: Long = System.currentTimeMillis(),
-        protected var mIsMiniMonth: Boolean = true
+    initialTime: Long = System.currentTimeMillis(),
+    protected var mIsMiniMonth: Boolean = true
 ) : SimpleDayPickerFragment(initialTime), CalendarController.EventHandler,
         LoaderManager.LoaderCallbacks<Cursor?>, OnScrollListener, OnTouchListener {
     protected var mMinimumTwoMonthFlingVelocity = 0f
@@ -221,18 +221,18 @@
     protected override fun setUpAdapter() {
         mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
         mShowWeekNumber = Utils.getShowWeekNumber(mContext)
-        val weekParams: HashMap<String, Int> = 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(
+        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)
+            mAdapter = MonthByWeekAdapter(getActivity(), weekParams) as SimpleWeeksAdapter?
             mAdapter?.registerDataSetObserver(mObserver)
         } else {
             mAdapter?.updateParams(weekParams)
@@ -242,9 +242,9 @@
 
     @Override
     override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
     ): View {
         val v: View
         v = if (mIsMiniMonth) {
@@ -338,7 +338,8 @@
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(
                         TAG,
-                        "Found " + data?.getCount()?.toString() + " cursor entries for uri " + mEventUri
+                        "Found " + data?.getCount()?.toString() + " cursor entries for uri " +
+                            mEventUri
                 )
             }
             val cLoader: CursorLoader = loader as CursorLoader
@@ -357,7 +358,7 @@
             )
             (mAdapter as MonthByWeekAdapter).setEvents(
                     mFirstLoadedJulianDay,
-                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events
+                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events as ArrayList<Event>?
             )
         }
     }
@@ -396,7 +397,8 @@
             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)
+                    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.
diff --git a/src/com/android/calendar/month/SimpleDayPickerFragment.kt b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
index d3c43e6..01fcbac 100644
--- a/src/com/android/calendar/month/SimpleDayPickerFragment.kt
+++ b/src/com/android/calendar/month/SimpleDayPickerFragment.kt
@@ -132,9 +132,9 @@
     @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)
+            val day: Time? = mAdapter!!.getSelectedDay()
+            if (day!!.year !== mSelectedDay!!.year || day!!.yearDay !== mSelectedDay.yearDay) {
+                goTo(day!!.toMillis(true), true, true, false)
             }
         }
     }
@@ -178,11 +178,11 @@
      * this method to provide a custom adapter.
      */
     protected open fun setUpAdapter() {
-        val weekParams: HashMap<String, Int> = 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,
+        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)
@@ -316,8 +316,11 @@
     }
 
     @Override
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
-                              savedInstanceState: Bundle?): View {
+    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
@@ -438,7 +441,11 @@
      */
     @Override
     override fun onScroll(
-            view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
+        view: AbsListView,
+        firstVisibleItem: Int,
+        visibleItemCount: Int,
+        totalItemCount: Int
+    ) {
         val child = view.getChildAt(0) as? SimpleWeekView
         if (child == null) {
             return
@@ -569,8 +576,8 @@
                         "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) {
+            if (mNewState == OnScrollListener.SCROLL_STATE_IDLE &&
+                    mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
                 mPreviousScrollState = mNewState
                 mAdapter?.updateFocusMonth(mCurrentMonthDisplayed)
             } else {
diff --git a/src/com/android/calendar/month/SimpleWeekView.kt b/src/com/android/calendar/month/SimpleWeekView.kt
index 7c1ef44..a71cff8 100644
--- a/src/com/android/calendar/month/SimpleWeekView.kt
+++ b/src/com/android/calendar/month/SimpleWeekView.kt
@@ -72,7 +72,7 @@
 
     // The position of this week, equivalent to weeks since the week of Jan 1st,
     // 1970
-    @JvmField protected var mWeek = -1
+    @JvmField var mWeek = -1
 
     // Quick reference to the width of this view, matches parent
     @JvmField protected var mWidth = 0
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