blob: 440d178b1706275f81391e0225125303e48c013b [file] [log] [blame]
/*
* 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
}
}