1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.calendar
17 
18 import android.animation.Animator
19 import android.animation.AnimatorListenerAdapter
20 import android.animation.ObjectAnimator
21 import android.animation.ValueAnimator
22 import android.app.Service
23 import android.content.Context
24 import android.content.res.Resources
25 import android.content.res.TypedArray
26 import android.graphics.Canvas
27 import android.graphics.Paint
28 import android.graphics.Paint.Align
29 import android.graphics.Paint.Style
30 import android.graphics.Rect
31 import android.graphics.Typeface
32 import android.graphics.drawable.Drawable
33 import android.os.Handler
34 import android.provider.CalendarContract.Attendees
35 import android.provider.CalendarContract.Calendars
36 import android.text.Layout.Alignment
37 import android.text.SpannableStringBuilder
38 import android.text.StaticLayout
39 import android.text.TextPaint
40 import android.text.format.DateFormat
41 import android.text.format.DateUtils
42 import android.text.format.Time
43 import android.text.style.StyleSpan
44 import android.util.Log
45 import android.view.ContextMenu
46 import android.view.ContextMenu.ContextMenuInfo
47 import android.view.GestureDetector
48 import android.view.KeyEvent
49 import android.view.LayoutInflater
50 import android.view.MenuItem
51 import android.view.MotionEvent
52 import android.view.ScaleGestureDetector
53 import android.view.View
54 import android.view.ViewConfiguration
55 import android.view.ViewGroup
56 import android.view.ViewGroup.LayoutParams
57 import android.view.WindowManager
58 import android.view.accessibility.AccessibilityEvent
59 import android.view.accessibility.AccessibilityManager
60 import android.view.animation.AccelerateDecelerateInterpolator
61 import android.view.animation.Animation
62 import android.view.animation.Interpolator
63 import android.view.animation.TranslateAnimation
64 import android.widget.EdgeEffect
65 import android.widget.OverScroller
66 import android.widget.PopupWindow
67 import android.widget.ViewSwitcher
68 import com.android.calendar.CalendarController.EventType
69 import com.android.calendar.CalendarController.ViewType
70 import java.util.ArrayList
71 import java.util.Arrays
72 import java.util.Calendar
73 import java.util.Formatter
74 import java.util.Locale
75 import java.util.regex.Matcher
76 import java.util.regex.Pattern
77 
78 /**
79  * View for multi-day view. So far only 1 and 7 day have been tested.
80  */
81 class DayView(
82     context: Context?,
83     controller: CalendarController?,
84     viewSwitcher: ViewSwitcher?,
85     eventLoader: EventLoader?,
86     numDays: Int
87 ) : View(context), View.OnCreateContextMenuListener, ScaleGestureDetector.OnScaleGestureListener,
88     View.OnClickListener, View.OnLongClickListener {
89     private var mOnFlingCalled = false
90     private var mStartingScroll = false
91     protected var mPaused = true
92     private var mHandler: Handler? = null
93 
94     /**
95      * ID of the last event which was displayed with the toast popup.
96      *
97      * This is used to prevent popping up multiple quick views for the same event, especially
98      * during calendar syncs. This becomes valid when an event is selected, either by default
99      * on starting calendar or by scrolling to an event. It becomes invalid when the user
100      * explicitly scrolls to an empty time slot, changes views, or deletes the event.
101      */
102     private var mLastPopupEventID: Long
103     protected var mContext: Context? = null
104     private val mContinueScroll: ContinueScroll = ContinueScroll()
105 
106     // Make this visible within the package for more informative debugging
107     var mBaseDate: Time? = null
108     private var mCurrentTime: Time? = null
109     private val mUpdateCurrentTime: UpdateCurrentTime = UpdateCurrentTime()
110     private var mTodayJulianDay = 0
111     private val mBold: Typeface = Typeface.DEFAULT_BOLD
112     private var mFirstJulianDay = 0
113     private var mLoadedFirstJulianDay = -1
114     private var mLastJulianDay = 0
115     private var mMonthLength = 0
116     private var mFirstVisibleDate = 0
117     private var mFirstVisibleDayOfWeek = 0
118     private var mEarliestStartHour: IntArray? = null // indexed by the week day offset
119     private var mHasAllDayEvent: BooleanArray? = null // indexed by the week day offset
120     private var mEventCountTemplate: String? = null
121     private var mClickedEvent: Event? = null // The event the user clicked on
122     private var mSavedClickedEvent: Event? = null
123     private var mClickedYLocation = 0
124     private var mDownTouchTime: Long = 0
125     private var mEventsAlpha = 255
126     private var mEventsCrossFadeAnimation: ObjectAnimator? = null
127     private val mTZUpdater: Runnable = object : Runnable {
128         @Override
runnull129         override fun run() {
130             val tz: String? = Utils.getTimeZone(mContext, this)
131             mBaseDate!!.timezone = tz
132             mBaseDate?.normalize(true)
133             mCurrentTime?.switchTimezone(tz)
134             invalidate()
135         }
136     }
137 
138     // Sets the "clicked" color from the clicked event
139     private val mSetClick: Runnable = object : Runnable {
140         @Override
runnull141         override fun run() {
142             mClickedEvent = mSavedClickedEvent
143             mSavedClickedEvent = null
144             this@DayView.invalidate()
145         }
146     }
147 
148     // Clears the "clicked" color from the clicked event and launch the event
149     private val mClearClick: Runnable = object : Runnable {
150         @Override
runnull151         override fun run() {
152             if (mClickedEvent != null) {
153                 mController.sendEventRelatedEvent(
154                     this as Object?, EventType.VIEW_EVENT, mClickedEvent!!.id,
155                     mClickedEvent!!.startMillis, mClickedEvent!!.endMillis,
156                     this@DayView.getWidth() / 2, mClickedYLocation,
157                     selectedTimeInMillis
158                 )
159             }
160             mClickedEvent = null
161             this@DayView.invalidate()
162         }
163     }
164     private val mTodayAnimatorListener: TodayAnimatorListener = TodayAnimatorListener()
165 
166     internal inner class TodayAnimatorListener : AnimatorListenerAdapter() {
167         @Volatile
168         private var mAnimator: Animator? = null
169 
170         @Volatile
171         private var mFadingIn = false
172         @Override
onAnimationEndnull173         override fun onAnimationEnd(animation: Animator) {
174             synchronized(this) {
175                 if (mAnimator !== animation) {
176                     animation.removeAllListeners()
177                     animation.cancel()
178                     return
179                 }
180                 if (mFadingIn) {
181                     if (mTodayAnimator != null) {
182                         mTodayAnimator?.removeAllListeners()
183                         mTodayAnimator?.cancel()
184                     }
185                     mTodayAnimator = ObjectAnimator
186                         .ofInt(this@DayView, "animateTodayAlpha", 255, 0)
187                     mAnimator = mTodayAnimator
188                     mFadingIn = false
189                     mTodayAnimator?.addListener(this)
190                     mTodayAnimator?.setDuration(600)
191                     mTodayAnimator?.start()
192                 } else {
193                     mAnimateToday = false
194                     mAnimateTodayAlpha = 0
195                     mAnimator?.removeAllListeners()
196                     mAnimator = null
197                     mTodayAnimator = null
198                     invalidate()
199                 }
200             }
201         }
202 
setAnimatornull203         fun setAnimator(animation: Animator?) {
204             mAnimator = animation
205         }
206 
setFadingInnull207         fun setFadingIn(fadingIn: Boolean) {
208             mFadingIn = fadingIn
209         }
210     }
211 
212     var mAnimatorListener: AnimatorListenerAdapter = object : AnimatorListenerAdapter() {
213         @Override
onAnimationStartnull214         override fun onAnimationStart(animation: Animator) {
215             mScrolling = true
216         }
217 
218         @Override
onAnimationCancelnull219         override fun onAnimationCancel(animation: Animator) {
220             mScrolling = false
221         }
222 
223         @Override
onAnimationEndnull224         override fun onAnimationEnd(animation: Animator) {
225             mScrolling = false
226             resetSelectedHour()
227             invalidate()
228         }
229     }
230 
231     /**
232      * This variable helps to avoid unnecessarily reloading events by keeping
233      * track of the start millis parameter used for the most recent loading
234      * of events.  If the next reload matches this, then the events are not
235      * reloaded.  To force a reload, set this to zero (this is set to zero
236      * in the method clearCachedEvents()).
237      */
238     private var mLastReloadMillis: Long = 0
239     private var mEvents: ArrayList<Event> = ArrayList<Event>()
240     private var mAllDayEvents: ArrayList<Event>? = ArrayList<Event>()
241     private var mLayouts: Array<StaticLayout?>? = null
242     private var mAllDayLayouts: Array<StaticLayout?>? = null
243     private var mSelectionDay = 0 // Julian day
244     private var mSelectionHour = 0
245     var mSelectionAllday = false
246 
247     // Current selection info for accessibility
248     private var mSelectionDayForAccessibility = 0 // Julian day
249     private var mSelectionHourForAccessibility = 0
250     private var mSelectedEventForAccessibility: Event? = null
251 
252     // Last selection info for accessibility
253     private var mLastSelectionDayForAccessibility = 0
254     private var mLastSelectionHourForAccessibility = 0
255     private var mLastSelectedEventForAccessibility: Event? = null
256 
257     /** Width of a day or non-conflicting event  */
258     private var mCellWidth = 0
259 
260     // Pre-allocate these objects and re-use them
261     private val mRect: Rect = Rect()
262     private val mDestRect: Rect = Rect()
263     private val mSelectionRect: Rect = Rect()
264 
265     // This encloses the more allDay events icon
266     private val mExpandAllDayRect: Rect = Rect()
267 
268     // TODO Clean up paint usage
269     private val mPaint: Paint = Paint()
270     private val mEventTextPaint: Paint = Paint()
271     private val mSelectionPaint: Paint = Paint()
272     private var mLines: FloatArray = emptyArray<Float>().toFloatArray()
273     private var mFirstDayOfWeek = 0 // First day of the week
274     private var mPopup: PopupWindow? = null
275     private var mPopupView: View? = null
276     private val mDismissPopup: DismissPopup = DismissPopup()
277     private var mRemeasure = true
278     private val mEventLoader: EventLoader
279     protected val mEventGeometry: EventGeometry
280     private var mAnimationDistance = 0f
281     private var mViewStartX = 0
282     private var mViewStartY = 0
283     private var mMaxViewStartY = 0
284     private var mViewHeight = 0
285     private var mViewWidth = 0
286     private var mGridAreaHeight = -1
287     private var mScrollStartY = 0
288     private var mPreviousDirection = 0
289 
290     /**
291      * Vertical distance or span between the two touch points at the start of a
292      * scaling gesture
293      */
294     private var mStartingSpanY = 0f
295 
296     /** Height of 1 hour in pixels at the start of a scaling gesture  */
297     private var mCellHeightBeforeScaleGesture = 0
298 
299     /** The hour at the center two touch points  */
300     private var mGestureCenterHour = 0f
301     private var mRecalCenterHour = false
302 
303     /**
304      * Flag to decide whether to handle the up event. Cases where up events
305      * should be ignored are 1) right after a scale gesture and 2) finger was
306      * down before app launch
307      */
308     private var mHandleActionUp = true
309     private var mHoursTextHeight = 0
310 
311     /**
312      * The height of the area used for allday events
313      */
314     private var mAlldayHeight = 0
315 
316     /**
317      * The height of the allday event area used during animation
318      */
319     private var mAnimateDayHeight = 0
320 
321     /**
322      * The height of an individual allday event during animation
323      */
324     private var mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
325 
326     /**
327      * Max of all day events in a given day in this view.
328      */
329     private var mMaxAlldayEvents = 0
330 
331     /**
332      * A count of the number of allday events that were not drawn for each day
333      */
334     private var mSkippedAlldayEvents: IntArray? = null
335 
336     /**
337      * The number of allDay events at which point we start hiding allDay events.
338      */
339     private var mMaxUnexpandedAlldayEventCount = 4
340     protected var mNumDays = 7
341     private var mNumHours = 10
342 
343     /** Width of the time line (list of hours) to the left.  */
344     private var mHoursWidth = 0
345     private var mDateStrWidth = 0
346 
347     /** Top of the scrollable region i.e. below date labels and all day events  */
348     private var mFirstCell = 0
349 
350     /** First fully visible hour  */
351     private var mFirstHour = -1
352 
353     /** Distance between the mFirstCell and the top of first fully visible hour.  */
354     private var mFirstHourOffset = 0
355     private var mHourStrs: Array<String>? = null
356     private var mDayStrs: Array<String?>? = null
357     private var mDayStrs2Letter: Array<String?>? = null
358     private var mIs24HourFormat = false
359     private val mSelectedEvents: ArrayList<Event> = ArrayList<Event>()
360     private var mComputeSelectedEvents = false
361     private var mUpdateToast = false
362     private var mSelectedEvent: Event? = null
363     private var mPrevSelectedEvent: Event? = null
364     private val mPrevBox: Rect = Rect()
365     protected val mResources: Resources
366     protected val mCurrentTimeLine: Drawable
367     protected val mCurrentTimeAnimateLine: Drawable
368     protected val mTodayHeaderDrawable: Drawable
369     protected val mExpandAlldayDrawable: Drawable
370     protected val mCollapseAlldayDrawable: Drawable
371     protected var mAcceptedOrTentativeEventBoxDrawable: Drawable
372     private var mAmString: String? = null
373     private var mPmString: String? = null
374     var mScaleGestureDetector: ScaleGestureDetector
375     private var mTouchMode = TOUCH_MODE_INITIAL_STATE
376     private var mSelectionMode = SELECTION_HIDDEN
377     private var mScrolling = false
378 
379     // Pixels scrolled
380     private var mInitialScrollX = 0f
381     private var mInitialScrollY = 0f
382     private var mAnimateToday = false
383     private var mAnimateTodayAlpha = 0
384 
385     // Animates the height of the allday region
386     var mAlldayAnimator: ObjectAnimator? = null
387 
388     // Animates the height of events in the allday region
389     var mAlldayEventAnimator: ObjectAnimator? = null
390 
391     // Animates the transparency of the more events text
392     var mMoreAlldayEventsAnimator: ObjectAnimator? = null
393 
394     // Animates the current time marker when Today is pressed
395     var mTodayAnimator: ObjectAnimator? = null
396 
397     // whether or not an event is stopping because it was cancelled
398     private var mCancellingAnimations = false
399 
400     // tracks whether a touch originated in the allday area
401     private var mTouchStartedInAlldayArea = false
402     private val mController: CalendarController
403     private val mViewSwitcher: ViewSwitcher
404     private val mGestureDetector: GestureDetector
405     private val mScroller: OverScroller
406     private val mEdgeEffectTop: EdgeEffect
407     private val mEdgeEffectBottom: EdgeEffect
408     private var mCallEdgeEffectOnAbsorb = false
409     private val OVERFLING_DISTANCE: Int
410     private var mLastVelocity = 0f
411     private val mHScrollInterpolator: ScrollInterpolator
412     private var mAccessibilityMgr: AccessibilityManager? = null
413     private var mIsAccessibilityEnabled = false
414     private var mTouchExplorationEnabled = false
415     private val mNewEventHintString: String
416     @Override
onAttachedToWindownull417     protected override fun onAttachedToWindow() {
418         if (mHandler == null) {
419             mHandler = getHandler()
420             mHandler?.post(mUpdateCurrentTime)
421         }
422     }
423 
initnull424     private fun init(context: Context) {
425         setFocusable(true)
426 
427         // Allow focus in touch mode so that we can do keyboard shortcuts
428         // even after we've entered touch mode.
429         setFocusableInTouchMode(true)
430         setClickable(true)
431         setOnCreateContextMenuListener(this)
432         mFirstDayOfWeek = Utils.getFirstDayOfWeek(context)
433         mCurrentTime = Time(Utils.getTimeZone(context, mTZUpdater))
434         val currentTime: Long = System.currentTimeMillis()
435         mCurrentTime?.set(currentTime)
436         mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
437         mWeek_saturdayColor = mResources.getColor(R.color.week_saturday)
438         mWeek_sundayColor = mResources.getColor(R.color.week_sunday)
439         mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color)
440         mFutureBgColorRes = mResources.getColor(R.color.calendar_future_bg_color)
441         mBgColor = mResources.getColor(R.color.calendar_hour_background)
442         mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label)
443         mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected)
444         mCalendarGridLineInnerHorizontalColor = mResources
445             .getColor(R.color.calendar_grid_line_inner_horizontal_color)
446         mCalendarGridLineInnerVerticalColor = mResources
447             .getColor(R.color.calendar_grid_line_inner_vertical_color)
448         mCalendarHourLabelColor = mResources.getColor(R.color.calendar_hour_label)
449         mEventTextColor = mResources.getColor(R.color.calendar_event_text_color)
450         mMoreEventsTextColor = mResources.getColor(R.color.month_event_other_color)
451         mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE)
452         mEventTextPaint.setTextAlign(Paint.Align.LEFT)
453         mEventTextPaint.setAntiAlias(true)
454         val gridLineColor: Int = mResources.getColor(R.color.calendar_grid_line_highlight_color)
455         var p: Paint = mSelectionPaint
456         p.setColor(gridLineColor)
457         p.setStyle(Style.FILL)
458         p.setAntiAlias(false)
459         p = mPaint
460         p.setAntiAlias(true)
461 
462         // Allocate space for 2 weeks worth of weekday names so that we can
463         // easily start the week display at any week day.
464         mDayStrs = arrayOfNulls(14)
465 
466         // Also create an array of 2-letter abbreviations.
467         mDayStrs2Letter = arrayOfNulls(14)
468         for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
469             val index: Int = i - Calendar.SUNDAY
470             // e.g. Tue for Tuesday
471             mDayStrs!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM)
472                 .toUpperCase()
473             mDayStrs!![index + 7] = mDayStrs!![index]
474             // e.g. Tu for Tuesday
475             mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT)
476                 .toUpperCase()
477 
478             // If we don't have 2-letter day strings, fall back to 1-letter.
479             if (mDayStrs2Letter!![index]!!.equals(mDayStrs!![index])) {
480                 mDayStrs2Letter!![index] = DateUtils.getDayOfWeekString(i,
481                 DateUtils.LENGTH_SHORTEST)
482             }
483             mDayStrs2Letter!![index + 7] = mDayStrs2Letter!![index]
484         }
485 
486         // Figure out how much space we need for the 3-letter abbrev names
487         // in the worst case.
488         p.setTextSize(DATE_HEADER_FONT_SIZE)
489         p.setTypeface(mBold)
490         val dateStrs = arrayOf<String?>(" 28", " 30")
491         mDateStrWidth = computeMaxStringWidth(0, dateStrs, p)
492         p.setTextSize(DAY_HEADER_FONT_SIZE)
493         mDateStrWidth += computeMaxStringWidth(0, mDayStrs as Array<String?>, p)
494         p.setTextSize(HOURS_TEXT_SIZE)
495         p.setTypeface(null)
496         handleOnResume()
497         mAmString = DateUtils.getAMPMString(Calendar.AM).toUpperCase()
498         mPmString = DateUtils.getAMPMString(Calendar.PM).toUpperCase()
499         val ampm = arrayOf(mAmString, mPmString)
500         p.setTextSize(AMPM_TEXT_SIZE)
501         mHoursWidth = Math.max(
502             HOURS_MARGIN, computeMaxStringWidth(mHoursWidth, ampm, p) +
503                 HOURS_RIGHT_MARGIN
504         )
505         mHoursWidth = Math.max(MIN_HOURS_WIDTH, mHoursWidth)
506         val inflater: LayoutInflater
507         inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
508         mPopupView = inflater.inflate(R.layout.bubble_event, null)
509         mPopupView?.setLayoutParams(
510             LayoutParams(
511                 ViewGroup.LayoutParams.MATCH_PARENT,
512                 ViewGroup.LayoutParams.WRAP_CONTENT
513             )
514         )
515         mPopup = PopupWindow(context)
516         mPopup?.setContentView(mPopupView)
517         val dialogTheme: Resources.Theme = getResources().newTheme()
518         dialogTheme.applyStyle(android.R.style.Theme_Dialog, true)
519         val ta: TypedArray = dialogTheme.obtainStyledAttributes(
520             intArrayOf(
521                 android.R.attr.windowBackground
522             )
523         )
524         mPopup?.setBackgroundDrawable(ta.getDrawable(0))
525         ta.recycle()
526 
527         // Enable touching the popup window
528         mPopupView?.setOnClickListener(this)
529         // Catch long clicks for creating a new event
530         setOnLongClickListener(this)
531         mBaseDate = Time(Utils.getTimeZone(context, mTZUpdater))
532         val millis: Long = System.currentTimeMillis()
533         mBaseDate?.set(millis)
534         mEarliestStartHour = IntArray(mNumDays)
535         mHasAllDayEvent = BooleanArray(mNumDays)
536 
537         // mLines is the array of points used with Canvas.drawLines() in
538         // drawGridBackground() and drawAllDayEvents().  Its size depends
539         // on the max number of lines that can ever be drawn by any single
540         // drawLines() call in either of those methods.
541         val maxGridLines = (24 + 1 + // max horizontal lines we might draw
542             (mNumDays + 1)) // max vertical lines we might draw
543         mLines = FloatArray(maxGridLines * 4)
544     }
545 
546     /**
547      * This is called when the popup window is pressed.
548      */
onClicknull549     override fun onClick(v: View) {
550         if (v === mPopupView) {
551             // Pretend it was a trackball click because that will always
552             // jump to the "View event" screen.
553             switchViews(true /* trackball */)
554         }
555     }
556 
handleOnResumenull557     fun handleOnResume() {
558         initAccessibilityVariables()
559         if (Utils.getSharedPreference(mContext, OtherPreferences.KEY_OTHER_1, false)) {
560             mFutureBgColor = 0
561         } else {
562             mFutureBgColor = mFutureBgColorRes
563         }
564         mIs24HourFormat = DateFormat.is24HourFormat(mContext)
565         mHourStrs = if (mIs24HourFormat) CalendarData.s24Hours else CalendarData.s12HoursNoAmPm
566         mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
567         mLastSelectionDayForAccessibility = 0
568         mLastSelectionHourForAccessibility = 0
569         mLastSelectedEventForAccessibility = null
570         mSelectionMode = SELECTION_HIDDEN
571     }
572 
initAccessibilityVariablesnull573     private fun initAccessibilityVariables() {
574         mAccessibilityMgr = mContext
575             ?.getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager
576         mIsAccessibilityEnabled = mAccessibilityMgr != null && mAccessibilityMgr!!.isEnabled()
577         mTouchExplorationEnabled = isTouchExplorationEnabled
578     } /* ignore isDst */ // We ignore the "isDst" field because we want normalize() to figure
579     // out the correct DST value and not adjust the selected time based
580     // on the current setting of DST.
581     /**
582      * Returns the start of the selected time in milliseconds since the epoch.
583      *
584      * @return selected time in UTC milliseconds since the epoch.
585      */
586     val selectedTimeInMillis: Long
587         get() {
588             val time = Time(mBaseDate)
589             time.setJulianDay(mSelectionDay)
590             time.hour = mSelectionHour
591 
592             // We ignore the "isDst" field because we want normalize() to figure
593             // out the correct DST value and not adjust the selected time based
594             // on the current setting of DST.
595             return time.normalize(true /* ignore isDst */)
596         } /* ignore isDst */
597 
598     // We ignore the "isDst" field because we want normalize() to figure
599     // out the correct DST value and not adjust the selected time based
600     // on the current setting of DST.
601     val selectedTime: Time
602         get() {
603             val time = Time(mBaseDate)
604             time.setJulianDay(mSelectionDay)
605             time.hour = mSelectionHour
606 
607             // We ignore the "isDst" field because we want normalize() to figure
608             // out the correct DST value and not adjust the selected time based
609             // on the current setting of DST.
610             time.normalize(true /* ignore isDst */)
611             return time
612         } /* ignore isDst */
613 
614     // We ignore the "isDst" field because we want normalize() to figure
615     // out the correct DST value and not adjust the selected time based
616     // on the current setting of DST.
617     val selectedTimeForAccessibility: Time
618         get() {
619             val time = Time(mBaseDate)
620             time.setJulianDay(mSelectionDayForAccessibility)
621             time.hour = mSelectionHourForAccessibility
622 
623             // We ignore the "isDst" field because we want normalize() to figure
624             // out the correct DST value and not adjust the selected time based
625             // on the current setting of DST.
626             time.normalize(true /* ignore isDst */)
627             return time
628         }
629 
630     /**
631      * Returns the start of the selected time in minutes since midnight,
632      * local time.  The derived class must ensure that this is consistent
633      * with the return value from getSelectedTimeInMillis().
634      */
635     val selectedMinutesSinceMidnight: Int
636         get() = mSelectionHour * MINUTES_PER_HOUR
637     var firstVisibleHour: Int
638         get() = mFirstHour
639         set(firstHour) {
640             mFirstHour = firstHour
641             mFirstHourOffset = 0
642         }
643 
setSelectednull644     fun setSelected(time: Time?, ignoreTime: Boolean, animateToday: Boolean) {
645         mBaseDate?.set(time)
646         setSelectedHour(mBaseDate!!.hour)
647         setSelectedEvent(null)
648         mPrevSelectedEvent = null
649         val millis: Long = mBaseDate!!.toMillis(false /* use isDst */)
650         setSelectedDay(Time.getJulianDay(millis, mBaseDate!!.gmtoff))
651         mSelectedEvents.clear()
652         mComputeSelectedEvents = true
653         var gotoY: Int = Integer.MIN_VALUE
654         if (!ignoreTime && mGridAreaHeight != -1) {
655             var lastHour = 0
656             if (mBaseDate!!.hour < mFirstHour) {
657                 // Above visible region
658                 gotoY = mBaseDate!!.hour * (mCellHeight + HOUR_GAP)
659             } else {
660                 lastHour = ((mGridAreaHeight - mFirstHourOffset) / (mCellHeight + HOUR_GAP) +
661                     mFirstHour)
662                 if (mBaseDate!!.hour >= lastHour) {
663                     // Below visible region
664 
665                     // target hour + 1 (to give it room to see the event) -
666                     // grid height (to get the y of the top of the visible
667                     // region)
668                     gotoY = ((mBaseDate!!.hour + 1 + mBaseDate!!.minute / 60.0f) *
669                         (mCellHeight + HOUR_GAP) - mGridAreaHeight).toInt()
670                 }
671             }
672             if (DEBUG) {
673                 Log.e(
674                     TAG, "Go " + gotoY + " 1st " + mFirstHour + ":" + mFirstHourOffset + "CH " +
675                         (mCellHeight + HOUR_GAP) + " lh " + lastHour + " gh " + mGridAreaHeight +
676                         " ymax " + mMaxViewStartY
677                 )
678             }
679             if (gotoY > mMaxViewStartY) {
680                 gotoY = mMaxViewStartY
681             } else if (gotoY < 0 && gotoY != Integer.MIN_VALUE) {
682                 gotoY = 0
683             }
684         }
685         recalc()
686         mRemeasure = true
687         invalidate()
688         var delayAnimateToday = false
689         if (gotoY != Integer.MIN_VALUE) {
690             val scrollAnim: ValueAnimator =
691                 ObjectAnimator.ofInt(this, "viewStartY", mViewStartY, gotoY)
692             scrollAnim.setDuration(GOTO_SCROLL_DURATION.toLong())
693             scrollAnim.setInterpolator(AccelerateDecelerateInterpolator())
694             scrollAnim.addListener(mAnimatorListener)
695             scrollAnim.start()
696             delayAnimateToday = true
697         }
698         if (animateToday) {
699             synchronized(mTodayAnimatorListener) {
700                 if (mTodayAnimator != null) {
701                     mTodayAnimator?.removeAllListeners()
702                     mTodayAnimator?.cancel()
703                 }
704                 mTodayAnimator = ObjectAnimator.ofInt(
705                     this, "animateTodayAlpha",
706                     mAnimateTodayAlpha, 255
707                 )
708                 mAnimateToday = true
709                 mTodayAnimatorListener.setFadingIn(true)
710                 mTodayAnimatorListener.setAnimator(mTodayAnimator)
711                 mTodayAnimator?.addListener(mTodayAnimatorListener)
712                 mTodayAnimator?.setDuration(150)
713                 if (delayAnimateToday) {
714                     mTodayAnimator?.setStartDelay(GOTO_SCROLL_DURATION.toLong())
715                 }
716                 mTodayAnimator?.start()
717             }
718         }
719         sendAccessibilityEventAsNeeded(false)
720     }
721 
722     // Called from animation framework via reflection. Do not remove
setViewStartYnull723     fun setViewStartY(viewStartY: Int) {
724         var viewStartY = viewStartY
725         if (viewStartY > mMaxViewStartY) {
726             viewStartY = mMaxViewStartY
727         }
728         mViewStartY = viewStartY
729         computeFirstHour()
730         invalidate()
731     }
732 
setAnimateTodayAlphanull733     fun setAnimateTodayAlpha(todayAlpha: Int) {
734         mAnimateTodayAlpha = todayAlpha
735         invalidate()
736     } /* ignore isDst */
737 
getSelectedDaynull738     fun getSelectedDay(): Time {
739         val time = Time(mBaseDate)
740         time.setJulianDay(mSelectionDay)
741         time.hour = mSelectionHour
742 
743         // We ignore the "isDst" field because we want normalize() to figure
744         // out the correct DST value and not adjust the selected time based
745         // on the current setting of DST.
746         time.normalize(true /* ignore isDst */)
747         return time
748     }
749 
updateTitlenull750     fun updateTitle() {
751         val start = Time(mBaseDate)
752         start.normalize(true)
753         val end = Time(start)
754         end.monthDay += mNumDays - 1
755         // Move it forward one minute so the formatter doesn't lose a day
756         end.minute += 1
757         end.normalize(true)
758         var formatFlags: Long = DateUtils.FORMAT_SHOW_DATE.toLong() or
759             DateUtils.FORMAT_SHOW_YEAR.toLong()
760         if (mNumDays != 1) {
761             // Don't show day of the month if for multi-day view
762             formatFlags = formatFlags or DateUtils.FORMAT_NO_MONTH_DAY.toLong()
763 
764             // Abbreviate the month if showing multiple months
765             if (start.month !== end.month) {
766                 formatFlags = formatFlags or DateUtils.FORMAT_ABBREV_MONTH.toLong()
767             }
768         }
769         mController.sendEvent(
770             this as Object?, EventType.UPDATE_TITLE, start, end, null, -1, ViewType.CURRENT,
771             formatFlags, null, null
772         )
773     }
774 
775     /**
776      * return a negative number if "time" is comes before the visible time
777      * range, a positive number if "time" is after the visible time range, and 0
778      * if it is in the visible time range.
779      */
compareToVisibleTimeRangenull780     fun compareToVisibleTimeRange(time: Time): Int {
781         val savedHour: Int = mBaseDate!!.hour
782         val savedMinute: Int = mBaseDate!!.minute
783         val savedSec: Int = mBaseDate!!.second
784         mBaseDate!!.hour = 0
785         mBaseDate!!.minute = 0
786         mBaseDate!!.second = 0
787         if (DEBUG) {
788             Log.d(TAG, "Begin " + mBaseDate.toString())
789             Log.d(TAG, "Diff  " + time.toString())
790         }
791 
792         // Compare beginning of range
793         var diff: Int = Time.compare(time, mBaseDate)
794         if (diff > 0) {
795             // Compare end of range
796             mBaseDate!!.monthDay += mNumDays
797             mBaseDate?.normalize(true)
798             diff = Time.compare(time, mBaseDate)
799             if (DEBUG) Log.d(TAG, "End   " + mBaseDate.toString())
800             mBaseDate!!.monthDay -= mNumDays
801             mBaseDate?.normalize(true)
802             if (diff < 0) {
803                 // in visible time
804                 diff = 0
805             } else if (diff == 0) {
806                 // Midnight of following day
807                 diff = 1
808             }
809         }
810         if (DEBUG) Log.d(TAG, "Diff: $diff")
811         mBaseDate!!.hour = savedHour
812         mBaseDate!!.minute = savedMinute
813         mBaseDate!!.second = savedSec
814         return diff
815     }
816 
recalcnull817     private fun recalc() {
818         // Set the base date to the beginning of the week if we are displaying
819         // 7 days at a time.
820         if (mNumDays == 7) {
821             adjustToBeginningOfWeek(mBaseDate)
822         }
823         val start: Long = mBaseDate!!.toMillis(false /* use isDst */)
824         mFirstJulianDay = Time.getJulianDay(start, mBaseDate!!.gmtoff)
825         mLastJulianDay = mFirstJulianDay + mNumDays - 1
826         mMonthLength = mBaseDate!!.getActualMaximum(Time.MONTH_DAY)
827         mFirstVisibleDate = mBaseDate!!.monthDay
828         mFirstVisibleDayOfWeek = mBaseDate!!.weekDay
829     }
830 
adjustToBeginningOfWeeknull831     private fun adjustToBeginningOfWeek(time: Time?) {
832         val dayOfWeek: Int = time!!.weekDay
833         var diff = dayOfWeek - mFirstDayOfWeek
834         if (diff != 0) {
835             if (diff < 0) {
836                 diff += 7
837             }
838             time.monthDay -= diff
839             time.normalize(true /* ignore isDst */)
840         }
841     }
842 
843     @Override
onSizeChangednull844     protected override fun onSizeChanged(width: Int, height: Int, oldw: Int, oldh: Int) {
845         mViewWidth = width
846         mViewHeight = height
847         mEdgeEffectTop.setSize(mViewWidth, mViewHeight)
848         mEdgeEffectBottom.setSize(mViewWidth, mViewHeight)
849         val gridAreaWidth = width - mHoursWidth
850         mCellWidth = (gridAreaWidth - mNumDays * DAY_GAP) / mNumDays
851 
852         // This would be about 1 day worth in a 7 day view
853         mHorizontalSnapBackThreshold = width / 7
854         val p = Paint()
855         p.setTextSize(HOURS_TEXT_SIZE)
856         mHoursTextHeight = Math.abs(p.ascent()).toInt()
857         remeasure(width, height)
858     }
859 
860     /**
861      * Measures the space needed for various parts of the view after
862      * loading new events.  This can change if there are all-day events.
863      */
remeasurenull864     private fun remeasure(width: Int, height: Int) {
865         // Shrink to fit available space but make sure we can display at least two events
866         MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
867         MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.min(MAX_UNEXPANDED_ALLDAY_HEIGHT, height / 6)
868         MAX_UNEXPANDED_ALLDAY_HEIGHT = Math.max(
869             MAX_UNEXPANDED_ALLDAY_HEIGHT,
870             MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt() * 2
871         )
872         mMaxUnexpandedAlldayEventCount =
873             (MAX_UNEXPANDED_ALLDAY_HEIGHT / MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
874 
875         // First, clear the array of earliest start times, and the array
876         // indicating presence of an all-day event.
877         for (day in 0 until mNumDays) {
878             mEarliestStartHour!![day] = 25 // some big number
879             mHasAllDayEvent!![day] = false
880         }
881         val maxAllDayEvents = mMaxAlldayEvents
882 
883         // The min is where 24 hours cover the entire visible area
884         mMinCellHeight = Math.max((height - DAY_HEADER_HEIGHT) / 24, MIN_EVENT_HEIGHT.toInt())
885         if (mCellHeight < mMinCellHeight) {
886             mCellHeight = mMinCellHeight
887         }
888 
889         // Calculate mAllDayHeight
890         mFirstCell = DAY_HEADER_HEIGHT
891         var allDayHeight = 0
892         if (maxAllDayEvents > 0) {
893             val maxAllAllDayHeight = height - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
894             // If there is at most one all-day event per day, then use less
895             // space (but more than the space for a single event).
896             if (maxAllDayEvents == 1) {
897                 allDayHeight = SINGLE_ALLDAY_HEIGHT
898             } else if (maxAllDayEvents <= mMaxUnexpandedAlldayEventCount) {
899                 // Allow the all-day area to grow in height depending on the
900                 // number of all-day events we need to show, up to a limit.
901                 allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT
902                 if (allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
903                     allDayHeight = MAX_UNEXPANDED_ALLDAY_HEIGHT
904                 }
905             } else {
906                 // if we have more than the magic number, check if we're animating
907                 // and if not adjust the sizes appropriately
908                 if (mAnimateDayHeight != 0) {
909                     // Don't shrink the space past the final allDay space. The animation
910                     // continues to hide the last event so the more events text can
911                     // fade in.
912                     allDayHeight = Math.max(mAnimateDayHeight, MAX_UNEXPANDED_ALLDAY_HEIGHT)
913                 } else {
914                     // Try to fit all the events in
915                     allDayHeight = (maxAllDayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
916                     // But clip the area depending on which mode we're in
917                     if (!mShowAllAllDayEvents && allDayHeight > MAX_UNEXPANDED_ALLDAY_HEIGHT) {
918                         allDayHeight = (mMaxUnexpandedAlldayEventCount *
919                             MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
920                     } else if (allDayHeight > maxAllAllDayHeight) {
921                         allDayHeight = maxAllAllDayHeight
922                     }
923                 }
924             }
925             mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN
926         } else {
927             mSelectionAllday = false
928         }
929         mAlldayHeight = allDayHeight
930         mGridAreaHeight = height - mFirstCell
931 
932         // Set up the expand icon position
933         val allDayIconWidth: Int = mExpandAlldayDrawable.getIntrinsicWidth()
934         mExpandAllDayRect.left = Math.max(
935             (mHoursWidth - allDayIconWidth) / 2,
936             EVENT_ALL_DAY_TEXT_LEFT_MARGIN
937         )
938         mExpandAllDayRect.right = Math.min(
939             mExpandAllDayRect.left + allDayIconWidth, mHoursWidth -
940                 EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
941         )
942         mExpandAllDayRect.bottom = mFirstCell - EXPAND_ALL_DAY_BOTTOM_MARGIN
943         mExpandAllDayRect.top = (mExpandAllDayRect.bottom -
944             mExpandAlldayDrawable.getIntrinsicHeight())
945         mNumHours = mGridAreaHeight / (mCellHeight + HOUR_GAP)
946         mEventGeometry.setHourHeight(mCellHeight.toFloat())
947         val minimumDurationMillis =
948             (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f)).toLong()
949         Event.computePositions(mEvents, minimumDurationMillis)
950 
951         // Compute the top of our reachable view
952         mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
953         if (DEBUG) {
954             Log.e(TAG, "mViewStartY: $mViewStartY")
955             Log.e(TAG, "mMaxViewStartY: $mMaxViewStartY")
956         }
957         if (mViewStartY > mMaxViewStartY) {
958             mViewStartY = mMaxViewStartY
959             computeFirstHour()
960         }
961         if (mFirstHour == -1) {
962             initFirstHour()
963             mFirstHourOffset = 0
964         }
965 
966         // When we change the base date, the number of all-day events may
967         // change and that changes the cell height.  When we switch dates,
968         // we use the mFirstHourOffset from the previous view, but that may
969         // be too large for the new view if the cell height is smaller.
970         if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
971             mFirstHourOffset = mCellHeight + HOUR_GAP - 1
972         }
973         mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset
974         val eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP)
975         // When we get new events we don't want to dismiss the popup unless the event changes
976         if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent!!.id) {
977             mPopup?.dismiss()
978         }
979         mPopup?.setWidth(eventAreaWidth - 20)
980         mPopup?.setHeight(WindowManager.LayoutParams.WRAP_CONTENT)
981     }
982 
983     /**
984      * Initialize the state for another view.  The given view is one that has
985      * its own bitmap and will use an animation to replace the current view.
986      * The current view and new view are either both Week views or both Day
987      * views.  They differ in their base date.
988      *
989      * @param view the view to initialize.
990      */
initViewnull991     private fun initView(view: DayView) {
992         view.setSelectedHour(mSelectionHour)
993         view.mSelectedEvents.clear()
994         view.mComputeSelectedEvents = true
995         view.mFirstHour = mFirstHour
996         view.mFirstHourOffset = mFirstHourOffset
997         view.remeasure(getWidth(), getHeight())
998         view.initAllDayHeights()
999         view.setSelectedEvent(null)
1000         view.mPrevSelectedEvent = null
1001         view.mFirstDayOfWeek = mFirstDayOfWeek
1002         if (view.mEvents.size > 0) {
1003             view.mSelectionAllday = mSelectionAllday
1004         } else {
1005             view.mSelectionAllday = false
1006         }
1007 
1008         // Redraw the screen so that the selection box will be redrawn.  We may
1009         // have scrolled to a different part of the day in some other view
1010         // so the selection box in this view may no longer be visible.
1011         view.recalc()
1012     }
1013 
1014     /**
1015      * Switch to another view based on what was selected (an event or a free
1016      * slot) and how it was selected (by touch or by trackball).
1017      *
1018      * @param trackBallSelection true if the selection was made using the
1019      * trackball.
1020      */
switchViewsnull1021     private fun switchViews(trackBallSelection: Boolean) {
1022         val selectedEvent: Event? = mSelectedEvent
1023         mPopup?.dismiss()
1024         mLastPopupEventID = INVALID_EVENT_ID
1025         if (mNumDays > 1) {
1026             // This is the Week view.
1027             // With touch, we always switch to Day/Agenda View
1028             // With track ball, if we selected a free slot, then create an event.
1029             // If we selected a specific event, switch to EventInfo view.
1030             if (trackBallSelection) {
1031                 if (selectedEvent != null) {
1032                     if (mIsAccessibilityEnabled) {
1033                         mAccessibilityMgr?.interrupt()
1034                     }
1035                 }
1036             }
1037         }
1038     }
1039 
1040     @Override
onKeyUpnull1041     override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
1042         mScrolling = false
1043         return super.onKeyUp(keyCode, event)
1044     }
1045 
1046     @Override
onKeyDownnull1047     override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
1048         return super.onKeyDown(keyCode, event)
1049     }
1050 
1051     @Override
onHoverEventnull1052     override fun onHoverEvent(event: MotionEvent?): Boolean {
1053         return true
1054     }
1055 
1056     private val isTouchExplorationEnabled: Boolean
1057         private get() = mIsAccessibilityEnabled && mAccessibilityMgr!!.isTouchExplorationEnabled()
1058 
sendAccessibilityEventAsNeedednull1059     private fun sendAccessibilityEventAsNeeded(speakEvents: Boolean) {
1060         if (!mIsAccessibilityEnabled) {
1061             return
1062         }
1063         val dayChanged = mLastSelectionDayForAccessibility != mSelectionDayForAccessibility
1064         val hourChanged = mLastSelectionHourForAccessibility != mSelectionHourForAccessibility
1065         if (dayChanged || hourChanged || mLastSelectedEventForAccessibility !==
1066             mSelectedEventForAccessibility) {
1067             mLastSelectionDayForAccessibility = mSelectionDayForAccessibility
1068             mLastSelectionHourForAccessibility = mSelectionHourForAccessibility
1069             mLastSelectedEventForAccessibility = mSelectedEventForAccessibility
1070             val b = StringBuilder()
1071 
1072             // Announce only the changes i.e. day or hour or both
1073             if (dayChanged) {
1074                 b.append(selectedTimeForAccessibility.format("%A "))
1075             }
1076             if (hourChanged) {
1077                 b.append(selectedTimeForAccessibility.format(if (mIs24HourFormat) "%k" else "%l%p"))
1078             }
1079             if (dayChanged || hourChanged) {
1080                 b.append(PERIOD_SPACE)
1081             }
1082             if (speakEvents) {
1083                 if (mEventCountTemplate == null) {
1084                     mEventCountTemplate = mContext?.getString(R.string.template_announce_item_index)
1085                 }
1086 
1087                 // Read out the relevant event(s)
1088                 val numEvents: Int = mSelectedEvents.size
1089                 if (numEvents > 0) {
1090                     if (mSelectedEventForAccessibility == null) {
1091                         // Read out all the events
1092                         var i = 1
1093                         for (calEvent in mSelectedEvents) {
1094                             if (numEvents > 1) {
1095                                 // Read out x of numEvents if there are more than one event
1096                                 mStringBuilder.setLength(0)
1097                                 b.append(mFormatter.format(mEventCountTemplate, i++, numEvents))
1098                                 b.append(" ")
1099                             }
1100                             appendEventAccessibilityString(b, calEvent)
1101                         }
1102                     } else {
1103                         if (numEvents > 1) {
1104                             // Read out x of numEvents if there are more than one event
1105                             mStringBuilder.setLength(0)
1106                             b.append(
1107                                 mFormatter.format(
1108                                     mEventCountTemplate, mSelectedEvents
1109                                         .indexOf(mSelectedEventForAccessibility) + 1, numEvents
1110                                 )
1111                             )
1112                             b.append(" ")
1113                         }
1114                         appendEventAccessibilityString(b, mSelectedEventForAccessibility)
1115                     }
1116                 }
1117             }
1118             if (dayChanged || hourChanged || speakEvents) {
1119                 val event: AccessibilityEvent = AccessibilityEvent
1120                     .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED)
1121                 val msg: CharSequence = b.toString()
1122                 event.getText().add(msg)
1123                 event.setAddedCount(msg.length)
1124                 sendAccessibilityEventUnchecked(event)
1125             }
1126         }
1127     }
1128 
1129     /**
1130      * @param b
1131      * @param calEvent
1132      */
appendEventAccessibilityStringnull1133     private fun appendEventAccessibilityString(b: StringBuilder, calEvent: Event?) {
1134         b.append(calEvent!!.titleAndLocation)
1135         b.append(PERIOD_SPACE)
1136         val `when`: String?
1137         var flags: Int = DateUtils.FORMAT_SHOW_DATE
1138         if (calEvent.allDay) {
1139             flags = flags or (DateUtils.FORMAT_UTC or DateUtils.FORMAT_SHOW_WEEKDAY)
1140         } else {
1141             flags = flags or DateUtils.FORMAT_SHOW_TIME
1142             if (DateFormat.is24HourFormat(mContext)) {
1143                 flags = flags or DateUtils.FORMAT_24HOUR
1144             }
1145         }
1146         `when` = Utils.formatDateRange(mContext, calEvent.startMillis, calEvent.endMillis,
1147             flags)
1148         b.append(`when`)
1149         b.append(PERIOD_SPACE)
1150     }
1151 
1152     private inner class GotoBroadcaster(start: Time, end: Time) : Animation.AnimationListener {
1153         private val mCounter: Int
1154         private val mStart: Time
1155         private val mEnd: Time
1156         @Override
onAnimationEndnull1157         override fun onAnimationEnd(animation: Animation) {
1158             var view = mViewSwitcher.getCurrentView() as DayView
1159             view.mViewStartX = 0
1160             view = mViewSwitcher.getNextView() as DayView
1161             view.mViewStartX = 0
1162             if (mCounter == sCounter) {
1163                 mController.sendEvent(
1164                     this as Object?, EventType.GO_TO, mStart, mEnd, null, -1,
1165                     ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null
1166                 )
1167             }
1168         }
1169 
1170         @Override
onAnimationRepeatnull1171         override fun onAnimationRepeat(animation: Animation) {
1172         }
1173 
1174         @Override
onAnimationStartnull1175         override fun onAnimationStart(animation: Animation) {
1176         }
1177 
1178         init {
1179             mCounter = ++sCounter
1180             mStart = start
1181             mEnd = end
1182         }
1183     }
1184 
switchViewsnull1185     private fun switchViews(forward: Boolean, xOffSet: Float, width: Float, velocity: Float): View {
1186         mAnimationDistance = width - xOffSet
1187         if (DEBUG) {
1188             Log.d(TAG, "switchViews($forward) O:$xOffSet Dist:$mAnimationDistance")
1189         }
1190         var progress: Float = Math.abs(xOffSet) / width
1191         if (progress > 1.0f) {
1192             progress = 1.0f
1193         }
1194         val inFromXValue: Float
1195         val inToXValue: Float
1196         val outFromXValue: Float
1197         val outToXValue: Float
1198         if (forward) {
1199             inFromXValue = 1.0f - progress
1200             inToXValue = 0.0f
1201             outFromXValue = -progress
1202             outToXValue = -1.0f
1203         } else {
1204             inFromXValue = progress - 1.0f
1205             inToXValue = 0.0f
1206             outFromXValue = progress
1207             outToXValue = 1.0f
1208         }
1209         val start = Time(mBaseDate!!.timezone)
1210         start.set(mController.time as Long)
1211         if (forward) {
1212             start.monthDay += mNumDays
1213         } else {
1214             start.monthDay -= mNumDays
1215         }
1216         mController.time = start.normalize(true)
1217         var newSelected: Time? = start
1218         if (mNumDays == 7) {
1219             newSelected = Time(start)
1220             adjustToBeginningOfWeek(start)
1221         }
1222         val end = Time(start)
1223         end.monthDay += mNumDays - 1
1224 
1225         // We have to allocate these animation objects each time we switch views
1226         // because that is the only way to set the animation parameters.
1227         val inAnimation = TranslateAnimation(
1228             Animation.RELATIVE_TO_SELF, inFromXValue,
1229             Animation.RELATIVE_TO_SELF, inToXValue,
1230             Animation.ABSOLUTE, 0.0f,
1231             Animation.ABSOLUTE, 0.0f
1232         )
1233         val outAnimation = TranslateAnimation(
1234             Animation.RELATIVE_TO_SELF, outFromXValue,
1235             Animation.RELATIVE_TO_SELF, outToXValue,
1236             Animation.ABSOLUTE, 0.0f,
1237             Animation.ABSOLUTE, 0.0f
1238         )
1239         val duration = calculateDuration(width - Math.abs(xOffSet), width, velocity)
1240         inAnimation.setDuration(duration)
1241         inAnimation.setInterpolator(mHScrollInterpolator)
1242         outAnimation.setInterpolator(mHScrollInterpolator)
1243         outAnimation.setDuration(duration)
1244         outAnimation.setAnimationListener(GotoBroadcaster(start, end))
1245         mViewSwitcher.setInAnimation(inAnimation)
1246         mViewSwitcher.setOutAnimation(outAnimation)
1247         var view = mViewSwitcher.getCurrentView() as DayView
1248         view.cleanup()
1249         mViewSwitcher.showNext()
1250         view = mViewSwitcher.getCurrentView() as DayView
1251         view.setSelected(newSelected, true, false)
1252         view.requestFocus()
1253         view.reloadEvents()
1254         view.updateTitle()
1255         view.restartCurrentTimeUpdates()
1256         return view
1257     }
1258 
1259     // This is called after scrolling stops to move the selected hour
1260     // to the visible part of the screen.
resetSelectedHournull1261     private fun resetSelectedHour() {
1262         if (mSelectionHour < mFirstHour + 1) {
1263             setSelectedHour(mFirstHour + 1)
1264             setSelectedEvent(null)
1265             mSelectedEvents.clear()
1266             mComputeSelectedEvents = true
1267         } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
1268             setSelectedHour(mFirstHour + mNumHours - 3)
1269             setSelectedEvent(null)
1270             mSelectedEvents.clear()
1271             mComputeSelectedEvents = true
1272         }
1273     }
1274 
initFirstHournull1275     private fun initFirstHour() {
1276         mFirstHour = mSelectionHour - mNumHours / 5
1277         if (mFirstHour < 0) {
1278             mFirstHour = 0
1279         } else if (mFirstHour + mNumHours > 24) {
1280             mFirstHour = 24 - mNumHours
1281         }
1282     }
1283 
1284     /**
1285      * Recomputes the first full hour that is visible on screen after the
1286      * screen is scrolled.
1287      */
computeFirstHournull1288     private fun computeFirstHour() {
1289         // Compute the first full hour that is visible on screen
1290         mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP)
1291         mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY
1292     }
1293 
adjustHourSelectionnull1294     private fun adjustHourSelection() {
1295         if (mSelectionHour < 0) {
1296             setSelectedHour(0)
1297             if (mMaxAlldayEvents > 0) {
1298                 mPrevSelectedEvent = null
1299                 mSelectionAllday = true
1300             }
1301         }
1302         if (mSelectionHour > 23) {
1303             setSelectedHour(23)
1304         }
1305 
1306         // If the selected hour is at least 2 time slots from the top and
1307         // bottom of the screen, then don't scroll the view.
1308         if (mSelectionHour < mFirstHour + 1) {
1309             // If there are all-days events for the selected day but there
1310             // are no more normal events earlier in the day, then jump to
1311             // the all-day event area.
1312             // Exception 1: allow the user to scroll to 8am with the trackball
1313             // before jumping to the all-day event area.
1314             // Exception 2: if 12am is on screen, then allow the user to select
1315             // 12am before going up to the all-day event area.
1316             val daynum = mSelectionDay - mFirstJulianDay
1317             if (daynum < mEarliestStartHour!!.size && daynum >= 0 && mMaxAlldayEvents > 0 &&
1318                 mEarliestStartHour!![daynum] > mSelectionHour &&
1319                 mFirstHour > 0 && mFirstHour < 8) {
1320                 mPrevSelectedEvent = null
1321                 mSelectionAllday = true
1322                 setSelectedHour(mFirstHour + 1)
1323                 return
1324             }
1325             if (mFirstHour > 0) {
1326                 mFirstHour -= 1
1327                 mViewStartY -= mCellHeight + HOUR_GAP
1328                 if (mViewStartY < 0) {
1329                     mViewStartY = 0
1330                 }
1331                 return
1332             }
1333         }
1334         if (mSelectionHour > mFirstHour + mNumHours - 3) {
1335             if (mFirstHour < 24 - mNumHours) {
1336                 mFirstHour += 1
1337                 mViewStartY += mCellHeight + HOUR_GAP
1338                 if (mViewStartY > mMaxViewStartY) {
1339                     mViewStartY = mMaxViewStartY
1340                 }
1341                 return
1342             } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
1343                 mViewStartY = mMaxViewStartY
1344             }
1345         }
1346     }
1347 
clearCachedEventsnull1348     fun clearCachedEvents() {
1349         mLastReloadMillis = 0
1350     }
1351 
1352     private val mCancelCallback: Runnable = object : Runnable {
runnull1353         override fun run() {
1354             clearCachedEvents()
1355         }
1356     }
1357 
1358     /* package */
reloadEventsnull1359     fun reloadEvents() {
1360         // Protect against this being called before this view has been
1361         // initialized.
1362 //        if (mContext == null) {
1363 //            return;
1364 //        }
1365 
1366         // Make sure our time zones are up to date
1367         mTZUpdater.run()
1368         setSelectedEvent(null)
1369         mPrevSelectedEvent = null
1370         mSelectedEvents.clear()
1371 
1372         // The start date is the beginning of the week at 12am
1373         val weekStart = Time(Utils.getTimeZone(mContext, mTZUpdater))
1374         weekStart.set(mBaseDate)
1375         weekStart.hour = 0
1376         weekStart.minute = 0
1377         weekStart.second = 0
1378         val millis: Long = weekStart.normalize(true /* ignore isDst */)
1379 
1380         // Avoid reloading events unnecessarily.
1381         if (millis == mLastReloadMillis) {
1382             return
1383         }
1384         mLastReloadMillis = millis
1385 
1386         // load events in the background
1387         // mContext.startProgressSpinner();
1388         val events: ArrayList<Event> = ArrayList<Event>()
1389         mEventLoader.loadEventsInBackground(mNumDays, events as ArrayList<Event?>, mFirstJulianDay,
1390             object : Runnable {
1391             override fun run() {
1392                 val fadeinEvents = mFirstJulianDay != mLoadedFirstJulianDay
1393                 mEvents = events
1394                 mLoadedFirstJulianDay = mFirstJulianDay
1395                 if (mAllDayEvents == null) {
1396                     mAllDayEvents = ArrayList<Event>()
1397                 } else {
1398                     mAllDayEvents?.clear()
1399                 }
1400 
1401                 // Create a shorter array for all day events
1402                 for (e in events) {
1403                     if (e.drawAsAllday()) {
1404                         mAllDayEvents?.add(e)
1405                     }
1406                 }
1407 
1408                 // New events, new layouts
1409                 if (mLayouts == null || mLayouts!!.size < events.size) {
1410                     mLayouts = arrayOfNulls<StaticLayout>(events.size)
1411                 } else {
1412                     Arrays.fill(mLayouts, null)
1413                 }
1414                 if (mAllDayLayouts == null || mAllDayLayouts!!.size < mAllDayEvents!!.size) {
1415                     mAllDayLayouts = arrayOfNulls<StaticLayout>(events.size)
1416                 } else {
1417                     Arrays.fill(mAllDayLayouts, null)
1418                 }
1419                 computeEventRelations()
1420                 mRemeasure = true
1421                 mComputeSelectedEvents = true
1422                 recalc()
1423 
1424                 // Start animation to cross fade the events
1425                 if (fadeinEvents) {
1426                     if (mEventsCrossFadeAnimation == null) {
1427                         mEventsCrossFadeAnimation =
1428                             ObjectAnimator.ofInt(this@DayView, "EventsAlpha", 0, 255)
1429                         mEventsCrossFadeAnimation?.setDuration(EVENTS_CROSS_FADE_DURATION.toLong())
1430                     }
1431                     mEventsCrossFadeAnimation?.start()
1432                 } else {
1433                     invalidate()
1434                 }
1435             }
1436         }, mCancelCallback)
1437     }
1438 
1439     var eventsAlpha: Int
1440         get() = mEventsAlpha
1441         set(alpha) {
1442             mEventsAlpha = alpha
1443             invalidate()
1444         }
1445 
stopEventsAnimationnull1446     fun stopEventsAnimation() {
1447         if (mEventsCrossFadeAnimation != null) {
1448             mEventsCrossFadeAnimation?.cancel()
1449         }
1450         mEventsAlpha = 255
1451     }
1452 
computeEventRelationsnull1453     private fun computeEventRelations() {
1454         // Compute the layout relation between each event before measuring cell
1455         // width, as the cell width should be adjusted along with the relation.
1456         //
1457         // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
1458         // We should mark them as "overwapped". Though they are not overwapped logically, but
1459         // minimum cell height implicitly expands the cell height of A and it should look like
1460         // (1:00pm - 1:15pm) after the cell height adjustment.
1461 
1462         // Compute the space needed for the all-day events, if any.
1463         // Make a pass over all the events, and keep track of the maximum
1464         // number of all-day events in any one day.  Also, keep track of
1465         // the earliest event in each day.
1466         var maxAllDayEvents = 0
1467         val events: ArrayList<Event> = mEvents
1468         val len: Int = events.size
1469         // Num of all-day-events on each day.
1470         val eventsCount = IntArray(mLastJulianDay - mFirstJulianDay + 1)
1471         Arrays.fill(eventsCount, 0)
1472         for (ii in 0 until len) {
1473             val event: Event = events.get(ii)
1474             if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
1475                 continue
1476             }
1477             if (event.drawAsAllday()) {
1478                 // Count all the events being drawn as allDay events
1479                 val firstDay: Int = Math.max(event.startDay, mFirstJulianDay)
1480                 val lastDay: Int = Math.min(event.endDay, mLastJulianDay)
1481                 for (day in firstDay..lastDay) {
1482                     val count = ++eventsCount[day - mFirstJulianDay]
1483                     if (maxAllDayEvents < count) {
1484                         maxAllDayEvents = count
1485                     }
1486                 }
1487                 var daynum: Int = event.startDay - mFirstJulianDay
1488                 var durationDays: Int = event.endDay - event.startDay + 1
1489                 if (daynum < 0) {
1490                     durationDays += daynum
1491                     daynum = 0
1492                 }
1493                 if (daynum + durationDays > mNumDays) {
1494                     durationDays = mNumDays - daynum
1495                 }
1496                 var day = daynum
1497                 while (durationDays > 0) {
1498                     mHasAllDayEvent!![day] = true
1499                     day++
1500                     durationDays--
1501                 }
1502             } else {
1503                 var daynum: Int = event.startDay - mFirstJulianDay
1504                 var hour: Int = event.startTime / 60
1505                 if (daynum >= 0 && hour < mEarliestStartHour!![daynum]) {
1506                     mEarliestStartHour!![daynum] = hour
1507                 }
1508 
1509                 // Also check the end hour in case the event spans more than
1510                 // one day.
1511                 daynum = event.endDay - mFirstJulianDay
1512                 hour = event.endTime / 60
1513                 if (daynum < mNumDays && hour < mEarliestStartHour!![daynum]) {
1514                     mEarliestStartHour!![daynum] = hour
1515                 }
1516             }
1517         }
1518         mMaxAlldayEvents = maxAllDayEvents
1519         initAllDayHeights()
1520     }
1521 
1522     @Override
onDrawnull1523     protected override fun onDraw(canvas: Canvas) {
1524         if (mRemeasure) {
1525             remeasure(getWidth(), getHeight())
1526             mRemeasure = false
1527         }
1528         canvas.save()
1529         val yTranslate = (-mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight).toFloat()
1530         // offset canvas by the current drag and header position
1531         canvas.translate(-mViewStartX.toFloat(), yTranslate)
1532         // clip to everything below the allDay area
1533         val dest: Rect = mDestRect
1534         dest.top = (mFirstCell - yTranslate).toInt()
1535         dest.bottom = (mViewHeight - yTranslate).toInt()
1536         dest.left = 0
1537         dest.right = mViewWidth
1538         canvas.save()
1539         canvas.clipRect(dest)
1540         // Draw the movable part of the view
1541         doDraw(canvas)
1542         // restore to having no clip
1543         canvas.restore()
1544         if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
1545             val xTranslate: Float
1546             xTranslate = if (mViewStartX > 0) {
1547                 mViewWidth.toFloat()
1548             } else {
1549                 -mViewWidth.toFloat()
1550             }
1551             // Move the canvas around to prep it for the next view
1552             // specifically, shift it by a screen and undo the
1553             // yTranslation which will be redone in the nextView's onDraw().
1554             canvas.translate(xTranslate, -yTranslate)
1555             val nextView = mViewSwitcher.getNextView() as DayView
1556 
1557             // Prevent infinite recursive calls to onDraw().
1558             nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE
1559             nextView.onDraw(canvas)
1560             // Move it back for this view
1561             canvas.translate(-xTranslate, 0f)
1562         } else {
1563             // If we drew another view we already translated it back
1564             // If we didn't draw another view we should be at the edge of the
1565             // screen
1566             canvas.translate(mViewStartX.toFloat(), -yTranslate)
1567         }
1568 
1569         // Draw the fixed areas (that don't scroll) directly to the canvas.
1570         drawAfterScroll(canvas)
1571         if (mComputeSelectedEvents && mUpdateToast) {
1572             mUpdateToast = false
1573         }
1574         mComputeSelectedEvents = false
1575 
1576         // Draw overscroll glow
1577         if (!mEdgeEffectTop.isFinished()) {
1578             if (DAY_HEADER_HEIGHT != 0) {
1579                 canvas.translate(0f, DAY_HEADER_HEIGHT.toFloat())
1580             }
1581             if (mEdgeEffectTop.draw(canvas)) {
1582                 invalidate()
1583             }
1584             if (DAY_HEADER_HEIGHT != 0) {
1585                 canvas.translate(0f, -DAY_HEADER_HEIGHT.toFloat())
1586             }
1587         }
1588         if (!mEdgeEffectBottom.isFinished()) {
1589             canvas.rotate(180f, mViewWidth.toFloat() / 2f, mViewHeight.toFloat() / 2f)
1590             if (mEdgeEffectBottom.draw(canvas)) {
1591                 invalidate()
1592             }
1593         }
1594         canvas.restore()
1595     }
1596 
drawAfterScrollnull1597     private fun drawAfterScroll(canvas: Canvas) {
1598         val p: Paint = mPaint
1599         val r: Rect = mRect
1600         drawAllDayHighlights(r, canvas, p)
1601         if (mMaxAlldayEvents != 0) {
1602             drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p)
1603             drawUpperLeftCorner(r, canvas, p)
1604         }
1605         drawScrollLine(r, canvas, p)
1606         drawDayHeaderLoop(r, canvas, p)
1607 
1608         // Draw the AM and PM indicators if we're in 12 hour mode
1609         if (!mIs24HourFormat) {
1610             drawAmPm(canvas, p)
1611         }
1612     }
1613 
1614     // This isn't really the upper-left corner. It's the square area just
1615     // below the upper-left corner, above the hours and to the left of the
1616     // all-day area.
drawUpperLeftCornernull1617     private fun drawUpperLeftCorner(r: Rect, canvas: Canvas, p: Paint) {
1618         setupHourTextPaint(p)
1619         if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
1620             // Draw the allDay expand/collapse icon
1621             if (mUseExpandIcon) {
1622                 mExpandAlldayDrawable.setBounds(mExpandAllDayRect)
1623                 mExpandAlldayDrawable.draw(canvas)
1624             } else {
1625                 mCollapseAlldayDrawable.setBounds(mExpandAllDayRect)
1626                 mCollapseAlldayDrawable.draw(canvas)
1627             }
1628         }
1629     }
1630 
drawScrollLinenull1631     private fun drawScrollLine(r: Rect, canvas: Canvas, p: Paint) {
1632         val right = computeDayLeftPosition(mNumDays)
1633         val y = mFirstCell - 1
1634         p.setAntiAlias(false)
1635         p.setStyle(Style.FILL)
1636         p.setColor(mCalendarGridLineInnerHorizontalColor)
1637         p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
1638         canvas.drawLine(GRID_LINE_LEFT_MARGIN, y.toFloat(), right.toFloat(), y.toFloat(), p)
1639         p.setAntiAlias(true)
1640     }
1641 
1642     // Computes the x position for the left side of the given day (base 0)
computeDayLeftPositionnull1643     private fun computeDayLeftPosition(day: Int): Int {
1644         val effectiveWidth = mViewWidth - mHoursWidth
1645         return day * effectiveWidth / mNumDays + mHoursWidth
1646     }
1647 
drawAllDayHighlightsnull1648     private fun drawAllDayHighlights(r: Rect, canvas: Canvas, p: Paint) {
1649         if (mFutureBgColor != 0) {
1650             // First, color the labels area light gray
1651             r.top = 0
1652             r.bottom = DAY_HEADER_HEIGHT
1653             r.left = 0
1654             r.right = mViewWidth
1655             p.setColor(mBgColor)
1656             p.setStyle(Style.FILL)
1657             canvas.drawRect(r, p)
1658             // and the area that says All day
1659             r.top = DAY_HEADER_HEIGHT
1660             r.bottom = mFirstCell - 1
1661             r.left = 0
1662             r.right = mHoursWidth
1663             canvas.drawRect(r, p)
1664             var startIndex = -1
1665             val todayIndex = mTodayJulianDay - mFirstJulianDay
1666             if (todayIndex < 0) {
1667                 // Future
1668                 startIndex = 0
1669             } else if (todayIndex >= 1 && todayIndex + 1 < mNumDays) {
1670                 // Multiday - tomorrow is visible.
1671                 startIndex = todayIndex + 1
1672             }
1673             if (startIndex >= 0) {
1674                 // Draw the future highlight
1675                 r.top = 0
1676                 r.bottom = mFirstCell - 1
1677                 r.left = computeDayLeftPosition(startIndex) + 1
1678                 r.right = computeDayLeftPosition(mNumDays)
1679                 p.setColor(mFutureBgColor)
1680                 p.setStyle(Style.FILL)
1681                 canvas.drawRect(r, p)
1682             }
1683         }
1684     }
1685 
drawDayHeaderLoopnull1686     private fun drawDayHeaderLoop(r: Rect, canvas: Canvas, p: Paint) {
1687         // Draw the horizontal day background banner
1688         // p.setColor(mCalendarDateBannerBackground);
1689         // r.top = 0;
1690         // r.bottom = DAY_HEADER_HEIGHT;
1691         // r.left = 0;
1692         // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
1693         // canvas.drawRect(r, p);
1694         //
1695         // Fill the extra space on the right side with the default background
1696         // r.left = r.right;
1697         // r.right = mViewWidth;
1698         // p.setColor(mCalendarGridAreaBackground);
1699         // canvas.drawRect(r, p);
1700         if (mNumDays == 1 && ONE_DAY_HEADER_HEIGHT == 0) {
1701             return
1702         }
1703         p.setTypeface(mBold)
1704         p.setTextAlign(Paint.Align.RIGHT)
1705         var cell = mFirstJulianDay
1706         val dayNames: Array<String?>?
1707         dayNames = if (mDateStrWidth < mCellWidth) {
1708             mDayStrs
1709         } else {
1710             mDayStrs2Letter
1711         }
1712         p.setAntiAlias(true)
1713         var day = 0
1714         while (day < mNumDays) {
1715             var dayOfWeek = day + mFirstVisibleDayOfWeek
1716             if (dayOfWeek >= 14) {
1717                 dayOfWeek -= 14
1718             }
1719             var color = mCalendarDateBannerTextColor
1720             if (mNumDays == 1) {
1721                 if (dayOfWeek == Time.SATURDAY) {
1722                     color = mWeek_saturdayColor
1723                 } else if (dayOfWeek == Time.SUNDAY) {
1724                     color = mWeek_sundayColor
1725                 }
1726             } else {
1727                 val column = day % 7
1728                 if (Utils.isSaturday(column, mFirstDayOfWeek)) {
1729                     color = mWeek_saturdayColor
1730                 } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
1731                     color = mWeek_sundayColor
1732                 }
1733             }
1734             p.setColor(color)
1735             drawDayHeader(dayNames!![dayOfWeek], day, cell, canvas, p)
1736             day++
1737             cell++
1738         }
1739         p.setTypeface(null)
1740     }
1741 
drawAmPmnull1742     private fun drawAmPm(canvas: Canvas, p: Paint) {
1743         p.setColor(mCalendarAmPmLabel)
1744         p.setTextSize(AMPM_TEXT_SIZE)
1745         p.setTypeface(mBold)
1746         p.setAntiAlias(true)
1747         p.setTextAlign(Paint.Align.RIGHT)
1748         var text = mAmString
1749         if (mFirstHour >= 12) {
1750             text = mPmString
1751         }
1752         var y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP
1753         canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
1754         if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
1755             // Also draw the "PM"
1756             text = mPmString
1757             y =
1758                 mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP) +
1759                     2 * mHoursTextHeight + HOUR_GAP
1760             canvas.drawText(text as String, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
1761         }
1762     }
1763 
drawCurrentTimeLinenull1764     private fun drawCurrentTimeLine(
1765         r: Rect,
1766         day: Int,
1767         top: Int,
1768         canvas: Canvas,
1769         p: Paint
1770     ) {
1771         r.left = computeDayLeftPosition(day) - CURRENT_TIME_LINE_SIDE_BUFFER + 1
1772         r.right = computeDayLeftPosition(day + 1) + CURRENT_TIME_LINE_SIDE_BUFFER + 1
1773         r.top = top - CURRENT_TIME_LINE_TOP_OFFSET
1774         r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight()
1775         mCurrentTimeLine.setBounds(r)
1776         mCurrentTimeLine.draw(canvas)
1777         if (mAnimateToday) {
1778             mCurrentTimeAnimateLine.setBounds(r)
1779             mCurrentTimeAnimateLine.setAlpha(mAnimateTodayAlpha)
1780             mCurrentTimeAnimateLine.draw(canvas)
1781         }
1782     }
1783 
doDrawnull1784     private fun doDraw(canvas: Canvas) {
1785         val p: Paint = mPaint
1786         val r: Rect = mRect
1787         if (mFutureBgColor != 0) {
1788             drawBgColors(r, canvas, p)
1789         }
1790         drawGridBackground(r, canvas, p)
1791         drawHours(r, canvas, p)
1792 
1793         // Draw each day
1794         var cell = mFirstJulianDay
1795         p.setAntiAlias(false)
1796         val alpha: Int = p.getAlpha()
1797         p.setAlpha(mEventsAlpha)
1798         var day = 0
1799         while (day < mNumDays) {
1800 
1801             // TODO Wow, this needs cleanup. drawEvents loop through all the
1802             // events on every call.
1803             drawEvents(cell, day, HOUR_GAP, canvas, p)
1804             // If this is today
1805             if (cell == mTodayJulianDay) {
1806                 val lineY: Int =
1807                     mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
1808                         mCellHeight / 60 + 1
1809 
1810                 // And the current time shows up somewhere on the screen
1811                 if (lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
1812                     drawCurrentTimeLine(r, day, lineY, canvas, p)
1813                 }
1814             }
1815             day++
1816             cell++
1817         }
1818         p.setAntiAlias(true)
1819         p.setAlpha(alpha)
1820     }
1821 
drawHoursnull1822     private fun drawHours(r: Rect, canvas: Canvas, p: Paint) {
1823         setupHourTextPaint(p)
1824         var y = HOUR_GAP + mHoursTextHeight + HOURS_TOP_MARGIN
1825         for (i in 0..23) {
1826             val time = mHourStrs!![i]
1827             canvas.drawText(time, HOURS_LEFT_MARGIN.toFloat(), y.toFloat(), p)
1828             y += mCellHeight + HOUR_GAP
1829         }
1830     }
1831 
setupHourTextPaintnull1832     private fun setupHourTextPaint(p: Paint) {
1833         p.setColor(mCalendarHourLabelColor)
1834         p.setTextSize(HOURS_TEXT_SIZE)
1835         p.setTypeface(Typeface.DEFAULT)
1836         p.setTextAlign(Paint.Align.RIGHT)
1837         p.setAntiAlias(true)
1838     }
1839 
drawDayHeadernull1840     private fun drawDayHeader(dayStr: String?, day: Int, cell: Int, canvas: Canvas, p: Paint) {
1841         var dateNum = mFirstVisibleDate + day
1842         var x: Int
1843         if (dateNum > mMonthLength) {
1844             dateNum -= mMonthLength
1845         }
1846         p.setAntiAlias(true)
1847         val todayIndex = mTodayJulianDay - mFirstJulianDay
1848         // Draw day of the month
1849         val dateNumStr: String = dateNum.toString()
1850         if (mNumDays > 1) {
1851             val y = (DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN).toFloat()
1852 
1853             // Draw day of the month
1854             x = computeDayLeftPosition(day + 1) - DAY_HEADER_RIGHT_MARGIN
1855             p.setTextAlign(Align.RIGHT)
1856             p.setTextSize(DATE_HEADER_FONT_SIZE)
1857             p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
1858             canvas.drawText(dateNumStr as String, x.toFloat(), y, p)
1859 
1860             // Draw day of the week
1861             x -= (p.measureText(" $dateNumStr")).toInt()
1862             p.setTextSize(DAY_HEADER_FONT_SIZE)
1863             p.setTypeface(Typeface.DEFAULT)
1864             canvas.drawText(dayStr as String, x.toFloat(), y, p)
1865         } else {
1866             val y = (ONE_DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN).toFloat()
1867             p.setTextAlign(Align.LEFT)
1868 
1869             // Draw day of the week
1870             x = computeDayLeftPosition(day) + DAY_HEADER_ONE_DAY_LEFT_MARGIN
1871             p.setTextSize(DAY_HEADER_FONT_SIZE)
1872             p.setTypeface(Typeface.DEFAULT)
1873             canvas.drawText(dayStr as String, x.toFloat(), y, p)
1874 
1875             // Draw day of the month
1876             x += (p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN).toInt()
1877             p.setTextSize(DATE_HEADER_FONT_SIZE)
1878             p.setTypeface(if (todayIndex == day) mBold else Typeface.DEFAULT)
1879             canvas.drawText(dateNumStr, x.toFloat(), y, p)
1880         }
1881     }
1882 
drawGridBackgroundnull1883     private fun drawGridBackground(r: Rect, canvas: Canvas, p: Paint) {
1884         val savedStyle: Style = p.getStyle()
1885         val stopX = computeDayLeftPosition(mNumDays).toFloat()
1886         var y = 0f
1887         val deltaY = (mCellHeight + HOUR_GAP).toFloat()
1888         var linesIndex = 0
1889         val startY = 0f
1890         val stopY = (HOUR_GAP + 24 * (mCellHeight + HOUR_GAP)).toFloat()
1891         var x = mHoursWidth.toFloat()
1892 
1893         // Draw the inner horizontal grid lines
1894         p.setColor(mCalendarGridLineInnerHorizontalColor)
1895         p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
1896         p.setAntiAlias(false)
1897         y = 0f
1898         linesIndex = 0
1899         for (hour in 0..24) {
1900             mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN
1901             mLines[linesIndex++] = y
1902             mLines[linesIndex++] = stopX
1903             mLines[linesIndex++] = y
1904             y += deltaY
1905         }
1906         if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
1907             canvas.drawLines(mLines, 0, linesIndex, p)
1908             linesIndex = 0
1909             p.setColor(mCalendarGridLineInnerVerticalColor)
1910         }
1911 
1912         // Draw the inner vertical grid lines
1913         for (day in 0..mNumDays) {
1914             x = computeDayLeftPosition(day).toFloat()
1915             mLines[linesIndex++] = x
1916             mLines[linesIndex++] = startY
1917             mLines[linesIndex++] = x
1918             mLines[linesIndex++] = stopY
1919         }
1920         canvas.drawLines(mLines, 0, linesIndex, p)
1921 
1922         // Restore the saved style.
1923         p.setStyle(savedStyle)
1924         p.setAntiAlias(true)
1925     }
1926 
1927     /**
1928      * @param r
1929      * @param canvas
1930      * @param p
1931      */
drawBgColorsnull1932     private fun drawBgColors(r: Rect, canvas: Canvas, p: Paint) {
1933         val todayIndex = mTodayJulianDay - mFirstJulianDay
1934         // Draw the hours background color
1935         r.top = mDestRect.top
1936         r.bottom = mDestRect.bottom
1937         r.left = 0
1938         r.right = mHoursWidth
1939         p.setColor(mBgColor)
1940         p.setStyle(Style.FILL)
1941         p.setAntiAlias(false)
1942         canvas.drawRect(r, p)
1943 
1944         // Draw background for grid area
1945         if (mNumDays == 1 && todayIndex == 0) {
1946             // Draw a white background for the time later than current time
1947             var lineY: Int =
1948                 mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
1949                     mCellHeight / 60 + 1
1950             if (lineY < mViewStartY + mViewHeight) {
1951                 lineY = Math.max(lineY, mViewStartY)
1952                 r.left = mHoursWidth
1953                 r.right = mViewWidth
1954                 r.top = lineY
1955                 r.bottom = mViewStartY + mViewHeight
1956                 p.setColor(mFutureBgColor)
1957                 canvas.drawRect(r, p)
1958             }
1959         } else if (todayIndex >= 0 && todayIndex < mNumDays) {
1960             // Draw today with a white background for the time later than current time
1961             var lineY: Int =
1962                 mCurrentTime!!.hour * (mCellHeight + HOUR_GAP) + mCurrentTime!!.minute *
1963                     mCellHeight / 60 + 1
1964             if (lineY < mViewStartY + mViewHeight) {
1965                 lineY = Math.max(lineY, mViewStartY)
1966                 r.left = computeDayLeftPosition(todayIndex) + 1
1967                 r.right = computeDayLeftPosition(todayIndex + 1)
1968                 r.top = lineY
1969                 r.bottom = mViewStartY + mViewHeight
1970                 p.setColor(mFutureBgColor)
1971                 canvas.drawRect(r, p)
1972             }
1973 
1974             // Paint Tomorrow and later days with future color
1975             if (todayIndex + 1 < mNumDays) {
1976                 r.left = computeDayLeftPosition(todayIndex + 1) + 1
1977                 r.right = computeDayLeftPosition(mNumDays)
1978                 r.top = mDestRect.top
1979                 r.bottom = mDestRect.bottom
1980                 p.setColor(mFutureBgColor)
1981                 canvas.drawRect(r, p)
1982             }
1983         } else if (todayIndex < 0) {
1984             // Future
1985             r.left = computeDayLeftPosition(0) + 1
1986             r.right = computeDayLeftPosition(mNumDays)
1987             r.top = mDestRect.top
1988             r.bottom = mDestRect.bottom
1989             p.setColor(mFutureBgColor)
1990             canvas.drawRect(r, p)
1991         }
1992         p.setAntiAlias(true)
1993     }
1994 
computeMaxStringWidthnull1995     private fun computeMaxStringWidth(currentMax: Int, strings: Array<String?>, p: Paint): Int {
1996         var maxWidthF = 0.0f
1997         val len = strings.size
1998         for (i in 0 until len) {
1999             val width: Float = p.measureText(strings[i])
2000             maxWidthF = Math.max(width, maxWidthF)
2001         }
2002         var maxWidth = (maxWidthF + 0.5).toInt()
2003         if (maxWidth < currentMax) {
2004             maxWidth = currentMax
2005         }
2006         return maxWidth
2007     }
2008 
saveSelectionPositionnull2009     private fun saveSelectionPosition(left: Float, top: Float, right: Float, bottom: Float) {
2010         mPrevBox.left = left.toInt()
2011         mPrevBox.right = right.toInt()
2012         mPrevBox.top = top.toInt()
2013         mPrevBox.bottom = bottom.toInt()
2014     }
2015 
setupTextRectnull2016     private fun setupTextRect(r: Rect) {
2017         if (r.bottom <= r.top || r.right <= r.left) {
2018             r.bottom = r.top
2019             r.right = r.left
2020             return
2021         }
2022         if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
2023             r.top += EVENT_TEXT_TOP_MARGIN
2024             r.bottom -= EVENT_TEXT_BOTTOM_MARGIN
2025         }
2026         if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
2027             r.left += EVENT_TEXT_LEFT_MARGIN
2028             r.right -= EVENT_TEXT_RIGHT_MARGIN
2029         }
2030     }
2031 
setupAllDayTextRectnull2032     private fun setupAllDayTextRect(r: Rect) {
2033         if (r.bottom <= r.top || r.right <= r.left) {
2034             r.bottom = r.top
2035             r.right = r.left
2036             return
2037         }
2038         if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
2039             r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN
2040             r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN
2041         }
2042         if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
2043             r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN
2044             r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN
2045         }
2046     }
2047 
2048     /**
2049      * Return the layout for a numbered event. Create it if not already existing
2050      */
getEventLayoutnull2051     private fun getEventLayout(
2052         layouts: Array<StaticLayout?>?,
2053         i: Int,
2054         event: Event,
2055         paint: Paint,
2056         r: Rect
2057     ): StaticLayout? {
2058         if (i < 0 || i >= layouts!!.size) {
2059             return null
2060         }
2061         var layout: StaticLayout? = layouts[i]
2062         // Check if we have already initialized the StaticLayout and that
2063         // the width hasn't changed (due to vertical resizing which causes
2064         // re-layout of events at min height)
2065         if (layout == null || r.width() !== layout.getWidth()) {
2066             val bob = SpannableStringBuilder()
2067             if (event.title != null) {
2068                 // MAX - 1 since we add a space
2069                 bob.append(drawTextSanitizer(event.title.toString(),
2070                     MAX_EVENT_TEXT_LEN - 1))
2071                 bob.setSpan(StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length, 0)
2072                 bob.append(' ')
2073             }
2074             if (event.location != null) {
2075                 bob.append(
2076                     drawTextSanitizer(
2077                         event.location.toString(),
2078                         MAX_EVENT_TEXT_LEN - bob.length
2079                     )
2080                 )
2081             }
2082             when (event.selfAttendeeStatus) {
2083                 Attendees.ATTENDEE_STATUS_INVITED -> paint.setColor(event.color)
2084                 Attendees.ATTENDEE_STATUS_DECLINED -> {
2085                     paint.setColor(mEventTextColor)
2086                     paint.setAlpha(Utils.DECLINED_EVENT_TEXT_ALPHA)
2087                 }
2088                 Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
2089                     Attendees.ATTENDEE_STATUS_TENTATIVE -> paint.setColor(
2090                     mEventTextColor
2091                 )
2092                 else -> paint.setColor(mEventTextColor)
2093             }
2094 
2095             // Leave a one pixel boundary on the left and right of the rectangle for the event
2096             layout = StaticLayout(
2097                 bob, 0, bob.length, TextPaint(paint), r.width(),
2098                 Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width()
2099             )
2100             layouts[i] = layout
2101         }
2102         layout.getPaint().setAlpha(mEventsAlpha)
2103         return layout
2104     }
2105 
drawAllDayEventsnull2106     private fun drawAllDayEvents(firstDay: Int, numDays: Int, canvas: Canvas, p: Paint) {
2107         p.setTextSize(NORMAL_FONT_SIZE)
2108         p.setTextAlign(Paint.Align.LEFT)
2109         val eventTextPaint: Paint = mEventTextPaint
2110         val startY = DAY_HEADER_HEIGHT.toFloat()
2111         val stopY = startY + mAlldayHeight + ALLDAY_TOP_MARGIN
2112         var x = 0f
2113         var linesIndex = 0
2114 
2115         // Draw the inner vertical grid lines
2116         p.setColor(mCalendarGridLineInnerVerticalColor)
2117         x = mHoursWidth.toFloat()
2118         p.setStrokeWidth(GRID_LINE_INNER_WIDTH)
2119         // Line bounding the top of the all day area
2120         mLines[linesIndex++] = GRID_LINE_LEFT_MARGIN
2121         mLines[linesIndex++] = startY
2122         mLines[linesIndex++] = computeDayLeftPosition(mNumDays).toFloat()
2123         mLines[linesIndex++] = startY
2124         for (day in 0..mNumDays) {
2125             x = computeDayLeftPosition(day).toFloat()
2126             mLines[linesIndex++] = x
2127             mLines[linesIndex++] = startY
2128             mLines[linesIndex++] = x
2129             mLines[linesIndex++] = stopY
2130         }
2131         p.setAntiAlias(false)
2132         canvas.drawLines(mLines, 0, linesIndex, p)
2133         p.setStyle(Style.FILL)
2134         val y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
2135         val lastDay = firstDay + numDays - 1
2136         val events: ArrayList<Event>? = mAllDayEvents
2137         val numEvents: Int = events!!.size
2138         // Whether or not we should draw the more events text
2139         var hasMoreEvents = false
2140         // size of the allDay area
2141         val drawHeight = mAlldayHeight.toFloat()
2142         // max number of events being drawn in one day of the allday area
2143         var numRectangles = mMaxAlldayEvents.toFloat()
2144         // Where to cut off drawn allday events
2145         var allDayEventClip = DAY_HEADER_HEIGHT + mAlldayHeight + ALLDAY_TOP_MARGIN
2146         // The number of events that weren't drawn in each day
2147         mSkippedAlldayEvents = IntArray(numDays)
2148         if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount &&
2149             !mShowAllAllDayEvents && mAnimateDayHeight == 0) {
2150             // We draw one fewer event than will fit so that more events text
2151             // can be drawn
2152             numRectangles = (mMaxUnexpandedAlldayEventCount - 1).toFloat()
2153             // We also clip the events above the more events text
2154             allDayEventClip -= MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
2155             hasMoreEvents = true
2156         } else if (mAnimateDayHeight != 0) {
2157             // clip at the end of the animating space
2158             allDayEventClip = DAY_HEADER_HEIGHT + mAnimateDayHeight + ALLDAY_TOP_MARGIN
2159         }
2160         var alpha: Int = eventTextPaint.getAlpha()
2161         eventTextPaint.setAlpha(mEventsAlpha)
2162         for (i in 0 until numEvents) {
2163             val event: Event = events.get(i)
2164             var startDay: Int = event.startDay
2165             var endDay: Int = event.endDay
2166             if (startDay > lastDay || endDay < firstDay) {
2167                 continue
2168             }
2169             if (startDay < firstDay) {
2170                 startDay = firstDay
2171             }
2172             if (endDay > lastDay) {
2173                 endDay = lastDay
2174             }
2175             val startIndex = startDay - firstDay
2176             val endIndex = endDay - firstDay
2177             var height =
2178                 if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount)
2179                     mAnimateDayEventHeight.toFloat() else drawHeight / numRectangles
2180 
2181             // Prevent a single event from getting too big
2182             if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
2183                 height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
2184             }
2185 
2186             // Leave a one-pixel space between the vertical day lines and the
2187             // event rectangle.
2188             event.left = computeDayLeftPosition(startIndex).toFloat()
2189             event.right = computeDayLeftPosition(endIndex + 1).toFloat() - DAY_GAP
2190             event.top = y + height * event.getColumn()
2191             event.bottom = event.top + height - ALL_DAY_EVENT_RECT_BOTTOM_MARGIN
2192             if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
2193                 // check if we should skip this event. We skip if it starts
2194                 // after the clip bound or ends after the skip bound and we're
2195                 // not animating.
2196                 if (event.top >= allDayEventClip) {
2197                     incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
2198                     continue
2199                 } else if (event.bottom > allDayEventClip) {
2200                     if (hasMoreEvents) {
2201                         incrementSkipCount(mSkippedAlldayEvents, startIndex, endIndex)
2202                         continue
2203                     }
2204                     event.bottom = allDayEventClip.toFloat()
2205                 }
2206             }
2207             val r: Rect = drawEventRect(
2208                 event, canvas, p, eventTextPaint, event.top.toInt(),
2209                 event.bottom.toInt()
2210             )
2211             setupAllDayTextRect(r)
2212             val layout: StaticLayout? = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r)
2213             drawEventText(layout, r, canvas, r.top, r.bottom, true)
2214 
2215             // Check if this all-day event intersects the selected day
2216             if (mSelectionAllday && mComputeSelectedEvents) {
2217                 if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
2218                     mSelectedEvents.add(event)
2219                 }
2220             }
2221         }
2222         eventTextPaint.setAlpha(alpha)
2223         if (mMoreAlldayEventsTextAlpha != 0 && mSkippedAlldayEvents != null) {
2224             // If the more allday text should be visible, draw it.
2225             alpha = p.getAlpha()
2226             p.setAlpha(mEventsAlpha)
2227             p.setColor(mMoreAlldayEventsTextAlpha shl 24 and mMoreEventsTextColor)
2228             for (i in mSkippedAlldayEvents!!.indices) {
2229                 if (mSkippedAlldayEvents!![i] > 0) {
2230                     drawMoreAlldayEvents(canvas, mSkippedAlldayEvents!![i], i, p)
2231                 }
2232             }
2233             p.setAlpha(alpha)
2234         }
2235         if (mSelectionAllday) {
2236             // Compute the neighbors for the list of all-day events that
2237             // intersect the selected day.
2238             computeAllDayNeighbors()
2239 
2240             // Set the selection position to zero so that when we move down
2241             // to the normal event area, we will highlight the topmost event.
2242             saveSelectionPosition(0f, 0f, 0f, 0f)
2243         }
2244     }
2245 
2246     // Helper method for counting the number of allday events skipped on each day
incrementSkipCountnull2247     private fun incrementSkipCount(counts: IntArray?, startIndex: Int, endIndex: Int) {
2248         if (counts == null || startIndex < 0 || endIndex > counts.size) {
2249             return
2250         }
2251         for (i in startIndex..endIndex) {
2252             counts[i]++
2253         }
2254     }
2255 
2256     // Draws the "box +n" text for hidden allday events
drawMoreAlldayEventsnull2257     protected fun drawMoreAlldayEvents(canvas: Canvas, remainingEvents: Int, day: Int, p: Paint) {
2258         var x = computeDayLeftPosition(day) + EVENT_ALL_DAY_TEXT_LEFT_MARGIN
2259         var y = (mAlldayHeight - .5f * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - (.5f *
2260             EVENT_SQUARE_WIDTH) + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN).toInt()
2261         val r: Rect = mRect
2262         r.top = y
2263         r.left = x
2264         r.bottom = y + EVENT_SQUARE_WIDTH
2265         r.right = x + EVENT_SQUARE_WIDTH
2266         p.setColor(mMoreEventsTextColor)
2267         p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
2268         p.setStyle(Style.STROKE)
2269         p.setAntiAlias(false)
2270         canvas.drawRect(r, p)
2271         p.setAntiAlias(true)
2272         p.setStyle(Style.FILL)
2273         p.setTextSize(EVENT_TEXT_FONT_SIZE)
2274         val text: String =
2275             mResources.getQuantityString(R.plurals.month_more_events, remainingEvents)
2276         y += EVENT_SQUARE_WIDTH
2277         x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING
2278         canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(), p)
2279     }
2280 
computeAllDayNeighborsnull2281     private fun computeAllDayNeighbors() {
2282         val len: Int = mSelectedEvents.size
2283         if (len == 0 || mSelectedEvent != null) {
2284             return
2285         }
2286 
2287         // First, clear all the links
2288         for (ii in 0 until len) {
2289             val ev: Event = mSelectedEvents.get(ii)
2290             ev.nextUp = null
2291             ev.nextDown = null
2292             ev.nextLeft = null
2293             ev.nextRight = null
2294         }
2295 
2296         // For each event in the selected event list "mSelectedEvents", find
2297         // its neighbors in the up and down directions. This could be done
2298         // more efficiently by sorting on the Event.getColumn() field, but
2299         // the list is expected to be very small.
2300 
2301         // Find the event in the same row as the previously selected all-day
2302         // event, if any.
2303         var startPosition = -1
2304         if (mPrevSelectedEvent != null && mPrevSelectedEvent!!.drawAsAllday()) {
2305             startPosition = mPrevSelectedEvent?.getColumn() as Int
2306         }
2307         var maxPosition = -1
2308         var startEvent: Event? = null
2309         var maxPositionEvent: Event? = null
2310         for (ii in 0 until len) {
2311             val ev: Event = mSelectedEvents.get(ii)
2312             val position: Int = ev.getColumn()
2313             if (position == startPosition) {
2314                 startEvent = ev
2315             } else if (position > maxPosition) {
2316                 maxPositionEvent = ev
2317                 maxPosition = position
2318             }
2319             for (jj in 0 until len) {
2320                 if (jj == ii) {
2321                     continue
2322                 }
2323                 val neighbor: Event = mSelectedEvents.get(jj)
2324                 val neighborPosition: Int = neighbor.getColumn()
2325                 if (neighborPosition == position - 1) {
2326                     ev.nextUp = neighbor
2327                 } else if (neighborPosition == position + 1) {
2328                     ev.nextDown = neighbor
2329                 }
2330             }
2331         }
2332         if (startEvent != null) {
2333             setSelectedEvent(startEvent)
2334         } else {
2335             setSelectedEvent(maxPositionEvent)
2336         }
2337     }
2338 
drawEventsnull2339     private fun drawEvents(date: Int, dayIndex: Int, top: Int, canvas: Canvas, p: Paint) {
2340         val eventTextPaint: Paint = mEventTextPaint
2341         val left = computeDayLeftPosition(dayIndex) + 1
2342         val cellWidth = computeDayLeftPosition(dayIndex + 1) - left + 1
2343         val cellHeight = mCellHeight
2344 
2345         // Use the selected hour as the selection region
2346         val selectionArea: Rect = mSelectionRect
2347         selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP)
2348         selectionArea.bottom = selectionArea.top + cellHeight
2349         selectionArea.left = left
2350         selectionArea.right = selectionArea.left + cellWidth
2351         val events: ArrayList<Event> = mEvents
2352         val numEvents: Int = events.size
2353         val geometry: EventGeometry = mEventGeometry
2354         val viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight
2355         val alpha: Int = eventTextPaint.getAlpha()
2356         eventTextPaint.setAlpha(mEventsAlpha)
2357         for (i in 0 until numEvents) {
2358             val event: Event = events.get(i)
2359             if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
2360                 continue
2361             }
2362 
2363             // Don't draw it if it is not visible
2364             if (event.bottom < mViewStartY || event.top > viewEndY) {
2365                 continue
2366             }
2367             if (date == mSelectionDay && !mSelectionAllday && mComputeSelectedEvents &&
2368                 geometry.eventIntersectsSelection(event, selectionArea)
2369             ) {
2370                 mSelectedEvents.add(event)
2371             }
2372             val r: Rect = drawEventRect(event, canvas, p, eventTextPaint, mViewStartY, viewEndY)
2373             setupTextRect(r)
2374 
2375             // Don't draw text if it is not visible
2376             if (r.top > viewEndY || r.bottom < mViewStartY) {
2377                 continue
2378             }
2379             val layout: StaticLayout? = getEventLayout(mLayouts, i, event, eventTextPaint, r)
2380             // TODO: not sure why we are 4 pixels off
2381             drawEventText(
2382                 layout,
2383                 r,
2384                 canvas,
2385                 mViewStartY + 4,
2386                 mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAlldayHeight,
2387                 false
2388             )
2389         }
2390         eventTextPaint.setAlpha(alpha)
2391     }
2392 
drawEventRectnull2393     private fun drawEventRect(
2394         event: Event,
2395         canvas: Canvas,
2396         p: Paint,
2397         eventTextPaint: Paint,
2398         visibleTop: Int,
2399         visibleBot: Int
2400     ): Rect {
2401         // Draw the Event Rect
2402         val r: Rect = mRect
2403         r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN, visibleTop)
2404         r.bottom = Math.min(event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN, visibleBot)
2405         r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
2406         r.right = event.right.toInt()
2407         var color: Int = event.color
2408         when (event.selfAttendeeStatus) {
2409             Attendees.ATTENDEE_STATUS_INVITED -> if (event !== mClickedEvent) {
2410                 p.setStyle(Style.STROKE)
2411             }
2412             Attendees.ATTENDEE_STATUS_DECLINED -> {
2413                 if (event !== mClickedEvent) {
2414                     color = Utils.getDeclinedColorFromColor(color)
2415                 }
2416                 p.setStyle(Style.FILL_AND_STROKE)
2417             }
2418             Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED,
2419                 Attendees.ATTENDEE_STATUS_TENTATIVE -> p.setStyle(
2420                 Style.FILL_AND_STROKE
2421             )
2422             else -> p.setStyle(Style.FILL_AND_STROKE)
2423         }
2424         p.setAntiAlias(false)
2425         val floorHalfStroke = Math.floor(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
2426         val ceilHalfStroke = Math.ceil(EVENT_RECT_STROKE_WIDTH.toDouble() / 2.0).toInt()
2427         r.top = Math.max(event.top.toInt() + EVENT_RECT_TOP_MARGIN + floorHalfStroke, visibleTop)
2428         r.bottom = Math.min(
2429             event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN - ceilHalfStroke,
2430             visibleBot
2431         )
2432         r.left += floorHalfStroke
2433         r.right -= ceilHalfStroke
2434         p.setStrokeWidth(EVENT_RECT_STROKE_WIDTH.toFloat())
2435         p.setColor(color)
2436         val alpha: Int = p.getAlpha()
2437         p.setAlpha(mEventsAlpha)
2438         canvas.drawRect(r, p)
2439         p.setAlpha(alpha)
2440         p.setStyle(Style.FILL)
2441 
2442         // Setup rect for drawEventText which follows
2443         r.top = event.top.toInt() + EVENT_RECT_TOP_MARGIN
2444         r.bottom = event.bottom.toInt() - EVENT_RECT_BOTTOM_MARGIN
2445         r.left = event.left.toInt() + EVENT_RECT_LEFT_MARGIN
2446         r.right = event.right.toInt() - EVENT_RECT_RIGHT_MARGIN
2447         return r
2448     }
2449 
2450     private val drawTextSanitizerFilter: Pattern = Pattern.compile("[\t\n],")
2451 
2452     // Sanitize a string before passing it to drawText or else we get little
2453     // squares. For newlines and tabs before a comma, delete the character.
2454     // Otherwise, just replace them with a space.
drawTextSanitizernull2455     private fun drawTextSanitizer(string: String, maxEventTextLen: Int): String {
2456         var string = string
2457         val m: Matcher = drawTextSanitizerFilter.matcher(string)
2458         string = m.replaceAll(",")
2459         var len: Int = string.length
2460         if (maxEventTextLen <= 0) {
2461             string = ""
2462             len = 0
2463         } else if (len > maxEventTextLen) {
2464             string = string.substring(0, maxEventTextLen)
2465             len = maxEventTextLen
2466         }
2467         return string.replace('\n', ' ')
2468     }
2469 
drawEventTextnull2470     private fun drawEventText(
2471         eventLayout: StaticLayout?,
2472         rect: Rect,
2473         canvas: Canvas,
2474         top: Int,
2475         bottom: Int,
2476         center: Boolean
2477     ) {
2478         // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
2479         val width: Int = rect.right - rect.left
2480         val height: Int = rect.bottom - rect.top
2481 
2482         // If the rectangle is too small for text, then return
2483         if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
2484             return
2485         }
2486         var totalLineHeight = 0
2487         val lineCount: Int = eventLayout.getLineCount()
2488         for (i in 0 until lineCount) {
2489             val lineBottom: Int = eventLayout.getLineBottom(i)
2490             totalLineHeight = if (lineBottom <= height) {
2491                 lineBottom
2492             } else {
2493                 break
2494             }
2495         }
2496 
2497         // + 2 is small workaround when the font is slightly bigger than the rect. This will
2498         // still allow the text to be shown without overflowing into the other all day rects.
2499         if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight + 2 < top) {
2500             return
2501         }
2502 
2503         // Use a StaticLayout to format the string.
2504         canvas.save()
2505         //  canvas.translate(rect.left, rect.top + (rect.bottom - rect.top / 2));
2506         val padding = if (center) (rect.bottom - rect.top - totalLineHeight) / 2 else 0
2507         canvas.translate(rect.left.toFloat(), rect.top.toFloat() + padding)
2508         rect.left = 0
2509         rect.right = width
2510         rect.top = 0
2511         rect.bottom = totalLineHeight
2512 
2513         // There's a bug somewhere. If this rect is outside of a previous
2514         // cliprect, this becomes a no-op. What happens is that the text draw
2515         // past the event rect. The current fix is to not draw the staticLayout
2516         // at all if it is completely out of bound.
2517         canvas.clipRect(rect)
2518         eventLayout.draw(canvas)
2519         canvas.restore()
2520     }
2521 
2522     // The following routines are called from the parent activity when certain
2523     // touch events occur.
doDownnull2524     private fun doDown(ev: MotionEvent) {
2525         mTouchMode = TOUCH_MODE_DOWN
2526         mViewStartX = 0
2527         mOnFlingCalled = false
2528         mHandler?.removeCallbacks(mContinueScroll)
2529         val x = ev.getX().toInt()
2530         val y = ev.getY().toInt()
2531 
2532         // Save selection information: we use setSelectionFromPosition to find the selected event
2533         // in order to show the "clicked" color. But since it is also setting the selected info
2534         // for new events, we need to restore the old info after calling the function.
2535         val oldSelectedEvent: Event? = mSelectedEvent
2536         val oldSelectionDay = mSelectionDay
2537         val oldSelectionHour = mSelectionHour
2538         if (setSelectionFromPosition(x, y, false)) {
2539             // If a time was selected (a blue selection box is visible) and the click location
2540             // is in the selected time, do not show a click on an event to prevent a situation
2541             // of both a selection and an event are clicked when they overlap.
2542             val pressedSelected = (mSelectionMode != SELECTION_HIDDEN &&
2543                 oldSelectionDay == mSelectionDay && oldSelectionHour == mSelectionHour)
2544             if (!pressedSelected && mSelectedEvent != null) {
2545                 mSavedClickedEvent = mSelectedEvent
2546                 mDownTouchTime = System.currentTimeMillis()
2547                 postDelayed(mSetClick, mOnDownDelay.toLong())
2548             } else {
2549                 eventClickCleanup()
2550             }
2551         }
2552         mSelectedEvent = oldSelectedEvent
2553         mSelectionDay = oldSelectionDay
2554         mSelectionHour = oldSelectionHour
2555         invalidate()
2556     }
2557 
2558     // Kicks off all the animations when the expand allday area is tapped
doExpandAllDayClicknull2559     private fun doExpandAllDayClick() {
2560         mShowAllAllDayEvents = !mShowAllAllDayEvents
2561         ObjectAnimator.setFrameDelay(0)
2562 
2563         // Determine the starting height
2564         if (mAnimateDayHeight == 0) {
2565             mAnimateDayHeight =
2566                 if (mShowAllAllDayEvents) mAlldayHeight - MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
2567                 else mAlldayHeight
2568         }
2569         // Cancel current animations
2570         mCancellingAnimations = true
2571         if (mAlldayAnimator != null) {
2572             mAlldayAnimator?.cancel()
2573         }
2574         if (mAlldayEventAnimator != null) {
2575             mAlldayEventAnimator?.cancel()
2576         }
2577         if (mMoreAlldayEventsAnimator != null) {
2578             mMoreAlldayEventsAnimator?.cancel()
2579         }
2580         mCancellingAnimations = false
2581         // get new animators
2582         mAlldayAnimator = allDayAnimator
2583         mAlldayEventAnimator = allDayEventAnimator
2584         mMoreAlldayEventsAnimator = ObjectAnimator.ofInt(
2585             this,
2586             "moreAllDayEventsTextAlpha",
2587             if (mShowAllAllDayEvents) MORE_EVENTS_MAX_ALPHA else 0,
2588             if (mShowAllAllDayEvents) 0 else MORE_EVENTS_MAX_ALPHA
2589         )
2590 
2591         // Set up delays and start the animators
2592         mAlldayAnimator?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION
2593             else 0)
2594         mAlldayAnimator?.start()
2595         mMoreAlldayEventsAnimator?.setStartDelay(if (mShowAllAllDayEvents) 0
2596             else ANIMATION_DURATION)
2597         mMoreAlldayEventsAnimator?.setDuration(ANIMATION_SECONDARY_DURATION)
2598         mMoreAlldayEventsAnimator?.start()
2599         if (mAlldayEventAnimator != null) {
2600             // This is the only animator that can return null, so check it
2601             mAlldayEventAnimator
2602                 ?.setStartDelay(if (mShowAllAllDayEvents) ANIMATION_SECONDARY_DURATION else 0)
2603             mAlldayEventAnimator?.start()
2604         }
2605     }
2606 
2607     /**
2608      * Figures out the initial heights for allDay events and space when
2609      * a view is being set up.
2610      */
initAllDayHeightsnull2611     fun initAllDayHeights() {
2612         if (mMaxAlldayEvents <= mMaxUnexpandedAlldayEventCount) {
2613             return
2614         }
2615         if (mShowAllAllDayEvents) {
2616             var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
2617             maxADHeight = Math.min(
2618                 maxADHeight,
2619                 (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
2620             )
2621             mAnimateDayEventHeight = maxADHeight / mMaxAlldayEvents
2622         } else {
2623             mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
2624         }
2625     } // First calculate the absolute max height
2626     // Now expand to fit but not beyond the absolute max
2627     // calculate the height of individual events in order to fit
2628     // if there's nothing to animate just return
2629 
2630     // Set up the animator with the calculated values
2631     // Sets up an animator for changing the height of allday events
2632     private val allDayEventAnimator: ObjectAnimator?
2633         private get() {
2634             // First calculate the absolute max height
2635             var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
2636             // Now expand to fit but not beyond the absolute max
2637             maxADHeight = Math.min(
2638                 maxADHeight,
2639                 (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
2640             )
2641             // calculate the height of individual events in order to fit
2642             val fitHeight = maxADHeight / mMaxAlldayEvents
2643             val currentHeight = mAnimateDayEventHeight
2644             val desiredHeight =
2645                 if (mShowAllAllDayEvents) fitHeight else MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
2646             // if there's nothing to animate just return
2647             if (currentHeight == desiredHeight) {
2648                 return null
2649             }
2650 
2651             // Set up the animator with the calculated values
2652             val animator: ObjectAnimator = ObjectAnimator.ofInt(
2653                 this, "animateDayEventHeight",
2654                 currentHeight, desiredHeight
2655             )
2656             animator.setDuration(ANIMATION_DURATION)
2657             return animator
2658         }
2659 
2660     // Set up the animator with the calculated values
2661     // Sets up an animator for changing the height of the allday area
2662     private val allDayAnimator: ObjectAnimator
2663         private get() {
2664             // Calculate the absolute max height
2665             var maxADHeight = mViewHeight - DAY_HEADER_HEIGHT - MIN_HOURS_HEIGHT
2666             // Find the desired height but don't exceed abs max
2667             maxADHeight = Math.min(
2668                 maxADHeight,
2669                 (mMaxAlldayEvents * MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT).toInt()
2670             )
2671             // calculate the current and desired heights
2672             val currentHeight = if (mAnimateDayHeight != 0) mAnimateDayHeight else mAlldayHeight
2673             val desiredHeight =
2674                 if (mShowAllAllDayEvents) maxADHeight else (MAX_UNEXPANDED_ALLDAY_HEIGHT -
2675                     MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT - 1).toInt()
2676 
2677             // Set up the animator with the calculated values
2678             val animator: ObjectAnimator = ObjectAnimator.ofInt(
2679                 this, "animateDayHeight",
2680                 currentHeight, desiredHeight
2681             )
2682             animator.setDuration(ANIMATION_DURATION)
2683             animator.addListener(object : AnimatorListenerAdapter() {
2684                 @Override
onAnimationEndnull2685                 override fun onAnimationEnd(animation: Animator) {
2686                     if (!mCancellingAnimations) {
2687                         // when finished, set this to 0 to signify not animating
2688                         mAnimateDayHeight = 0
2689                         mUseExpandIcon = !mShowAllAllDayEvents
2690                     }
2691                     mRemeasure = true
2692                     invalidate()
2693                 }
2694             })
2695             return animator
2696         }
2697 
2698     // setter for the 'box +n' alpha text used by the animator
setMoreAllDayEventsTextAlphanull2699     fun setMoreAllDayEventsTextAlpha(alpha: Int) {
2700         mMoreAlldayEventsTextAlpha = alpha
2701         invalidate()
2702     }
2703 
2704     // setter for the height of the allday area used by the animator
setAnimateDayHeightnull2705     fun setAnimateDayHeight(height: Int) {
2706         mAnimateDayHeight = height
2707         mRemeasure = true
2708         invalidate()
2709     }
2710 
2711     // setter for the height of allday events used by the animator
setAnimateDayEventHeightnull2712     fun setAnimateDayEventHeight(height: Int) {
2713         mAnimateDayEventHeight = height
2714         mRemeasure = true
2715         invalidate()
2716     }
2717 
doSingleTapUpnull2718     private fun doSingleTapUp(ev: MotionEvent) {
2719         if (!mHandleActionUp || mScrolling) {
2720             return
2721         }
2722         val x = ev.getX().toInt()
2723         val y = ev.getY().toInt()
2724         val selectedDay = mSelectionDay
2725         val selectedHour = mSelectionHour
2726         if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
2727             // check if the tap was in the allday expansion area
2728             val bottom = mFirstCell
2729             if (x < mHoursWidth && y > DAY_HEADER_HEIGHT && y < DAY_HEADER_HEIGHT + mAlldayHeight ||
2730                 !mShowAllAllDayEvents && mAnimateDayHeight == 0 && y < bottom && y >= bottom -
2731                 MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT
2732             ) {
2733                 doExpandAllDayClick()
2734                 return
2735             }
2736         }
2737         val validPosition = setSelectionFromPosition(x, y, false)
2738         if (!validPosition) {
2739             if (y < DAY_HEADER_HEIGHT) {
2740                 val selectedTime = Time(mBaseDate)
2741                 selectedTime.setJulianDay(mSelectionDay)
2742                 selectedTime.hour = mSelectionHour
2743                 selectedTime.normalize(true /* ignore isDst */)
2744                 mController.sendEvent(
2745                     this as? Object, EventType.GO_TO, null, null, selectedTime, -1,
2746                     ViewType.DAY, CalendarController.EXTRA_GOTO_DATE, null, null
2747                 )
2748             }
2749             return
2750         }
2751         val hasSelection = mSelectionMode != SELECTION_HIDDEN
2752         val pressedSelected = ((hasSelection || mTouchExplorationEnabled) &&
2753             selectedDay == mSelectionDay && selectedHour == mSelectionHour)
2754         if (mSelectedEvent != null) {
2755             // If the tap is on an event, launch the "View event" view
2756             if (mIsAccessibilityEnabled) {
2757                 mAccessibilityMgr?.interrupt()
2758             }
2759             mSelectionMode = SELECTION_HIDDEN
2760             var yLocation = ((mSelectedEvent!!.top + mSelectedEvent!!.bottom) / 2) as Int
2761             // Y location is affected by the position of the event in the scrolling
2762             // view (mViewStartY) and the presence of all day events (mFirstCell)
2763             if (!mSelectedEvent!!.allDay) {
2764                 yLocation += mFirstCell - mViewStartY
2765             }
2766             mClickedYLocation = yLocation
2767             val clearDelay: Long = CLICK_DISPLAY_DURATION + mOnDownDelay -
2768                 (System.currentTimeMillis() - mDownTouchTime)
2769             if (clearDelay > 0) {
2770                 this.postDelayed(mClearClick, clearDelay)
2771             } else {
2772                 this.post(mClearClick)
2773             }
2774         }
2775         invalidate()
2776     }
2777 
doLongPressnull2778     private fun doLongPress(ev: MotionEvent) {
2779         eventClickCleanup()
2780         if (mScrolling) {
2781             return
2782         }
2783 
2784         // Scale gesture in progress
2785         if (mStartingSpanY != 0f) {
2786             return
2787         }
2788         val x = ev.getX().toInt()
2789         val y = ev.getY().toInt()
2790         val validPosition = setSelectionFromPosition(x, y, false)
2791         if (!validPosition) {
2792             // return if the touch wasn't on an area of concern
2793             return
2794         }
2795         invalidate()
2796         performLongClick()
2797     }
2798 
doScrollnull2799     private fun doScroll(e1: MotionEvent?, e2: MotionEvent, deltaX: Float, deltaY: Float) {
2800         cancelAnimation()
2801         if (mStartingScroll) {
2802             mInitialScrollX = 0f
2803             mInitialScrollY = 0f
2804             mStartingScroll = false
2805         }
2806         mInitialScrollX += deltaX
2807         mInitialScrollY += deltaY
2808         val distanceX = mInitialScrollX.toInt()
2809         val distanceY = mInitialScrollY.toInt()
2810         val focusY = getAverageY(e2)
2811         if (mRecalCenterHour) {
2812             // Calculate the hour that correspond to the average of the Y touch points
2813             mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
2814                 (mCellHeight + DAY_GAP))
2815             mRecalCenterHour = false
2816         }
2817 
2818         // If we haven't figured out the predominant scroll direction yet,
2819         // then do it now.
2820         if (mTouchMode == TOUCH_MODE_DOWN) {
2821             val absDistanceX: Int = Math.abs(distanceX)
2822             val absDistanceY: Int = Math.abs(distanceY)
2823             mScrollStartY = mViewStartY
2824             mPreviousDirection = 0
2825             if (absDistanceX > absDistanceY) {
2826                 val slopFactor = if (mScaleGestureDetector.isInProgress()) 20 else 2
2827                 if (absDistanceX > mScaledPagingTouchSlop * slopFactor) {
2828                     mTouchMode = TOUCH_MODE_HSCROLL
2829                     mViewStartX = distanceX
2830                     initNextView(-mViewStartX)
2831                 }
2832             } else {
2833                 mTouchMode = TOUCH_MODE_VSCROLL
2834             }
2835         } else if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
2836             // We are already scrolling horizontally, so check if we
2837             // changed the direction of scrolling so that the other week
2838             // is now visible.
2839             mViewStartX = distanceX
2840             if (distanceX != 0) {
2841                 val direction = if (distanceX > 0) 1 else -1
2842                 if (direction != mPreviousDirection) {
2843                     // The user has switched the direction of scrolling
2844                     // so re-init the next view
2845                     initNextView(-mViewStartX)
2846                     mPreviousDirection = direction
2847                 }
2848             }
2849         }
2850         if (mTouchMode and TOUCH_MODE_VSCROLL != 0) {
2851             // Calculate the top of the visible region in the calendar grid.
2852             // Increasing/decrease this will scroll the calendar grid up/down.
2853             mViewStartY = ((mGestureCenterHour * (mCellHeight + DAY_GAP) -
2854                 focusY) + DAY_HEADER_HEIGHT + mAlldayHeight).toInt()
2855 
2856             // If dragging while already at the end, do a glow
2857             val pulledToY = (mScrollStartY + deltaY).toInt()
2858             if (pulledToY < 0) {
2859                 mEdgeEffectTop.onPull(deltaY / mViewHeight)
2860                 if (!mEdgeEffectBottom.isFinished()) {
2861                     mEdgeEffectBottom.onRelease()
2862                 }
2863             } else if (pulledToY > mMaxViewStartY) {
2864                 mEdgeEffectBottom.onPull(deltaY / mViewHeight)
2865                 if (!mEdgeEffectTop.isFinished()) {
2866                     mEdgeEffectTop.onRelease()
2867                 }
2868             }
2869             if (mViewStartY < 0) {
2870                 mViewStartY = 0
2871                 mRecalCenterHour = true
2872             } else if (mViewStartY > mMaxViewStartY) {
2873                 mViewStartY = mMaxViewStartY
2874                 mRecalCenterHour = true
2875             }
2876             if (mRecalCenterHour) {
2877                 // Calculate the hour that correspond to the average of the Y touch points
2878                 mGestureCenterHour = ((mViewStartY + focusY - DAY_HEADER_HEIGHT - mAlldayHeight) /
2879                     (mCellHeight + DAY_GAP))
2880                 mRecalCenterHour = false
2881             }
2882             computeFirstHour()
2883         }
2884         mScrolling = true
2885         mSelectionMode = SELECTION_HIDDEN
2886         invalidate()
2887     }
2888 
getAverageYnull2889     private fun getAverageY(me: MotionEvent): Float {
2890         val count: Int = me.getPointerCount()
2891         var focusY = 0f
2892         for (i in 0 until count) {
2893             focusY += me.getY(i)
2894         }
2895         focusY /= count.toFloat()
2896         return focusY
2897     }
2898 
cancelAnimationnull2899     private fun cancelAnimation() {
2900         val `in`: Animation? = mViewSwitcher.getInAnimation()
2901         if (`in` != null) {
2902             // cancel() doesn't terminate cleanly.
2903             `in`.scaleCurrentDuration(0f)
2904         }
2905         val out: Animation? = mViewSwitcher.getOutAnimation()
2906         if (out != null) {
2907             // cancel() doesn't terminate cleanly.
2908             out.scaleCurrentDuration(0f)
2909         }
2910     }
2911 
doFlingnull2912     private fun doFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float) {
2913         cancelAnimation()
2914         mSelectionMode = SELECTION_HIDDEN
2915         eventClickCleanup()
2916         mOnFlingCalled = true
2917         if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
2918             // Horizontal fling.
2919             // initNextView(deltaX);
2920             mTouchMode = TOUCH_MODE_INITIAL_STATE
2921             if (DEBUG) Log.d(TAG, "doFling: velocityX $velocityX")
2922             val deltaX = e2.getX().toInt() - e1!!.getX().toInt()
2923             switchViews(deltaX < 0, mViewStartX.toFloat(), mViewWidth.toFloat(), velocityX)
2924             mViewStartX = 0
2925             return
2926         }
2927         if (mTouchMode and TOUCH_MODE_VSCROLL == 0) {
2928             if (DEBUG) Log.d(TAG, "doFling: no fling")
2929             return
2930         }
2931 
2932         // Vertical fling.
2933         mTouchMode = TOUCH_MODE_INITIAL_STATE
2934         mViewStartX = 0
2935         if (DEBUG) {
2936             Log.d(TAG, "doFling: mViewStartY$mViewStartY velocityY $velocityY")
2937         }
2938 
2939         // Continue scrolling vertically
2940         mScrolling = true
2941         mScroller.fling(
2942             0 /* startX */, mViewStartY /* startY */, 0 /* velocityX */,
2943             (-velocityY).toInt(), 0 /* minX */, 0 /* maxX */, 0 /* minY */,
2944             mMaxViewStartY /* maxY */, OVERFLING_DISTANCE, OVERFLING_DISTANCE
2945         )
2946 
2947         // When flinging down, show a glow when it hits the end only if it
2948         // wasn't started at the top
2949         if (velocityY > 0 && mViewStartY != 0) {
2950             mCallEdgeEffectOnAbsorb = true
2951         } else if (velocityY < 0 && mViewStartY != mMaxViewStartY) {
2952             mCallEdgeEffectOnAbsorb = true
2953         }
2954         mHandler?.post(mContinueScroll)
2955     }
2956 
initNextViewnull2957     private fun initNextView(deltaX: Int): Boolean {
2958         // Change the view to the previous day or week
2959         val view = mViewSwitcher.getNextView() as DayView
2960         val date: Time? = view.mBaseDate
2961         date?.set(mBaseDate)
2962         val switchForward: Boolean
2963         if (deltaX > 0) {
2964             date!!.monthDay -= mNumDays
2965             view.setSelectedDay(mSelectionDay - mNumDays)
2966             switchForward = false
2967         } else {
2968             date!!.monthDay += mNumDays
2969             view.setSelectedDay(mSelectionDay + mNumDays)
2970             switchForward = true
2971         }
2972         date?.normalize(true /* ignore isDst */)
2973         initView(view)
2974         view.layout(getLeft(), getTop(), getRight(), getBottom())
2975         view.reloadEvents()
2976         return switchForward
2977     }
2978 
2979     // ScaleGestureDetector.OnScaleGestureListener
onScaleBeginnull2980     override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
2981         mHandleActionUp = false
2982         val gestureCenterInPixels: Float = detector.getFocusY() - DAY_HEADER_HEIGHT - mAlldayHeight
2983         mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP)
2984         mStartingSpanY = Math.max(MIN_Y_SPAN.toFloat(),
2985             Math.abs(detector.getCurrentSpanY().toFloat()))
2986         mCellHeightBeforeScaleGesture = mCellHeight
2987         if (DEBUG_SCALING) {
2988             val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
2989             Log.d(
2990                 TAG, "onScaleBegin: mGestureCenterHour:" + mGestureCenterHour +
2991                     "\tViewStartHour: " + ViewStartHour + "\tmViewStartY:" + mViewStartY +
2992                     "\tmCellHeight:" + mCellHeight + " SpanY:" + detector.getCurrentSpanY()
2993             )
2994         }
2995         return true
2996     }
2997 
2998     // ScaleGestureDetector.OnScaleGestureListener
onScalenull2999     override fun onScale(detector: ScaleGestureDetector): Boolean {
3000         val spanY: Float = Math.max(MIN_Y_SPAN.toFloat(),
3001             Math.abs(detector.getCurrentSpanY().toFloat()))
3002         mCellHeight = (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY).toInt()
3003         if (mCellHeight < mMinCellHeight) {
3004             // If mStartingSpanY is too small, even a small increase in the
3005             // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
3006             mStartingSpanY = spanY
3007             mCellHeight = mMinCellHeight
3008             mCellHeightBeforeScaleGesture = mMinCellHeight
3009         } else if (mCellHeight > MAX_CELL_HEIGHT) {
3010             mStartingSpanY = spanY
3011             mCellHeight = MAX_CELL_HEIGHT
3012             mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT
3013         }
3014         val gestureCenterInPixels = detector.getFocusY().toInt() - DAY_HEADER_HEIGHT - mAlldayHeight
3015         mViewStartY = (mGestureCenterHour * (mCellHeight + DAY_GAP)).toInt() - gestureCenterInPixels
3016         mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight
3017         if (DEBUG_SCALING) {
3018             val ViewStartHour = mViewStartY / (mCellHeight + DAY_GAP).toFloat()
3019             Log.d(
3020                 TAG, "onScale: mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: " +
3021                     ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:" +
3022                     mCellHeight + " SpanY:" + detector.getCurrentSpanY()
3023             )
3024         }
3025         if (mViewStartY < 0) {
3026             mViewStartY = 0
3027             mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
3028                 (mCellHeight + DAY_GAP).toFloat())
3029         } else if (mViewStartY > mMaxViewStartY) {
3030             mViewStartY = mMaxViewStartY
3031             mGestureCenterHour = ((mViewStartY + gestureCenterInPixels) /
3032                 (mCellHeight + DAY_GAP).toFloat())
3033         }
3034         computeFirstHour()
3035         mRemeasure = true
3036         invalidate()
3037         return true
3038     }
3039 
3040     // ScaleGestureDetector.OnScaleGestureListener
onScaleEndnull3041     override fun onScaleEnd(detector: ScaleGestureDetector) {
3042         mScrollStartY = mViewStartY
3043         mInitialScrollY = 0f
3044         mInitialScrollX = 0f
3045         mStartingSpanY = 0f
3046     }
3047 
3048     @Override
onTouchEventnull3049     override fun onTouchEvent(ev: MotionEvent): Boolean {
3050         val action: Int = ev.getAction()
3051         if (DEBUG) Log.e(TAG, "" + action + " ev.getPointerCount() = " + ev.getPointerCount())
3052         if (ev.getActionMasked() === MotionEvent.ACTION_DOWN ||
3053             ev.getActionMasked() === MotionEvent.ACTION_UP ||
3054             ev.getActionMasked() === MotionEvent.ACTION_POINTER_UP ||
3055             ev.getActionMasked() === MotionEvent.ACTION_POINTER_DOWN
3056         ) {
3057             mRecalCenterHour = true
3058         }
3059         if (mTouchMode and TOUCH_MODE_HSCROLL == 0) {
3060             mScaleGestureDetector.onTouchEvent(ev)
3061         }
3062         return when (action) {
3063             MotionEvent.ACTION_DOWN -> {
3064                 mStartingScroll = true
3065                 if (DEBUG) {
3066                     Log.e(
3067                         TAG,
3068                         "ACTION_DOWN ev.getDownTime = " + ev.getDownTime().toString() + " Cnt=" +
3069                             ev.getPointerCount()
3070                     )
3071                 }
3072                 val bottom =
3073                     mAlldayHeight + DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
3074                 mTouchStartedInAlldayArea = if (ev.getY() < bottom) {
3075                     true
3076                 } else {
3077                     false
3078                 }
3079                 mHandleActionUp = true
3080                 mGestureDetector.onTouchEvent(ev)
3081                 true
3082             }
3083             MotionEvent.ACTION_MOVE -> {
3084                 if (DEBUG) Log.e(
3085                     TAG,
3086                     "ACTION_MOVE Cnt=" + ev.getPointerCount() + this@DayView
3087                 )
3088                 mGestureDetector.onTouchEvent(ev)
3089                 true
3090             }
3091             MotionEvent.ACTION_UP -> {
3092                 if (DEBUG) Log.e(
3093                     TAG,
3094                     "ACTION_UP Cnt=" + ev.getPointerCount() + mHandleActionUp
3095                 )
3096                 mEdgeEffectTop.onRelease()
3097                 mEdgeEffectBottom.onRelease()
3098                 mStartingScroll = false
3099                 mGestureDetector.onTouchEvent(ev)
3100                 if (!mHandleActionUp) {
3101                     mHandleActionUp = true
3102                     mViewStartX = 0
3103                     invalidate()
3104                     return true
3105                 }
3106                 if (mOnFlingCalled) {
3107                     return true
3108                 }
3109 
3110                 // If we were scrolling, then reset the selected hour so that it
3111                 // is visible.
3112                 if (mScrolling) {
3113                     mScrolling = false
3114                     resetSelectedHour()
3115                     invalidate()
3116                 }
3117                 if (mTouchMode and TOUCH_MODE_HSCROLL != 0) {
3118                     mTouchMode = TOUCH_MODE_INITIAL_STATE
3119                     if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
3120                         // The user has gone beyond the threshold so switch views
3121                         if (DEBUG) Log.d(
3122                             TAG,
3123                             "- horizontal scroll: switch views"
3124                         )
3125                         switchViews(
3126                             mViewStartX > 0,
3127                             mViewStartX.toFloat(),
3128                             mViewWidth.toFloat(),
3129                             0f
3130                         )
3131                         mViewStartX = 0
3132                         return true
3133                     } else {
3134                         // Not beyond the threshold so invalidate which will cause
3135                         // the view to snap back. Also call recalc() to ensure
3136                         // that we have the correct starting date and title.
3137                         if (DEBUG) Log.d(
3138                             TAG,
3139                             "- horizontal scroll: snap back"
3140                         )
3141                         recalc()
3142                         invalidate()
3143                         mViewStartX = 0
3144                     }
3145                 }
3146                 true
3147             }
3148             MotionEvent.ACTION_CANCEL -> {
3149                 if (DEBUG) Log.e(
3150                     TAG,
3151                     "ACTION_CANCEL"
3152                 )
3153                 mGestureDetector.onTouchEvent(ev)
3154                 mScrolling = false
3155                 resetSelectedHour()
3156                 true
3157             }
3158             else -> {
3159                 if (DEBUG) Log.e(
3160                     TAG,
3161                     "Not MotionEvent " + ev.toString()
3162                 )
3163                 if (mGestureDetector.onTouchEvent(ev)) {
3164                     true
3165                 } else super.onTouchEvent(ev)
3166             }
3167         }
3168     }
3169 
onCreateContextMenunull3170     override fun onCreateContextMenu(menu: ContextMenu, view: View?, menuInfo: ContextMenuInfo?) {
3171         var item: MenuItem
3172 
3173         // If the trackball is held down, then the context menu pops up and
3174         // we never get onKeyUp() for the long-press. So check for it here
3175         // and change the selection to the long-press state.
3176         if (mSelectionMode != SELECTION_LONGPRESS) {
3177             invalidate()
3178         }
3179         val startMillis = selectedTimeInMillis
3180         val flags: Int = (DateUtils.FORMAT_SHOW_TIME
3181             or DateUtils.FORMAT_CAP_NOON_MIDNIGHT
3182             or DateUtils.FORMAT_SHOW_WEEKDAY)
3183         val title: String? = Utils.formatDateRange(mContext, startMillis, startMillis, flags)
3184         menu.setHeaderTitle(title)
3185         mPopup?.dismiss()
3186     }
3187 
3188     /**
3189      * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
3190      * If the touch position is not within the displayed grid, then this
3191      * method returns false.
3192      *
3193      * @param x the x position of the touch
3194      * @param y the y position of the touch
3195      * @param keepOldSelection - do not change the selection info (used for invoking accessibility
3196      * messages)
3197      * @return true if the touch position is valid
3198      */
setSelectionFromPositionnull3199     private fun setSelectionFromPosition(x: Int, y: Int, keepOldSelection: Boolean): Boolean {
3200         var x = x
3201         var savedEvent: Event? = null
3202         var savedDay = 0
3203         var savedHour = 0
3204         var savedAllDay = false
3205         if (keepOldSelection) {
3206             // Store selection info and restore it at the end. This way, we can invoke the
3207             // right accessibility message without affecting the selection.
3208             savedEvent = mSelectedEvent
3209             savedDay = mSelectionDay
3210             savedHour = mSelectionHour
3211             savedAllDay = mSelectionAllday
3212         }
3213         if (x < mHoursWidth) {
3214             x = mHoursWidth
3215         }
3216         var day = (x - mHoursWidth) / (mCellWidth + DAY_GAP)
3217         if (day >= mNumDays) {
3218             day = mNumDays - 1
3219         }
3220         day += mFirstJulianDay
3221         setSelectedDay(day)
3222         if (y < DAY_HEADER_HEIGHT) {
3223             sendAccessibilityEventAsNeeded(false)
3224             return false
3225         }
3226         setSelectedHour(mFirstHour) /* First fully visible hour */
3227         mSelectionAllday = if (y < mFirstCell) {
3228             true
3229         } else {
3230             // y is now offset from top of the scrollable region
3231             val adjustedY = y - mFirstCell
3232             if (adjustedY < mFirstHourOffset) {
3233                 setSelectedHour(mSelectionHour - 1) /* In the partially visible hour */
3234             } else {
3235                 setSelectedHour(
3236                     mSelectionHour +
3237                         (adjustedY - mFirstHourOffset) / (mCellHeight + HOUR_GAP)
3238                 )
3239             }
3240             false
3241         }
3242         findSelectedEvent(x, y)
3243         sendAccessibilityEventAsNeeded(true)
3244 
3245         // Restore old values
3246         if (keepOldSelection) {
3247             mSelectedEvent = savedEvent
3248             mSelectionDay = savedDay
3249             mSelectionHour = savedHour
3250             mSelectionAllday = savedAllDay
3251         }
3252         return true
3253     }
3254 
findSelectedEventnull3255     private fun findSelectedEvent(x: Int, y: Int) {
3256         var y = y
3257         val date = mSelectionDay
3258         val cellWidth = mCellWidth
3259         var events: ArrayList<Event>? = mEvents
3260         var numEvents: Int = events!!.size
3261         val left = computeDayLeftPosition(mSelectionDay - mFirstJulianDay)
3262         val top = 0
3263         setSelectedEvent(null)
3264         mSelectedEvents.clear()
3265         if (mSelectionAllday) {
3266             var yDistance: Float
3267             var minYdistance = 10000.0f // any large number
3268             var closestEvent: Event? = null
3269             val drawHeight = mAlldayHeight.toFloat()
3270             val yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN
3271             var maxUnexpandedColumn = mMaxUnexpandedAlldayEventCount
3272             if (mMaxAlldayEvents > mMaxUnexpandedAlldayEventCount) {
3273                 // Leave a gap for the 'box +n' text
3274                 maxUnexpandedColumn--
3275             }
3276             events = mAllDayEvents
3277             numEvents = events!!.size
3278             for (i in 0 until numEvents) {
3279                 val event: Event? = events.get(i)
3280                 if (!event!!.drawAsAllday() ||
3281                     !mShowAllAllDayEvents && event.getColumn() >= maxUnexpandedColumn
3282                 ) {
3283                     // Don't check non-allday events or events that aren't shown
3284                     continue
3285                 }
3286                 if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
3287                     val numRectangles =
3288                         if (mShowAllAllDayEvents) mMaxAlldayEvents.toFloat()
3289                         else mMaxUnexpandedAlldayEventCount.toFloat()
3290                     var height = drawHeight / numRectangles
3291                     if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
3292                         height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT.toFloat()
3293                     }
3294                     val eventTop: Float = yOffset + height * event.getColumn()
3295                     val eventBottom = eventTop + height
3296                     if (eventTop < y && eventBottom > y) {
3297                         // If the touch is inside the event rectangle, then
3298                         // add the event.
3299                         mSelectedEvents.add(event)
3300                         closestEvent = event
3301                         break
3302                     } else {
3303                         // Find the closest event
3304                         yDistance = if (eventTop >= y) {
3305                             eventTop - y
3306                         } else {
3307                             y - eventBottom
3308                         }
3309                         if (yDistance < minYdistance) {
3310                             minYdistance = yDistance
3311                             closestEvent = event
3312                         }
3313                     }
3314                 }
3315             }
3316             setSelectedEvent(closestEvent)
3317             return
3318         }
3319 
3320         // Adjust y for the scrollable bitmap
3321         y += mViewStartY - mFirstCell
3322 
3323         // Use a region around (x,y) for the selection region
3324         val region: Rect = mRect
3325         region.left = x - 10
3326         region.right = x + 10
3327         region.top = y - 10
3328         region.bottom = y + 10
3329         val geometry: EventGeometry = mEventGeometry
3330         for (i in 0 until numEvents) {
3331             val event: Event? = events.get(i)
3332             // Compute the event rectangle.
3333             if (!geometry.computeEventRect(date, left, top, cellWidth, event as Event)) {
3334                 continue
3335             }
3336 
3337             // If the event intersects the selection region, then add it to
3338             // mSelectedEvents.
3339             if (geometry.eventIntersectsSelection(event as Event, region)) {
3340                 mSelectedEvents.add(event as Event)
3341             }
3342         }
3343 
3344         // If there are any events in the selected region, then assign the
3345         // closest one to mSelectedEvent.
3346         if (mSelectedEvents.size > 0) {
3347             val len: Int = mSelectedEvents.size
3348             var closestEvent: Event? = null
3349             var minDist = (mViewWidth + mViewHeight).toFloat() // some large distance
3350             for (index in 0 until len) {
3351                 val ev: Event? = mSelectedEvents.get(index)
3352                 val dist: Float = geometry.pointToEvent(x.toFloat(), y.toFloat(), ev as Event)
3353                 if (dist < minDist) {
3354                     minDist = dist
3355                     closestEvent = ev
3356                 }
3357             }
3358             setSelectedEvent(closestEvent)
3359 
3360             // Keep the selected hour and day consistent with the selected
3361             // event. They could be different if we touched on an empty hour
3362             // slot very close to an event in the previous hour slot. In
3363             // that case we will select the nearby event.
3364             val startDay: Int = mSelectedEvent!!.startDay
3365             val endDay: Int = mSelectedEvent!!.endDay
3366             if (mSelectionDay < startDay) {
3367                 setSelectedDay(startDay)
3368             } else if (mSelectionDay > endDay) {
3369                 setSelectedDay(endDay)
3370             }
3371             val startHour: Int = mSelectedEvent!!.startTime / 60
3372             val endHour: Int
3373             endHour = if (mSelectedEvent!!.startTime < mSelectedEvent!!.endTime) {
3374                 (mSelectedEvent!!.endTime - 1) / 60
3375             } else {
3376                 mSelectedEvent!!.endTime / 60
3377             }
3378             if (mSelectionHour < startHour && mSelectionDay == startDay) {
3379                 setSelectedHour(startHour)
3380             } else if (mSelectionHour > endHour && mSelectionDay == endDay) {
3381                 setSelectedHour(endHour)
3382             }
3383         }
3384     }
3385 
3386     // Encapsulates the code to continue the scrolling after the
3387     // finger is lifted. Instead of stopping the scroll immediately,
3388     // the scroll continues to "free spin" and gradually slows down.
3389     private inner class ContinueScroll : Runnable {
runnull3390         override fun run() {
3391             mScrolling = mScrolling && mScroller.computeScrollOffset()
3392             if (!mScrolling || mPaused) {
3393                 resetSelectedHour()
3394                 invalidate()
3395                 return
3396             }
3397             mViewStartY = mScroller.getCurrY()
3398             if (mCallEdgeEffectOnAbsorb) {
3399                 if (mViewStartY < 0) {
3400                     mEdgeEffectTop.onAbsorb(mLastVelocity.toInt())
3401                     mCallEdgeEffectOnAbsorb = false
3402                 } else if (mViewStartY > mMaxViewStartY) {
3403                     mEdgeEffectBottom.onAbsorb(mLastVelocity.toInt())
3404                     mCallEdgeEffectOnAbsorb = false
3405                 }
3406                 mLastVelocity = mScroller.getCurrVelocity()
3407             }
3408             if (mScrollStartY == 0 || mScrollStartY == mMaxViewStartY) {
3409                 // Allow overscroll/springback only on a fling,
3410                 // not a pull/fling from the end
3411                 if (mViewStartY < 0) {
3412                     mViewStartY = 0
3413                 } else if (mViewStartY > mMaxViewStartY) {
3414                     mViewStartY = mMaxViewStartY
3415                 }
3416             }
3417             computeFirstHour()
3418             mHandler?.post(this)
3419             invalidate()
3420         }
3421     }
3422 
3423     /**
3424      * Cleanup the pop-up and timers.
3425      */
cleanupnull3426     fun cleanup() {
3427         // Protect against null-pointer exceptions
3428         if (mPopup != null) {
3429             mPopup?.dismiss()
3430         }
3431         mPaused = true
3432         mLastPopupEventID = INVALID_EVENT_ID
3433         if (mHandler != null) {
3434             mHandler?.removeCallbacks(mDismissPopup)
3435             mHandler?.removeCallbacks(mUpdateCurrentTime)
3436         }
3437         Utils.setSharedPreference(
3438             mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
3439             mCellHeight
3440         )
3441         // Clear all click animations
3442         eventClickCleanup()
3443         // Turn off redraw
3444         mRemeasure = false
3445         // Turn off scrolling to make sure the view is in the correct state if we fling back to it
3446         mScrolling = false
3447     }
3448 
eventClickCleanupnull3449     private fun eventClickCleanup() {
3450         this.removeCallbacks(mClearClick)
3451         this.removeCallbacks(mSetClick)
3452         mClickedEvent = null
3453         mSavedClickedEvent = null
3454     }
3455 
setSelectedEventnull3456     private fun setSelectedEvent(e: Event?) {
3457         mSelectedEvent = e
3458         mSelectedEventForAccessibility = e
3459     }
3460 
setSelectedHournull3461     private fun setSelectedHour(h: Int) {
3462         mSelectionHour = h
3463         mSelectionHourForAccessibility = h
3464     }
3465 
setSelectedDaynull3466     private fun setSelectedDay(d: Int) {
3467         mSelectionDay = d
3468         mSelectionDayForAccessibility = d
3469     }
3470 
3471     /**
3472      * Restart the update timer
3473      */
restartCurrentTimeUpdatesnull3474     fun restartCurrentTimeUpdates() {
3475         mPaused = false
3476         if (mHandler != null) {
3477             mHandler?.removeCallbacks(mUpdateCurrentTime)
3478             mHandler?.post(mUpdateCurrentTime)
3479         }
3480     }
3481 
3482     @Override
onDetachedFromWindownull3483     protected override fun onDetachedFromWindow() {
3484         cleanup()
3485         super.onDetachedFromWindow()
3486     }
3487 
3488     internal inner class DismissPopup : Runnable {
runnull3489         override fun run() {
3490             // Protect against null-pointer exceptions
3491             if (mPopup != null) {
3492                 mPopup?.dismiss()
3493             }
3494         }
3495     }
3496 
3497     internal inner class UpdateCurrentTime : Runnable {
runnull3498         override fun run() {
3499             val currentTime: Long = System.currentTimeMillis()
3500             mCurrentTime?.set(currentTime)
3501             // % causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
3502             if (!mPaused) {
3503                 mHandler?.postDelayed(
3504                     mUpdateCurrentTime, UPDATE_CURRENT_TIME_DELAY -
3505                         currentTime % UPDATE_CURRENT_TIME_DELAY
3506                 )
3507             }
3508             mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime!!.gmtoff)
3509             invalidate()
3510         }
3511     }
3512 
3513     internal inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
3514         @Override
onSingleTapUpnull3515         override fun onSingleTapUp(ev: MotionEvent): Boolean {
3516             if (DEBUG) Log.e(TAG, "GestureDetector.onSingleTapUp")
3517             doSingleTapUp(ev)
3518             return true
3519         }
3520 
3521         @Override
onLongPressnull3522         override fun onLongPress(ev: MotionEvent) {
3523             if (DEBUG) Log.e(TAG, "GestureDetector.onLongPress")
3524             doLongPress(ev)
3525         }
3526 
3527         @Override
onScrollnull3528         override fun onScroll(
3529             e1: MotionEvent?,
3530             e2: MotionEvent,
3531             distanceX: Float,
3532             distanceY: Float
3533         ): Boolean {
3534             var distanceY = distanceY
3535             if (DEBUG) Log.e(TAG, "GestureDetector.onScroll")
3536             eventClickCleanup()
3537             if (mTouchStartedInAlldayArea) {
3538                 if (Math.abs(distanceX) < Math.abs(distanceY)) {
3539                     // Make sure that click feedback is gone when you scroll from the
3540                     // all day area
3541                     invalidate()
3542                     return false
3543                 }
3544                 // don't scroll vertically if this started in the allday area
3545                 distanceY = 0f
3546             }
3547             doScroll(e1, e2, distanceX, distanceY)
3548             return true
3549         }
3550 
3551         @Override
onFlingnull3552         override fun onFling(
3553             e1: MotionEvent?,
3554             e2: MotionEvent,
3555             velocityX: Float,
3556             velocityY: Float
3557         ): Boolean {
3558             var velocityY = velocityY
3559             if (DEBUG) Log.e(TAG, "GestureDetector.onFling")
3560             if (mTouchStartedInAlldayArea) {
3561                 if (Math.abs(velocityX) < Math.abs(velocityY)) {
3562                     return false
3563                 }
3564                 // don't fling vertically if this started in the allday area
3565                 velocityY = 0f
3566             }
3567             doFling(e1, e2, velocityX, velocityY)
3568             return true
3569         }
3570 
3571         @Override
onDownnull3572         override fun onDown(ev: MotionEvent): Boolean {
3573             if (DEBUG) Log.e(TAG, "GestureDetector.onDown")
3574             doDown(ev)
3575             return true
3576         }
3577     }
3578 
3579     @Override
onLongClicknull3580     override fun onLongClick(v: View?): Boolean {
3581         return true
3582     }
3583 
3584     private inner class ScrollInterpolator : Interpolator {
getInterpolationnull3585         override fun getInterpolation(t: Float): Float {
3586             var t = t
3587             t -= 1.0f
3588             t = t * t * t * t * t + 1
3589             if ((1 - t) * mAnimationDistance < 1) {
3590                 cancelAnimation()
3591             }
3592             return t
3593         }
3594     }
3595 
calculateDurationnull3596     private fun calculateDuration(delta: Float, width: Float, velocity: Float): Long {
3597         /*
3598          * Here we compute a "distance" that will be used in the computation of
3599          * the overall snap duration. This is a function of the actual distance
3600          * that needs to be traveled; we keep this value close to half screen
3601          * size in order to reduce the variance in snap duration as a function
3602          * of the distance the page needs to travel.
3603          */
3604         var velocity = velocity
3605         val halfScreenSize = width / 2
3606         val distanceRatio = delta / width
3607         val distanceInfluenceForSnapDuration = distanceInfluenceForSnapDuration(distanceRatio)
3608         val distance = halfScreenSize + halfScreenSize * distanceInfluenceForSnapDuration
3609         velocity = Math.abs(velocity)
3610         velocity = Math.max(MINIMUM_SNAP_VELOCITY.toFloat(), velocity)
3611 
3612         /*
3613          * we want the page's snap velocity to approximately match the velocity
3614          * at which the user flings, so we scale the duration by a value near to
3615          * the derivative of the scroll interpolator at zero, ie. 5. We use 6 to
3616          * make it a little slower.
3617          */
3618         val duration: Long = 6L * Math.round(1000 * Math.abs(distance / velocity))
3619         if (DEBUG) {
3620             Log.e(
3621                 TAG, "halfScreenSize:" + halfScreenSize + " delta:" + delta + " distanceRatio:" +
3622                     distanceRatio + " distance:" + distance + " velocity:" + velocity +
3623                     " duration:" + duration + " distanceInfluenceForSnapDuration:" +
3624                     distanceInfluenceForSnapDuration
3625             )
3626         }
3627         return duration
3628     }
3629 
3630     /*
3631      * We want the duration of the page snap animation to be influenced by the
3632      * distance that the screen has to travel, however, we don't want this
3633      * duration to be effected in a purely linear fashion. Instead, we use this
3634      * method to moderate the effect that the distance of travel has on the
3635      * overall snap duration.
3636      */
distanceInfluenceForSnapDurationnull3637     private fun distanceInfluenceForSnapDuration(f: Float): Float {
3638         var f = f
3639         f -= 0.5f // center the values about 0.
3640         f *= (0.3f * Math.PI / 2.0f).toFloat()
3641         return Math.sin(f.toDouble()).toFloat()
3642     }
3643 
3644     companion object {
3645         private const val TAG = "DayView"
3646         private const val DEBUG = false
3647         private const val DEBUG_SCALING = false
3648         private const val PERIOD_SPACE = ". "
3649         private var mScale = 0f // Used for supporting different screen densities
3650         private const val INVALID_EVENT_ID: Long = -1 // This is used for remembering a null event
3651 
3652         // Duration of the allday expansion
3653         private const val ANIMATION_DURATION: Long = 400
3654 
3655         // duration of the more allday event text fade
3656         private const val ANIMATION_SECONDARY_DURATION: Long = 200
3657 
3658         // duration of the scroll to go to a specified time
3659         private const val GOTO_SCROLL_DURATION = 200
3660 
3661         // duration for events' cross-fade animation
3662         private const val EVENTS_CROSS_FADE_DURATION = 400
3663 
3664         // duration to show the event clicked
3665         private const val CLICK_DISPLAY_DURATION = 50
3666         private const val MENU_DAY = 3
3667         private const val MENU_EVENT_VIEW = 5
3668         private const val MENU_EVENT_CREATE = 6
3669         private const val MENU_EVENT_EDIT = 7
3670         private const val MENU_EVENT_DELETE = 8
3671         private var DEFAULT_CELL_HEIGHT = 64
3672         private var MAX_CELL_HEIGHT = 150
3673         private var MIN_Y_SPAN = 100
3674         private val CALENDARS_PROJECTION = arrayOf<String>(
3675             Calendars._ID, // 0
3676             Calendars.CALENDAR_ACCESS_LEVEL, // 1
3677             Calendars.OWNER_ACCOUNT
3678         )
3679         private const val CALENDARS_INDEX_ACCESS_LEVEL = 1
3680         private const val CALENDARS_INDEX_OWNER_ACCOUNT = 2
3681         private val CALENDARS_WHERE: String = Calendars._ID.toString() + "=%d"
3682         private const val FROM_NONE = 0
3683         private const val FROM_ABOVE = 1
3684         private const val FROM_BELOW = 2
3685         private const val FROM_LEFT = 4
3686         private const val FROM_RIGHT = 8
3687         private const val ACCESS_LEVEL_NONE = 0
3688         private const val ACCESS_LEVEL_DELETE = 1
3689         private const val ACCESS_LEVEL_EDIT = 2
3690         private var mHorizontalSnapBackThreshold = 128
3691 
3692         // Update the current time line every five minutes if the window is left open that long
3693         private const val UPDATE_CURRENT_TIME_DELAY = 300000
3694         private var mOnDownDelay = 0
3695         protected var mStringBuilder: StringBuilder = StringBuilder(50)
3696 
3697         // TODO recreate formatter when locale changes
3698         protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault())
3699 
3700         // The number of milliseconds to show the popup window
3701         private const val POPUP_DISMISS_DELAY = 3000
3702         private var GRID_LINE_LEFT_MARGIN = 0f
3703         private const val GRID_LINE_INNER_WIDTH = 1f
3704         private const val DAY_GAP = 1
3705         private const val HOUR_GAP = 1
3706 
3707         // This is the standard height of an allday event with no restrictions
3708         private var SINGLE_ALLDAY_HEIGHT = 34
3709 
3710         /**
3711          * This is the minimum desired height of a allday event.
3712          * When unexpanded, allday events will use this height.
3713          * When expanded allDay events will attempt to grow to fit all
3714          * events at this height.
3715          */
3716         private var MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = 28.0f // in pixels
3717 
3718         /**
3719          * This is how big the unexpanded allday height is allowed to be.
3720          * It will get adjusted based on screen size
3721          */
3722         private var MAX_UNEXPANDED_ALLDAY_HEIGHT = (MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT * 4).toInt()
3723 
3724         /**
3725          * This is the minimum size reserved for displaying regular events.
3726          * The expanded allDay region can't expand into this.
3727          */
3728         private const val MIN_HOURS_HEIGHT = 180
3729         private var ALLDAY_TOP_MARGIN = 1
3730 
3731         // The largest a single allDay event will become.
3732         private var MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34
3733         private var HOURS_TOP_MARGIN = 2
3734         private var HOURS_LEFT_MARGIN = 2
3735         private var HOURS_RIGHT_MARGIN = 4
3736         private var HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
3737         private var NEW_EVENT_MARGIN = 4
3738         private var NEW_EVENT_WIDTH = 2
3739         private var NEW_EVENT_MAX_LENGTH = 16
3740         private var CURRENT_TIME_LINE_SIDE_BUFFER = 4
3741         private var CURRENT_TIME_LINE_TOP_OFFSET = 2
3742 
3743         /* package */
3744         const val MINUTES_PER_HOUR = 60
3745 
3746         /* package */
3747         const val MINUTES_PER_DAY = MINUTES_PER_HOUR * 24
3748 
3749         /* package */
3750         const val MILLIS_PER_MINUTE = 60 * 1000
3751 
3752         /* package */
3753         const val MILLIS_PER_HOUR = 3600 * 1000
3754 
3755         /* package */
3756         const val MILLIS_PER_DAY = MILLIS_PER_HOUR * 24
3757 
3758         // More events text will transition between invisible and this alpha
3759         private const val MORE_EVENTS_MAX_ALPHA = 0x4C
3760         private var DAY_HEADER_ONE_DAY_LEFT_MARGIN = 0
3761         private var DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5
3762         private var DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6
3763         private var DAY_HEADER_RIGHT_MARGIN = 4
3764         private var DAY_HEADER_BOTTOM_MARGIN = 3
3765         private var DAY_HEADER_FONT_SIZE = 14f
3766         private var DATE_HEADER_FONT_SIZE = 32f
3767         private var NORMAL_FONT_SIZE = 12f
3768         private var EVENT_TEXT_FONT_SIZE = 12f
3769         private var HOURS_TEXT_SIZE = 12f
3770         private var AMPM_TEXT_SIZE = 9f
3771         private var MIN_HOURS_WIDTH = 96
3772         private var MIN_CELL_WIDTH_FOR_TEXT = 20
3773         private const val MAX_EVENT_TEXT_LEN = 500
3774 
3775         // smallest height to draw an event with
3776         private var MIN_EVENT_HEIGHT = 24.0f // in pixels
3777         private var CALENDAR_COLOR_SQUARE_SIZE = 10
3778         private var EVENT_RECT_TOP_MARGIN = 1
3779         private var EVENT_RECT_BOTTOM_MARGIN = 0
3780         private var EVENT_RECT_LEFT_MARGIN = 1
3781         private var EVENT_RECT_RIGHT_MARGIN = 0
3782         private var EVENT_RECT_STROKE_WIDTH = 2
3783         private var EVENT_TEXT_TOP_MARGIN = 2
3784         private var EVENT_TEXT_BOTTOM_MARGIN = 2
3785         private var EVENT_TEXT_LEFT_MARGIN = 6
3786         private var EVENT_TEXT_RIGHT_MARGIN = 6
3787         private var ALL_DAY_EVENT_RECT_BOTTOM_MARGIN = 1
3788         private var EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
3789         private var EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN
3790         private var EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
3791         private var EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN
3792 
3793         // margins and sizing for the expand allday icon
3794         private var EXPAND_ALL_DAY_BOTTOM_MARGIN = 10
3795 
3796         // sizing for "box +n" in allDay events
3797         private var EVENT_SQUARE_WIDTH = 10
3798         private var EVENT_LINE_PADDING = 4
3799         private var NEW_EVENT_HINT_FONT_SIZE = 12
3800         private var mEventTextColor = 0
3801         private var mMoreEventsTextColor = 0
3802         private var mWeek_saturdayColor = 0
3803         private var mWeek_sundayColor = 0
3804         private var mCalendarDateBannerTextColor = 0
3805         private var mCalendarAmPmLabel = 0
3806         private var mCalendarGridAreaSelected = 0
3807         private var mCalendarGridLineInnerHorizontalColor = 0
3808         private var mCalendarGridLineInnerVerticalColor = 0
3809         private var mFutureBgColor = 0
3810         private var mFutureBgColorRes = 0
3811         private var mBgColor = 0
3812         private var mNewEventHintColor = 0
3813         private var mCalendarHourLabelColor = 0
3814         private var mMoreAlldayEventsTextAlpha = MORE_EVENTS_MAX_ALPHA
3815         private var mCellHeight = 0 // shared among all DayViews
3816         private var mMinCellHeight = 32
3817         private var mScaledPagingTouchSlop = 0
3818 
3819         /**
3820          * Whether to use the expand or collapse icon.
3821          */
3822         private var mUseExpandIcon = true
3823 
3824         /**
3825          * The height of the day names/numbers
3826          */
3827         private var DAY_HEADER_HEIGHT = 45
3828 
3829         /**
3830          * The height of the day names/numbers for multi-day views
3831          */
3832         private var MULTI_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
3833 
3834         /**
3835          * The height of the day names/numbers when viewing a single day
3836          */
3837         private var ONE_DAY_HEADER_HEIGHT = DAY_HEADER_HEIGHT
3838 
3839         /**
3840          * Whether or not to expand the allDay area to fill the screen
3841          */
3842         private var mShowAllAllDayEvents = false
3843         private var sCounter = 0
3844 
3845         /**
3846          * The initial state of the touch mode when we enter this view.
3847          */
3848         private const val TOUCH_MODE_INITIAL_STATE = 0
3849 
3850         /**
3851          * Indicates we just received the touch event and we are waiting to see if
3852          * it is a tap or a scroll gesture.
3853          */
3854         private const val TOUCH_MODE_DOWN = 1
3855 
3856         /**
3857          * Indicates the touch gesture is a vertical scroll
3858          */
3859         private const val TOUCH_MODE_VSCROLL = 0x20
3860 
3861         /**
3862          * Indicates the touch gesture is a horizontal scroll
3863          */
3864         private const val TOUCH_MODE_HSCROLL = 0x40
3865 
3866         /**
3867          * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
3868          */
3869         private const val SELECTION_HIDDEN = 0
3870         private const val SELECTION_PRESSED = 1 // D-pad down but not up yet
3871         private const val SELECTION_SELECTED = 2
3872         private const val SELECTION_LONGPRESS = 3
3873 
3874         // The rest of this file was borrowed from Launcher2 - PagedView.java
3875         private const val MINIMUM_SNAP_VELOCITY = 2200
3876     }
3877 
3878     init {
3879         mContext = context
3880         initAccessibilityVariables()
3881         mResources = context!!.getResources()
3882         mNewEventHintString = mResources.getString(R.string.day_view_new_event_hint)
3883         mNumDays = numDays
3884         DATE_HEADER_FONT_SIZE =
3885             mResources.getDimension(R.dimen.date_header_text_size).toInt().toFloat()
3886         DAY_HEADER_FONT_SIZE =
3887             mResources.getDimension(R.dimen.day_label_text_size).toInt().toFloat()
3888         ONE_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.one_day_header_height).toInt()
3889         DAY_HEADER_BOTTOM_MARGIN = mResources.getDimension(R.dimen.day_header_bottom_margin).toInt()
3890         EXPAND_ALL_DAY_BOTTOM_MARGIN =
3891             mResources.getDimension(R.dimen.all_day_bottom_margin).toInt()
3892         HOURS_TEXT_SIZE = mResources.getDimension(R.dimen.hours_text_size).toInt().toFloat()
3893         AMPM_TEXT_SIZE = mResources.getDimension(R.dimen.ampm_text_size).toInt().toFloat()
3894         MIN_HOURS_WIDTH = mResources.getDimension(R.dimen.min_hours_width).toInt()
3895         HOURS_LEFT_MARGIN = mResources.getDimension(R.dimen.hours_left_margin).toInt()
3896         HOURS_RIGHT_MARGIN = mResources.getDimension(R.dimen.hours_right_margin).toInt()
3897         MULTI_DAY_HEADER_HEIGHT = mResources.getDimension(R.dimen.day_header_height).toInt()
3898         val eventTextSizeId: Int
3899         eventTextSizeId = if (mNumDays == 1) {
3900             R.dimen.day_view_event_text_size
3901         } else {
3902             R.dimen.week_view_event_text_size
3903         }
3904         EVENT_TEXT_FONT_SIZE = mResources.getDimension(eventTextSizeId).toFloat()
3905         NEW_EVENT_HINT_FONT_SIZE = mResources.getDimension(R.dimen.new_event_hint_text_size).toInt()
3906         MIN_EVENT_HEIGHT = mResources.getDimension(R.dimen.event_min_height)
3907         MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT = MIN_EVENT_HEIGHT
3908         EVENT_TEXT_TOP_MARGIN = mResources.getDimension(R.dimen.event_text_vertical_margin).toInt()
3909         EVENT_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
3910         EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN
3911         EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_TOP_MARGIN
3912         EVENT_TEXT_LEFT_MARGIN = mResources
3913             .getDimension(R.dimen.event_text_horizontal_margin).toInt()
3914         EVENT_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
3915         EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN
3916         EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_LEFT_MARGIN
3917         if (mScale == 0f) {
3918             mScale = mResources.getDisplayMetrics().density
3919             if (mScale != 1f) {
3920                 SINGLE_ALLDAY_HEIGHT *= mScale.toInt()
3921                 ALLDAY_TOP_MARGIN *= mScale.toInt()
3922                 MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale.toInt()
3923                 NORMAL_FONT_SIZE *= mScale
3924                 GRID_LINE_LEFT_MARGIN *= mScale
3925                 HOURS_TOP_MARGIN *= mScale.toInt()
3926                 MIN_CELL_WIDTH_FOR_TEXT *= mScale.toInt()
3927                 MAX_UNEXPANDED_ALLDAY_HEIGHT *= mScale.toInt()
3928                 mAnimateDayEventHeight = MIN_UNEXPANDED_ALLDAY_EVENT_HEIGHT.toInt()
3929                 CURRENT_TIME_LINE_SIDE_BUFFER *= mScale.toInt()
3930                 CURRENT_TIME_LINE_TOP_OFFSET *= mScale.toInt()
3931                 MIN_Y_SPAN *= mScale.toInt()
3932                 MAX_CELL_HEIGHT *= mScale.toInt()
3933                 DEFAULT_CELL_HEIGHT *= mScale.toInt()
3934                 DAY_HEADER_HEIGHT *= mScale.toInt()
3935                 DAY_HEADER_RIGHT_MARGIN *= mScale.toInt()
3936                 DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale.toInt()
3937                 DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale.toInt()
3938                 DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale.toInt()
3939                 CALENDAR_COLOR_SQUARE_SIZE *= mScale.toInt()
3940                 EVENT_RECT_TOP_MARGIN *= mScale.toInt()
3941                 EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
3942                 ALL_DAY_EVENT_RECT_BOTTOM_MARGIN *= mScale.toInt()
3943                 EVENT_RECT_LEFT_MARGIN *= mScale.toInt()
3944                 EVENT_RECT_RIGHT_MARGIN *= mScale.toInt()
3945                 EVENT_RECT_STROKE_WIDTH *= mScale.toInt()
3946                 EVENT_SQUARE_WIDTH *= mScale.toInt()
3947                 EVENT_LINE_PADDING *= mScale.toInt()
3948                 NEW_EVENT_MARGIN *= mScale.toInt()
3949                 NEW_EVENT_WIDTH *= mScale.toInt()
3950                 NEW_EVENT_MAX_LENGTH *= mScale.toInt()
3951             }
3952         }
3953         HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN
3954         DAY_HEADER_HEIGHT = if (mNumDays == 1) ONE_DAY_HEADER_HEIGHT else MULTI_DAY_HEADER_HEIGHT
3955         mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_indicator_holo_light)
3956         mCurrentTimeAnimateLine = mResources
3957             .getDrawable(R.drawable.timeline_indicator_activated_holo_light)
3958         mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light)
3959         mExpandAlldayDrawable = mResources.getDrawable(R.drawable.ic_expand_holo_light)
3960         mCollapseAlldayDrawable = mResources.getDrawable(R.drawable.ic_collapse_holo_light)
3961         mNewEventHintColor = mResources.getColor(R.color.new_event_hint_text_color)
3962         mAcceptedOrTentativeEventBoxDrawable = mResources
3963             .getDrawable(R.drawable.panel_month_event_holo_light)
3964         mEventLoader = eventLoader as EventLoader
3965         mEventGeometry = EventGeometry()
3966         mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT)
3967         mEventGeometry.setHourGap(HOUR_GAP.toFloat())
3968         mEventGeometry.setCellMargin(DAY_GAP)
3969         mLastPopupEventID = INVALID_EVENT_ID
3970         mController = controller as CalendarController
3971         mViewSwitcher = viewSwitcher as ViewSwitcher
3972         mGestureDetector = GestureDetector(context, CalendarGestureListener())
3973         mScaleGestureDetector = ScaleGestureDetector(getContext(), this)
3974         if (mCellHeight == 0) {
3975             mCellHeight = Utils.getSharedPreference(
3976                 mContext,
3977                 GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT
3978             )
3979         }
3980         mScroller = OverScroller(context)
3981         mHScrollInterpolator = ScrollInterpolator()
3982         mEdgeEffectTop = EdgeEffect(context)
3983         mEdgeEffectBottom = EdgeEffect(context)
3984         val vc: ViewConfiguration = ViewConfiguration.get(context)
3985         mScaledPagingTouchSlop = vc.getScaledPagingTouchSlop()
3986         mOnDownDelay = ViewConfiguration.getTapTimeout()
3987         OVERFLING_DISTANCE = vc.getScaledOverflingDistance()
3988         init(context as Context)
3989     }
3990 }
3991