• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.widget
17 
18 import com.android.calendar.R
19 import com.android.calendar.Utils
20 import android.content.Context
21 import android.database.Cursor
22 import android.text.TextUtils
23 import android.text.format.DateFormat
24 import android.text.format.DateUtils
25 import android.text.format.Time
26 import android.util.Log
27 import android.view.View
28 import java.util.ArrayList
29 import java.util.LinkedList
30 import java.util.TimeZone
31 
32 internal class CalendarAppWidgetModel(context: Context, timeZone: String?) {
33     private var mHomeTZName: String? = null
34     private var mShowTZ = false
35 
36     /**
37      * [RowInfo] is a class that represents a single row in the widget. It
38      * is actually only a pointer to either a [DayInfo] or an
39      * [EventInfo] instance, since a row in the widget might be either a
40      * day header or an event.
41      */
42     internal class RowInfo(
43         /**
44          * mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
45          */
46         @JvmField val mType: Int,
47         /**
48          * If mType is TYPE_DAY, then mData is the index into day infos.
49          * Otherwise mType is TYPE_MEETING and mData is the index into event
50          * infos.
51          */
52         @JvmField val mIndex: Int
53     ) {
54         companion object {
55             const val TYPE_DAY = 0
56             const val TYPE_MEETING = 1
57         }
58     }
59 
60     /**
61      * [EventInfo] is a class that represents an event in the widget. It
62      * contains all of the data necessary to display that event, including the
63      * properly localized strings and visibility settings.
64      */
65     internal class EventInfo {
66         // Visibility value for When textview (View.GONE or View.VISIBLE)
67         @JvmField var visibWhen: Int
68         @JvmField var `when`: String? = null
69         // Visibility value for Where textview (View.GONE or View.VISIBLE)
70         @JvmField var visibWhere: Int
71         @JvmField var where: String? = null
72         // Visibility value for Title textview (View.GONE or View.VISIBLE)
73         @JvmField var visibTitle: Int
74         @JvmField var title: String? = null
75         @JvmField var selfAttendeeStatus = 0
76         @JvmField var id: Long = 0
77         @JvmField var start: Long = 0
78         @JvmField var end: Long = 0
79         @JvmField var allDay = false
80         @JvmField var color = 0
81 
82         @Override
toStringnull83         override fun toString(): String {
84             val builder = StringBuilder()
85             builder.append("EventInfo [visibTitle=")
86             builder.append(visibTitle)
87             builder.append(", title=")
88             builder.append(title)
89             builder.append(", visibWhen=")
90             builder.append(visibWhen)
91             builder.append(", id=")
92             builder.append(id)
93             builder.append(", when=")
94             builder.append(`when`)
95             builder.append(", visibWhere=")
96             builder.append(visibWhere)
97             builder.append(", where=")
98             builder.append(where)
99             builder.append(", color=")
100             builder.append(String.format("0x%x", color))
101             builder.append(", selfAttendeeStatus=")
102             builder.append(selfAttendeeStatus)
103             builder.append("]")
104             return builder.toString()
105         }
106 
107         @Override
hashCodenull108         override fun hashCode(): Int {
109             val prime = 31
110             var result = 1
111             result = prime * result + if (allDay) 1231 else 1237
112             result = prime * result + (id xor (id ushr 32)).toInt()
113             result = prime * result + (end xor (end ushr 32)).toInt()
114             result = prime * result + (start xor (start ushr 32)).toInt()
115             result = prime * result + if (title == null) 0 else title!!.hashCode()
116             result = prime * result + visibTitle
117             result = prime * result + visibWhen
118             result = prime * result + visibWhere
119             result = prime * result + if (`when` == null) 0 else `when`!!.hashCode()
120             result = prime * result + if (where == null) 0 else where!!.hashCode()
121             result = prime * result + color
122             result = prime * result + selfAttendeeStatus
123             return result
124         }
125 
126         @Override
equalsnull127         override fun equals(obj: Any?): Boolean {
128             if (this == obj) return true
129             if (obj == null) return false
130             if (this::class != obj::class) return false
131             val other = obj as EventInfo
132             if (id != other.id) return false
133             if (allDay != other.allDay) return false
134             if (end != other.end) return false
135             if (start != other.start) return false
136             if (title == null) {
137                 if (other.title != null) return false
138             } else if (!title!!.equals(other.title)) return false
139             if (visibTitle != other.visibTitle) return false
140             if (visibWhen != other.visibWhen) return false
141             if (visibWhere != other.visibWhere) return false
142             if (`when` == null) {
143                 if (other.`when` != null) return false
144             } else if (!`when`!!.equals(other.`when`)) {
145                 return false
146             }
147             if (where == null) {
148                 if (other.where != null) return false
149             } else if (!where!!.equals(other.where)) {
150                 return false
151             }
152             if (color != other.color) {
153                 return false
154             }
155             return if (selfAttendeeStatus != other.selfAttendeeStatus) {
156                 false
157             } else true
158         }
159 
160         init {
161             visibWhen = View.GONE
162             visibWhere = View.GONE
163             visibTitle = View.GONE
164         }
165     }
166 
167     /**
168      * [DayInfo] is a class that represents a day header in the widget. It
169      * contains all of the data necessary to display that day header, including
170      * the properly localized string.
171      */
172     internal class DayInfo(
173         /** The Julian day  */
174         @JvmField var mJulianDay: Int,
175         /** The string representation of this day header, to be displayed  */
176         @JvmField var mDayLabel: String? = null
177     ) {
178         @Override
toStringnull179         override fun toString(): String {
180             return mDayLabel as String
181         }
182 
183         @Override
hashCodenull184         override fun hashCode(): Int {
185             val prime = 31
186             var result = 1
187             result = prime * result + (mDayLabel?.hashCode() ?: 0)
188             result = prime * result + mJulianDay
189             return result
190         }
191 
192         @Override
equalsnull193         override fun equals(obj: Any?): Boolean {
194             if (this == obj) return true
195             if (obj == null) return false
196             if (this::class !== obj::class) return false
197             val other = obj as DayInfo
198             if (mDayLabel == null) {
199                 if (other.mDayLabel != null) return false
200             } else if (!mDayLabel.equals(other.mDayLabel)) return false
201             return if (mJulianDay != other.mJulianDay) false else true
202         }
203     }
204 
205     @JvmField val mRowInfos: ArrayList<RowInfo>
206     @JvmField val mEventInfos: ArrayList<EventInfo>
207     @JvmField val mDayInfos: ArrayList<DayInfo>
208     @JvmField val mContext: Context?
209     @JvmField val mNow: Long
210     @JvmField val mTodayJulianDay: Int
211     @JvmField val mMaxJulianDay: Int
buildFromCursornull212     fun buildFromCursor(cursor: Cursor, timeZone: String?) {
213         val recycle = Time(timeZone)
214         val mBuckets: ArrayList<LinkedList<RowInfo>> =
215             ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS)
216         for (i in 0 until CalendarAppWidgetService.MAX_DAYS) {
217             mBuckets.add(LinkedList<RowInfo>())
218         }
219         recycle.setToNow()
220         mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone())
221         if (mShowTZ) {
222             mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(
223                 recycle.isDst !== 0,
224                 TimeZone.SHORT
225             )
226         }
227         cursor.moveToPosition(-1)
228         val tz = Utils.getTimeZone(mContext, null)
229         while (cursor.moveToNext()) {
230             val rowId: Int = cursor.getPosition()
231             val eventId: Long = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID)
232             val allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) !== 0
233             var start: Long = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN)
234             var end: Long = cursor.getLong(CalendarAppWidgetService.INDEX_END)
235             val title: String = cursor.getString(CalendarAppWidgetService.INDEX_TITLE)
236             val location: String = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION)
237             // we don't compute these ourselves because it seems to produce the
238             // wrong endDay for all day events
239             val startDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY)
240             val endDay: Int = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY)
241             val color: Int = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR)
242             val selfStatus: Int = cursor
243                 .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS)
244 
245             // Adjust all-day times into local timezone
246             if (allDay) {
247                 start = Utils.convertAlldayUtcToLocal(recycle, start, tz as String)
248                 end = Utils.convertAlldayUtcToLocal(recycle, end, tz as String)
249             }
250             if (LOGD) {
251                 Log.d(
252                     TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start +
253                         " end:" + end + " eventId:" + eventId
254                 )
255             }
256 
257             // we might get some extra events when querying, in order to
258             // deal with all-day events
259             if (end < mNow) {
260                 continue
261             }
262             val i: Int = mEventInfos.size
263             mEventInfos.add(
264                 populateEventInfo(
265                     eventId, allDay, start, end, startDay, endDay, title,
266                     location, color, selfStatus
267                 )
268             )
269             // populate the day buckets that this event falls into
270             val from: Int = Math.max(startDay, mTodayJulianDay)
271             val to: Int = Math.min(endDay, mMaxJulianDay)
272             for (day in from..to) {
273                 val bucket: LinkedList<RowInfo> = mBuckets.get(day - mTodayJulianDay)
274                 val rowInfo = RowInfo(RowInfo.TYPE_MEETING, i)
275                 if (allDay) {
276                     bucket.addFirst(rowInfo)
277                 } else {
278                     bucket.add(rowInfo)
279                 }
280             }
281         }
282         var day = mTodayJulianDay
283         var count = 0
284         for (bucket in mBuckets) {
285             if (!bucket.isEmpty()) {
286                 // We don't show day header in today
287                 if (day != mTodayJulianDay) {
288                     val dayInfo = populateDayInfo(day, recycle)
289                     // Add the day header
290                     val dayIndex: Int = mDayInfos.size
291                     mDayInfos.add(dayInfo as CalendarAppWidgetModel.DayInfo)
292                     mRowInfos.add(RowInfo(RowInfo.TYPE_DAY, dayIndex))
293                 }
294 
295                 // Add the event row infos
296                 mRowInfos.addAll(bucket)
297                 count += bucket.size
298             }
299             day++
300             if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
301                 break
302             }
303         }
304     }
305 
populateEventInfonull306     private fun populateEventInfo(
307         eventId: Long,
308         allDay: Boolean,
309         start: Long,
310         end: Long,
311         startDay: Int,
312         endDay: Int,
313         title: String,
314         location: String,
315         color: Int,
316         selfStatus: Int
317     ): EventInfo {
318         val eventInfo = EventInfo()
319 
320         // Compute a human-readable string for the start time of the event
321         val whenString = StringBuilder()
322         val visibWhen: Int
323         var flags: Int = DateUtils.FORMAT_ABBREV_ALL
324         visibWhen = View.VISIBLE
325         if (allDay) {
326             flags = flags or DateUtils.FORMAT_SHOW_DATE
327             whenString.append(Utils.formatDateRange(mContext, start, end, flags))
328         } else {
329             flags = flags or DateUtils.FORMAT_SHOW_TIME
330             if (DateFormat.is24HourFormat(mContext)) {
331                 flags = flags or DateUtils.FORMAT_24HOUR
332             }
333             if (endDay > startDay) {
334                 flags = flags or DateUtils.FORMAT_SHOW_DATE
335             }
336             whenString.append(Utils.formatDateRange(mContext, start, end, flags))
337             if (mShowTZ) {
338                 whenString.append(" ").append(mHomeTZName)
339             }
340         }
341         eventInfo.id = eventId
342         eventInfo.start = start
343         eventInfo.end = end
344         eventInfo.allDay = allDay
345         eventInfo.`when` = whenString.toString()
346         eventInfo.visibWhen = visibWhen
347         eventInfo.color = color
348         eventInfo.selfAttendeeStatus = selfStatus
349 
350         // What
351         if (TextUtils.isEmpty(title)) {
352             eventInfo.title = mContext?.getString(R.string.no_title_label)
353         } else {
354             eventInfo.title = title
355         }
356         eventInfo.visibTitle = View.VISIBLE
357 
358         // Where
359         if (!TextUtils.isEmpty(location)) {
360             eventInfo.visibWhere = View.VISIBLE
361             eventInfo.where = location
362         } else {
363             eventInfo.visibWhere = View.GONE
364         }
365         return eventInfo
366     }
367 
populateDayInfonull368     private fun populateDayInfo(julianDay: Int, recycle: Time?): DayInfo? {
369         val millis: Long = recycle?.setJulianDay(julianDay) as Long
370         var flags: Int = DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_DATE
371         val label: String?
372         if (julianDay == mTodayJulianDay + 1) {
373             label = mContext?.getString(
374                 R.string.agenda_tomorrow,
375                 Utils.formatDateRange(mContext, millis, millis, flags).toString()
376             )
377         } else {
378             flags = flags or DateUtils.FORMAT_SHOW_WEEKDAY
379             label = Utils.formatDateRange(mContext, millis, millis, flags)
380         }
381         return DayInfo(julianDay, label as String)
382     }
383 
384     @Override
toStringnull385     override fun toString(): String {
386         val builder = StringBuilder()
387         builder.append("\nCalendarAppWidgetModel [eventInfos=")
388         builder.append(mEventInfos)
389         builder.append("]")
390         return builder.toString()
391     }
392 
393     companion object {
394         private val TAG: String = CalendarAppWidgetModel::class.java.getSimpleName()
395         private const val LOGD = false
396     }
397 
398     init {
399         mNow = System.currentTimeMillis()
400         val time = Time(timeZone)
401         time.setToNow() // This is needed for gmtoff to be set
402         mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff)
403         mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1
404         mEventInfos = ArrayList<EventInfo>(50)
405         mRowInfos = ArrayList<RowInfo>(50)
406         mDayInfos = ArrayList<DayInfo>(8)
407         mContext = context
408     }
409 }