summaryrefslogtreecommitdiff
path: root/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/widget/CalendarAppWidgetModel.kt')
-rw-r--r--src/com/android/calendar/widget/CalendarAppWidgetModel.kt409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.kt b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
new file mode 100644
index 00000000..440d178b
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetModel.kt
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.widget
+
+import com.android.calendar.R
+import com.android.calendar.Utils
+import android.content.Context
+import android.database.Cursor
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.View
+import java.util.ArrayList
+import java.util.LinkedList
+import java.util.TimeZone
+
+internal class CalendarAppWidgetModel(context: Context, timeZone: String?) {
+ private var mHomeTZName: String? = null
+ private var mShowTZ = false
+
+ /**
+ * [RowInfo] is a class that represents a single row in the widget. It
+ * is actually only a pointer to either a [DayInfo] or an
+ * [EventInfo] instance, since a row in the widget might be either a
+ * day header or an event.
+ */
+ internal class RowInfo(
+ /**
+ * mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
+ */
+ @JvmField val mType: Int,
+ /**
+ * If mType is TYPE_DAY, then mData is the index into day infos.
+ * Otherwise mType is TYPE_MEETING and mData is the index into event
+ * infos.
+ */
+ @JvmField val mIndex: Int
+ ) {
+ companion object {
+ const val TYPE_DAY = 0
+ const val TYPE_MEETING = 1
+ }
+ }
+
+ /**
+ * [EventInfo] is a class that represents an event in the widget. It
+ * contains all of the data necessary to display that event, including the
+ * properly localized strings and visibility settings.
+ */
+ internal class EventInfo {
+ // Visibility value for When textview (View.GONE or View.VISIBLE)
+ @JvmField var visibWhen: Int
+ @JvmField var `when`: String? = null
+ // Visibility value for Where textview (View.GONE or View.VISIBLE)
+ @JvmField var visibWhere: Int
+ @JvmField var where: String? = null
+ // Visibility value for Title textview (View.GONE or View.VISIBLE)
+ @JvmField var visibTitle: Int
+ @JvmField var title: String? = null
+ @JvmField var selfAttendeeStatus = 0
+ @JvmField var id: Long = 0
+ @JvmField var start: Long = 0
+ @JvmField var end: Long = 0
+ @JvmField var allDay = false
+ @JvmField var color = 0
+
+ @Override
+ override fun toString(): String {
+ val builder = StringBuilder()
+ builder.append("EventInfo [visibTitle=")
+ builder.append(visibTitle)
+ builder.append(", title=")
+ builder.append(title)
+ builder.append(", visibWhen=")
+ builder.append(visibWhen)
+ builder.append(", id=")
+ builder.append(id)
+ builder.append(", when=")
+ builder.append(`when`)
+ builder.append(", visibWhere=")
+ builder.append(visibWhere)
+ builder.append(", where=")
+ builder.append(where)
+ builder.append(", color=")
+ builder.append(String.format("0x%x", color))
+ builder.append(", selfAttendeeStatus=")
+ builder.append(selfAttendeeStatus)
+ builder.append("]")
+ return builder.toString()
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ val prime = 31
+ var result = 1
+ result = prime * result + if (allDay) 1231 else 1237
+ result = prime * result + (id xor (id ushr 32)).toInt()
+ result = prime * result + (end xor (end ushr 32)).toInt()
+ result = prime * result + (start xor (start ushr 32)).toInt()
+ result = prime * result + if (title == null) 0 else title!!.hashCode()
+ result = prime * result + visibTitle
+ result = prime * result + visibWhen
+ result = prime * result + visibWhere
+ result = prime * result + if (`when` == null) 0 else `when`!!.hashCode()
+ result = prime * result + if (where == null) 0 else where!!.hashCode()
+ result = prime * result + color
+ result = prime * result + selfAttendeeStatus
+ return result
+ }
+
+ @Override
+ override fun equals(obj: Any?): Boolean {
+ if (this == obj) return true
+ if (obj == null) return false
+ if (this::class != obj::class) return false
+ val other = obj as EventInfo
+ if (id != other.id) return false
+ if (allDay != other.allDay) return false
+ if (end != other.end) return false
+ if (start != other.start) return false
+ if (title == null) {
+ if (other.title != null) return false
+ } else if (!title!!.equals(other.title)) return false
+ if (visibTitle != other.visibTitle) return false
+ if (visibWhen != other.visibWhen) return false
+ if (visibWhere != other.visibWhere) return false
+ if (`when` == null) {
+ if (other.`when` != null) return false
+ } else if (!`when`!!.equals(other.`when`)) {
+ return false
+ }
+ if (where == null) {
+ if (other.where != null) return false
+ } else if (!where!!.equals(other.where)) {
+ return false
+ }
+ if (color != other.color) {
+ return false
+ }
+ return if (selfAttendeeStatus != other.selfAttendeeStatus) {
+ false
+ } else true
+ }
+
+ init {
+ visibWhen = View.GONE
+ visibWhere = View.GONE
+ visibTitle = View.GONE
+ }
+ }
+
+ /**
+ * [DayInfo] is a class that represents a day header in the widget. It
+ * contains all of the data necessary to display that day header, including
+ * the properly localized string.
+ */
+ internal class DayInfo(
+ /** The Julian day */
+ @JvmField var mJulianDay: Int,
+ /** The string representation of this day header, to be displayed */
+ @JvmField var mDayLabel: String? = null
+ ) {
+ @Override
+ override fun toString(): String {
+ return mDayLabel as String
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ val prime = 31
+ var result = 1
+ result = prime * result + (mDayLabel?.hashCode() ?: 0)
+ result = prime * result + mJulianDay
+ return result
+ }
+
+ @Override
+ override fun equals(obj: Any?): Boolean {
+ if (this == obj) return true
+ if (obj == null) return false
+ if (this::class !== obj::class) return false
+ val other = obj as DayInfo
+ if (mDayLabel == null) {
+ if (other.mDayLabel != null) return false
+ } else if (!mDayLabel.equals(other.mDayLabel)) return false
+ return if (mJulianDay != other.mJulianDay) false else true
+ }
+ }
+
+ @JvmField val mRowInfos: ArrayList<RowInfo>
+ @JvmField val mEventInfos: ArrayList<EventInfo>
+ @JvmField val mDayInfos: ArrayList<DayInfo>
+ @JvmField val mContext: Context?
+ @JvmField val mNow: Long
+ @JvmField val mTodayJulianDay: Int
+ @JvmField val mMaxJulianDay: Int
+ fun buildFromCursor(cursor: Cursor, timeZone: String?) {
+ val recycle = Time(timeZone)
+ val mBuckets: ArrayList<LinkedList<RowInfo>> =
+ ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS)
+ for (i in 0 until CalendarAppWidgetService.MAX_DAYS) {
+ mBuckets.add(LinkedList<RowInfo>())
+ }
+ recycle.setToNow()
+ mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone())
+ if (mShowTZ) {
+ mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(
+ recycle.isDst !== 0,
+ TimeZone.SHORT
+ )
+ }
+ cursor.moveToPosition(-1)
+ val tz = Utils.getTimeZone(mContext, null)
+ while (cursor.moveToNext()) {
+ val rowId: Int = cursor.getPosition()
+ val eventId: Long = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID)
+ val allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) !== 0
+ var start: Long = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN)
+ var end: Long = cursor.getLong(CalendarAppWidgetService.INDEX_END)
+ val title: String = cursor.getString(CalendarAppWidgetService.INDEX_TITLE)
+ val location: String = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION)
+ // we don't compute these ourselves because it seems to produce the
+ // wrong endDay for all day events
+ val startDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY)
+ val endDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY)
+ val color: Int = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR)
+ val selfStatus: Int = cursor
+ .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS)
+
+ // Adjust all-day times into local timezone
+ if (allDay) {
+ start = Utils.convertAlldayUtcToLocal(recycle, start, tz as String)
+ end = Utils.convertAlldayUtcToLocal(recycle, end, tz as String)
+ }
+ if (LOGD) {
+ Log.d(
+ TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start +
+ " end:" + end + " eventId:" + eventId
+ )
+ }
+
+ // we might get some extra events when querying, in order to
+ // deal with all-day events
+ if (end < mNow) {
+ continue
+ }
+ val i: Int = mEventInfos.size
+ mEventInfos.add(
+ populateEventInfo(
+ eventId, allDay, start, end, startDay, endDay, title,
+ location, color, selfStatus
+ )
+ )
+ // populate the day buckets that this event falls into
+ val from: Int = Math.max(startDay, mTodayJulianDay)
+ val to: Int = Math.min(endDay, mMaxJulianDay)
+ for (day in from..to) {
+ val bucket: LinkedList<RowInfo> = mBuckets.get(day - mTodayJulianDay)
+ val rowInfo = RowInfo(RowInfo.TYPE_MEETING, i)
+ if (allDay) {
+ bucket.addFirst(rowInfo)
+ } else {
+ bucket.add(rowInfo)
+ }
+ }
+ }
+ var day = mTodayJulianDay
+ var count = 0
+ for (bucket in mBuckets) {
+ if (!bucket.isEmpty()) {
+ // We don't show day header in today
+ if (day != mTodayJulianDay) {
+ val dayInfo = populateDayInfo(day, recycle)
+ // Add the day header
+ val dayIndex: Int = mDayInfos.size
+ mDayInfos.add(dayInfo as CalendarAppWidgetModel.DayInfo)
+ mRowInfos.add(RowInfo(RowInfo.TYPE_DAY, dayIndex))
+ }
+
+ // Add the event row infos
+ mRowInfos.addAll(bucket)
+ count += bucket.size
+ }
+ day++
+ if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
+ break
+ }
+ }
+ }
+
+ private fun populateEventInfo(
+ eventId: Long,
+ allDay: Boolean,
+ start: Long,
+ end: Long,
+ startDay: Int,
+ endDay: Int,
+ title: String,
+ location: String,
+ color: Int,
+ selfStatus: Int
+ ): EventInfo {
+ val eventInfo = EventInfo()
+
+ // Compute a human-readable string for the start time of the event
+ val whenString = StringBuilder()
+ val visibWhen: Int
+ var flags: Int = DateUtils.FORMAT_ABBREV_ALL
+ visibWhen = View.VISIBLE
+ if (allDay) {
+ flags = flags or DateUtils.FORMAT_SHOW_DATE
+ whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+ } else {
+ flags = flags or DateUtils.FORMAT_SHOW_TIME
+ if (DateFormat.is24HourFormat(mContext)) {
+ flags = flags or DateUtils.FORMAT_24HOUR
+ }
+ if (endDay > startDay) {
+ flags = flags or DateUtils.FORMAT_SHOW_DATE
+ }
+ whenString.append(Utils.formatDateRange(mContext, start, end, flags))
+ if (mShowTZ) {
+ whenString.append(" ").append(mHomeTZName)
+ }
+ }
+ eventInfo.id = eventId
+ eventInfo.start = start
+ eventInfo.end = end
+ eventInfo.allDay = allDay
+ eventInfo.`when` = whenString.toString()
+ eventInfo.visibWhen = visibWhen
+ eventInfo.color = color
+ eventInfo.selfAttendeeStatus = selfStatus
+
+ // What
+ if (TextUtils.isEmpty(title)) {
+ eventInfo.title = mContext?.getString(R.string.no_title_label)
+ } else {
+ eventInfo.title = title
+ }
+ eventInfo.visibTitle = View.VISIBLE
+
+ // Where
+ if (!TextUtils.isEmpty(location)) {
+ eventInfo.visibWhere = View.VISIBLE
+ eventInfo.where = location
+ } else {
+ eventInfo.visibWhere = View.GONE
+ }
+ return eventInfo
+ }
+
+ private fun populateDayInfo(julianDay: Int, recycle: Time?): DayInfo? {
+ val millis: Long = recycle?.setJulianDay(julianDay) as Long
+ var flags: Int = DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
+ val label: String?
+ if (julianDay == mTodayJulianDay + 1) {
+ label = mContext?.getString(
+ R.string.agenda_tomorrow,
+ Utils.formatDateRange(mContext, millis, millis, flags).toString()
+ )
+ } else {
+ flags = flags or DateUtils.FORMAT_SHOW_WEEKDAY
+ label = Utils.formatDateRange(mContext, millis, millis, flags)
+ }
+ return DayInfo(julianDay, label as String)
+ }
+
+ @Override
+ override fun toString(): String {
+ val builder = StringBuilder()
+ builder.append("\nCalendarAppWidgetModel [eventInfos=")
+ builder.append(mEventInfos)
+ builder.append("]")
+ return builder.toString()
+ }
+
+ companion object {
+ private val TAG: String = CalendarAppWidgetModel::class.java.getSimpleName()
+ private const val LOGD = false
+ }
+
+ init {
+ mNow = System.currentTimeMillis()
+ val time = Time(timeZone)
+ time.setToNow() // This is needed for gmtoff to be set
+ mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff)
+ mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1
+ mEventInfos = ArrayList<EventInfo>(50)
+ mRowInfos = ArrayList<RowInfo>(50)
+ mDayInfos = ArrayList<DayInfo>(8)
+ mContext = context
+ }
+} \ No newline at end of file