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.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME 19 import android.provider.CalendarContract.EXTRA_EVENT_END_TIME 20 import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS 21 import android.content.ComponentName 22 import android.content.ContentUris 23 import android.content.Context 24 import android.content.Intent 25 import android.net.Uri 26 import android.provider.CalendarContract.Attendees 27 import android.provider.CalendarContract.Events 28 import android.text.format.Time 29 import android.util.Log 30 import android.util.Pair 31 import java.lang.ref.WeakReference 32 import java.util.LinkedHashMap 33 import java.util.LinkedList 34 import java.util.WeakHashMap 35 36 class CalendarController private constructor(context: Context?) { 37 private var mContext: Context? = null 38 39 // This uses a LinkedHashMap so that we can replace fragments based on the 40 // view id they are being expanded into since we can't guarantee a reference 41 // to the handler will be findable 42 private val eventHandlers: LinkedHashMap<Int, EventHandler> = 43 LinkedHashMap<Int, EventHandler>(5) 44 private val mToBeRemovedEventHandlers: LinkedList<Int> = LinkedList<Int>() 45 private val mToBeAddedEventHandlers: LinkedHashMap<Int, EventHandler> = 46 LinkedHashMap<Int, EventHandler>() 47 private var mFirstEventHandler: Pair<Int, EventHandler>? = null 48 private var mToBeAddedFirstEventHandler: Pair<Int, EventHandler>? = null 49 50 @Volatile 51 private var mDispatchInProgressCounter = 0 52 private val filters: WeakHashMap<Object, Long> = WeakHashMap<Object, Long>(1) 53 54 // Forces the viewType. Should only be used for initialization. 55 var viewType = -1 56 private var mDetailViewType = -1 57 var previousViewType = -1 58 private set 59 60 // The last event ID the edit view was launched with 61 var eventId: Long = -1 62 private val mTime: Time? = Time() 63 64 // The last set of date flags sent with 65 var dateFlags: Long = 0 66 private set 67 private val mUpdateTimezone: Runnable = object : Runnable { 68 @Override runnull69 override fun run() { 70 mTime?.switchTimezone(Utils.getTimeZone(mContext, this)) 71 } 72 } 73 74 /** 75 * One of the event types that are sent to or from the controller 76 */ 77 interface EventType { 78 companion object { 79 // Simple view of an event 80 const val VIEW_EVENT = 1L shl 1 81 82 // Full detail view in read only mode 83 const val VIEW_EVENT_DETAILS = 1L shl 2 84 85 // full detail view in edit mode 86 const val EDIT_EVENT = 1L shl 3 87 const val GO_TO = 1L shl 5 88 const val EVENTS_CHANGED = 1L shl 7 89 const val USER_HOME = 1L shl 9 90 91 // date range has changed, update the title 92 const val UPDATE_TITLE = 1L shl 10 93 } 94 } 95 96 /** 97 * One of the Agenda/Day/Week/Month view types 98 */ 99 interface ViewType { 100 companion object { 101 const val DETAIL = -1 102 const val CURRENT = 0 103 const val AGENDA = 1 104 const val DAY = 2 105 const val WEEK = 3 106 const val MONTH = 4 107 const val EDIT = 5 108 const val MAX_VALUE = 5 109 } 110 } 111 112 class EventInfo { 113 @JvmField var eventType: Long = 0 // one of the EventType 114 @JvmField var viewType = 0 // one of the ViewType 115 @JvmField var id: Long = 0 // event id 116 @JvmField var selectedTime: Time? = null // the selected time in focus 117 118 // Event start and end times. All-day events are represented in: 119 // - local time for GO_TO commands 120 // - UTC time for VIEW_EVENT and other event-related commands 121 @JvmField var startTime: Time? = null 122 @JvmField var endTime: Time? = null 123 @JvmField var x = 0 // x coordinate in the activity space 124 @JvmField var y = 0 // y coordinate in the activity space 125 @JvmField var query: String? = null // query for a user search 126 @JvmField var componentName: ComponentName? = null // used in combination with query 127 @JvmField var eventTitle: String? = null 128 @JvmField var calendarId: Long = 0 129 130 /** 131 * For EventType.VIEW_EVENT: 132 * It is the default attendee response and an all day event indicator. 133 * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED, 134 * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE. 135 * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response. 136 * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay(). 137 * 138 * 139 * For EventType.GO_TO: 140 * Set to [.EXTRA_GOTO_TIME] to go to the specified date/time. 141 * Set to [.EXTRA_GOTO_DATE] to consider the date but ignore the time. 142 * Set to [.EXTRA_GOTO_BACK_TO_PREVIOUS] if back should bring back previous view. 143 * Set to [.EXTRA_GOTO_TODAY] if this is a user request to go to the current time. 144 * 145 * 146 * For EventType.UPDATE_TITLE: 147 * Set formatting flags for Utils.formatDateRange 148 */ 149 @JvmField var extraLong: Long = 0 150 val isAllDay: Boolean 151 get() { 152 if (eventType != EventType.VIEW_EVENT) { 153 Log.wtf(TAG, "illegal call to isAllDay , wrong event type $eventType") 154 return false 155 } 156 return if (extraLong and ALL_DAY_MASK != 0L) true else false 157 } 158 val response: Int 159 get() { 160 if (eventType != EventType.VIEW_EVENT) { 161 Log.wtf(TAG, "illegal call to getResponse , wrong event type $eventType") 162 return Attendees.ATTENDEE_STATUS_NONE 163 } 164 val response = (extraLong and ATTENTEE_STATUS_MASK).toInt() 165 when (response) { 166 ATTENDEE_STATUS_NONE_MASK -> return Attendees.ATTENDEE_STATUS_NONE 167 ATTENDEE_STATUS_ACCEPTED_MASK -> return Attendees.ATTENDEE_STATUS_ACCEPTED 168 ATTENDEE_STATUS_DECLINED_MASK -> return Attendees.ATTENDEE_STATUS_DECLINED 169 ATTENDEE_STATUS_TENTATIVE_MASK -> return Attendees.ATTENDEE_STATUS_TENTATIVE 170 else -> Log.wtf(TAG, "Unknown attendee response $response") 171 } 172 return ATTENDEE_STATUS_NONE_MASK 173 } 174 175 companion object { 176 private const val ATTENTEE_STATUS_MASK: Long = 0xFF 177 private const val ALL_DAY_MASK: Long = 0x100 178 private const val ATTENDEE_STATUS_NONE_MASK = 0x01 179 private const val ATTENDEE_STATUS_ACCEPTED_MASK = 0x02 180 private const val ATTENDEE_STATUS_DECLINED_MASK = 0x04 181 private const val ATTENDEE_STATUS_TENTATIVE_MASK = 0x08 182 183 // Used to build the extra long for a VIEW event. buildViewExtraLongnull184 @JvmStatic fun buildViewExtraLong(response: Int, allDay: Boolean): Long { 185 var extra = if (allDay) ALL_DAY_MASK else 0 186 extra = when (response) { 187 Attendees.ATTENDEE_STATUS_NONE -> extra or 188 ATTENDEE_STATUS_NONE_MASK.toLong() 189 Attendees.ATTENDEE_STATUS_ACCEPTED -> extra or 190 ATTENDEE_STATUS_ACCEPTED_MASK.toLong() 191 Attendees.ATTENDEE_STATUS_DECLINED -> extra or 192 ATTENDEE_STATUS_DECLINED_MASK.toLong() 193 Attendees.ATTENDEE_STATUS_TENTATIVE -> extra or 194 ATTENDEE_STATUS_TENTATIVE_MASK.toLong() 195 else -> { 196 Log.wtf( 197 TAG, 198 "Unknown attendee response $response" 199 ) 200 extra or ATTENDEE_STATUS_NONE_MASK.toLong() 201 } 202 } 203 return extra 204 } 205 } 206 } 207 208 interface EventHandler { 209 val supportedEventTypes: Long handleEventnull210 fun handleEvent(event: EventInfo?) 211 212 /** 213 * This notifies the handler that the database has changed and it should 214 * update its view. 215 */ 216 fun eventsChanged() 217 } 218 219 fun sendEventRelatedEvent( 220 sender: Object?, 221 eventType: Long, 222 eventId: Long, 223 startMillis: Long, 224 endMillis: Long, 225 x: Int, 226 y: Int, 227 selectedMillis: Long 228 ) { 229 // TODO: pass the real allDay status or at least a status that says we don't know the 230 // status and have the receiver query the data. 231 // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo 232 // so currently the missing allDay status has no effect. 233 sendEventRelatedEventWithExtra( 234 sender, eventType, eventId, startMillis, endMillis, x, y, 235 EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false), 236 selectedMillis 237 ) 238 } 239 240 /** 241 * Helper for sending New/View/Edit/Delete events 242 * 243 * @param sender object of the caller 244 * @param eventType one of [EventType] 245 * @param eventId event id 246 * @param startMillis start time 247 * @param endMillis end time 248 * @param x x coordinate in the activity space 249 * @param y y coordinate in the activity space 250 * @param extraLong default response value for the "simple event view" and all day indication. 251 * Use Attendees.ATTENDEE_STATUS_NONE for no response. 252 * @param selectedMillis The time to specify as selected 253 */ sendEventRelatedEventWithExtranull254 fun sendEventRelatedEventWithExtra( 255 sender: Object?, 256 eventType: Long, 257 eventId: Long, 258 startMillis: Long, 259 endMillis: Long, 260 x: Int, 261 y: Int, 262 extraLong: Long, 263 selectedMillis: Long 264 ) { 265 sendEventRelatedEventWithExtraWithTitleWithCalendarId( 266 sender, eventType, eventId, 267 startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1 268 ) 269 } 270 271 /** 272 * Helper for sending New/View/Edit/Delete events 273 * 274 * @param sender object of the caller 275 * @param eventType one of [EventType] 276 * @param eventId event id 277 * @param startMillis start time 278 * @param endMillis end time 279 * @param x x coordinate in the activity space 280 * @param y y coordinate in the activity space 281 * @param extraLong default response value for the "simple event view" and all day indication. 282 * Use Attendees.ATTENDEE_STATUS_NONE for no response. 283 * @param selectedMillis The time to specify as selected 284 * @param title The title of the event 285 * @param calendarId The id of the calendar which the event belongs to 286 */ sendEventRelatedEventWithExtraWithTitleWithCalendarIdnull287 fun sendEventRelatedEventWithExtraWithTitleWithCalendarId( 288 sender: Object?, 289 eventType: Long, 290 eventId: Long, 291 startMillis: Long, 292 endMillis: Long, 293 x: Int, 294 y: Int, 295 extraLong: Long, 296 selectedMillis: Long, 297 title: String?, 298 calendarId: Long 299 ) { 300 val info = EventInfo() 301 info.eventType = eventType 302 if (eventType == EventType.VIEW_EVENT_DETAILS) { 303 info.viewType = ViewType.CURRENT 304 } 305 info.id = eventId 306 info.startTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone)) 307 (info.startTime as Time).set(startMillis) 308 if (selectedMillis != -1L) { 309 info.selectedTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone)) 310 (info.selectedTime as Time).set(selectedMillis) 311 } else { 312 info.selectedTime = info.startTime 313 } 314 info.endTime = Time(Utils.getTimeZone(mContext, mUpdateTimezone)) 315 (info.endTime as Time).set(endMillis) 316 info.x = x 317 info.y = y 318 info.extraLong = extraLong 319 info.eventTitle = title 320 info.calendarId = calendarId 321 this.sendEvent(sender, info) 322 } 323 324 /** 325 * Helper for sending non-calendar-event events 326 * 327 * @param sender object of the caller 328 * @param eventType one of [EventType] 329 * @param start start time 330 * @param end end time 331 * @param eventId event id 332 * @param viewType [ViewType] 333 */ sendEventnull334 fun sendEvent( 335 sender: Object?, 336 eventType: Long, 337 start: Time?, 338 end: Time?, 339 eventId: Long, 340 viewType: Int 341 ) { 342 sendEvent( 343 sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null, 344 null 345 ) 346 } 347 348 /** 349 * sendEvent() variant with extraLong, search query, and search component name. 350 */ sendEventnull351 fun sendEvent( 352 sender: Object?, 353 eventType: Long, 354 start: Time?, 355 end: Time?, 356 eventId: Long, 357 viewType: Int, 358 extraLong: Long, 359 query: String?, 360 componentName: ComponentName? 361 ) { 362 sendEvent( 363 sender, eventType, start, end, start, eventId, viewType, extraLong, query, 364 componentName 365 ) 366 } 367 sendEventnull368 fun sendEvent( 369 sender: Object?, 370 eventType: Long, 371 start: Time?, 372 end: Time?, 373 selected: Time?, 374 eventId: Long, 375 viewType: Int, 376 extraLong: Long, 377 query: String?, 378 componentName: ComponentName? 379 ) { 380 val info = EventInfo() 381 info.eventType = eventType 382 info.startTime = start 383 info.selectedTime = selected 384 info.endTime = end 385 info.id = eventId 386 info.viewType = viewType 387 info.query = query 388 info.componentName = componentName 389 info.extraLong = extraLong 390 this.sendEvent(sender, info) 391 } 392 sendEventnull393 fun sendEvent(sender: Object?, event: EventInfo) { 394 // TODO Throw exception on invalid events 395 if (DEBUG) { 396 Log.d(TAG, eventInfoToString(event)) 397 } 398 val filteredTypes: Long? = filters.get(sender) 399 if (filteredTypes != null && filteredTypes.toLong() and event.eventType != 0L) { 400 // Suppress event per filter 401 if (DEBUG) { 402 Log.d(TAG, "Event suppressed") 403 } 404 return 405 } 406 previousViewType = viewType 407 408 // Fix up view if not specified 409 if (event.viewType == ViewType.DETAIL) { 410 event.viewType = mDetailViewType 411 viewType = mDetailViewType 412 } else if (event.viewType == ViewType.CURRENT) { 413 event.viewType = viewType 414 } else if (event.viewType != ViewType.EDIT) { 415 viewType = event.viewType 416 if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY || 417 Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK) { 418 mDetailViewType = viewType 419 } 420 } 421 if (DEBUG) { 422 Log.d(TAG, "vvvvvvvvvvvvvvv") 423 Log.d( 424 TAG, 425 "Start " + if (event.startTime == null) "null" else event.startTime.toString() 426 ) 427 Log.d(TAG, "End " + if (event.endTime == null) "null" else event.endTime.toString()) 428 Log.d( 429 TAG, 430 "Select " + if (event.selectedTime == null) "null" 431 else event.selectedTime.toString() 432 ) 433 Log.d(TAG, "mTime " + if (mTime == null) "null" else mTime.toString()) 434 } 435 var startMillis: Long = 0 436 val temp = event.startTime 437 if (temp != null) { 438 startMillis = (event.startTime as Time).toMillis(false) 439 } 440 441 // Set mTime if selectedTime is set 442 val temp1 = event.selectedTime 443 if (temp1 != null && temp1.toMillis(false) != 0L) { 444 mTime?.set(event.selectedTime) 445 } else { 446 if (startMillis != 0L) { 447 // selectedTime is not set so set mTime to startTime iff it is not 448 // within start and end times 449 val mtimeMillis: Long = mTime?.toMillis(false) as Long 450 val temp2 = event.endTime 451 if (mtimeMillis < startMillis || 452 temp2 != null && mtimeMillis > temp2.toMillis(false)) { 453 mTime.set(event.startTime) 454 } 455 } 456 event.selectedTime = mTime 457 } 458 // Store the formatting flags if this is an update to the title 459 if (event.eventType == EventType.UPDATE_TITLE) { 460 dateFlags = event.extraLong 461 } 462 463 // Fix up start time if not specified 464 if (startMillis == 0L) { 465 event.startTime = mTime 466 } 467 if (DEBUG) { 468 Log.d( 469 TAG, 470 "Start " + if (event.startTime == null) "null" else 471 event.startTime.toString() 472 ) 473 Log.d(TAG, "End " + if (event.endTime == null) "null" else 474 event.endTime.toString()) 475 Log.d( 476 TAG, 477 "Select " + if (event.selectedTime == null) "null" else 478 event.selectedTime.toString() 479 ) 480 Log.d(TAG, "mTime " + if (mTime == null) "null" else mTime.toString()) 481 Log.d(TAG, "^^^^^^^^^^^^^^^") 482 } 483 484 // Store the eventId if we're entering edit event 485 if ((event.eventType and EventType.VIEW_EVENT_DETAILS) != 0L) { 486 if (event.id > 0) { 487 eventId = event.id 488 } else { 489 eventId = -1 490 } 491 } 492 var handled = false 493 synchronized(this) { 494 mDispatchInProgressCounter++ 495 if (DEBUG) { 496 Log.d( 497 TAG, 498 "sendEvent: Dispatching to " + eventHandlers.size.toString() + " handlers" 499 ) 500 } 501 // Dispatch to event handler(s) 502 val temp3 = mFirstEventHandler 503 if (temp3 != null) { 504 // Handle the 'first' one before handling the others 505 val handler: EventHandler? = mFirstEventHandler?.second 506 if (handler != null && handler.supportedEventTypes and event.eventType != 0L && 507 !mToBeRemovedEventHandlers.contains(mFirstEventHandler?.first)) { 508 handler.handleEvent(event) 509 handled = true 510 } 511 } 512 val handlers: MutableIterator<MutableMap.MutableEntry<Int, 513 CalendarController.EventHandler>> = eventHandlers.entries.iterator() 514 while (handlers.hasNext()) { 515 val entry: MutableMap.MutableEntry<Int, 516 CalendarController.EventHandler> = handlers.next() 517 val key: Int = entry.key.toInt() 518 val temp4 = mFirstEventHandler 519 if (temp4 != null && key.toInt() == temp4.first.toInt()) { 520 // If this was the 'first' handler it was already handled 521 continue 522 } 523 val eventHandler: EventHandler = entry.value 524 if (eventHandler != null && 525 eventHandler.supportedEventTypes and event.eventType != 0L) { 526 if (mToBeRemovedEventHandlers.contains(key)) { 527 continue 528 } 529 eventHandler.handleEvent(event) 530 handled = true 531 } 532 } 533 mDispatchInProgressCounter-- 534 if (mDispatchInProgressCounter == 0) { 535 536 // Deregister removed handlers 537 if (mToBeRemovedEventHandlers.size > 0) { 538 for (zombie in mToBeRemovedEventHandlers) { 539 eventHandlers.remove(zombie) 540 val temp5 = mFirstEventHandler 541 if (temp5 != null && zombie.equals(temp5.first)) { 542 mFirstEventHandler = null 543 } 544 } 545 mToBeRemovedEventHandlers.clear() 546 } 547 // Add new handlers 548 if (mToBeAddedFirstEventHandler != null) { 549 mFirstEventHandler = mToBeAddedFirstEventHandler 550 mToBeAddedFirstEventHandler = null 551 } 552 if (mToBeAddedEventHandlers.size > 0) { 553 for (food in mToBeAddedEventHandlers.entries) { 554 eventHandlers.put(food.key, food.value) 555 } 556 } 557 } 558 } 559 } 560 561 /** 562 * Adds or updates an event handler. This uses a LinkedHashMap so that we can 563 * replace fragments based on the view id they are being expanded into. 564 * 565 * @param key The view id or placeholder for this handler 566 * @param eventHandler Typically a fragment or activity in the calendar app 567 */ registerEventHandlernull568 fun registerEventHandler(key: Int, eventHandler: EventHandler?) { 569 synchronized(this) { 570 if (mDispatchInProgressCounter > 0) { 571 mToBeAddedEventHandlers.put(key, 572 eventHandler as CalendarController.EventHandler) 573 } else { 574 eventHandlers.put(key, eventHandler as CalendarController.EventHandler) 575 } 576 } 577 } 578 registerFirstEventHandlernull579 fun registerFirstEventHandler(key: Int, eventHandler: EventHandler?) { 580 synchronized(this) { 581 registerEventHandler(key, eventHandler) 582 if (mDispatchInProgressCounter > 0) { 583 mToBeAddedFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler) 584 } else { 585 mFirstEventHandler = Pair<Int, EventHandler>(key, eventHandler) 586 } 587 } 588 } 589 deregisterEventHandlernull590 fun deregisterEventHandler(key: Int) { 591 synchronized(this) { 592 if (mDispatchInProgressCounter > 0) { 593 // To avoid ConcurrencyException, stash away the event handler for now. 594 mToBeRemovedEventHandlers.add(key) 595 } else { 596 eventHandlers.remove(key) 597 val temp6 = mFirstEventHandler 598 if (temp6 != null && temp6.first == key) { 599 mFirstEventHandler = null 600 } else {} 601 } 602 } 603 } 604 deregisterAllEventHandlersnull605 fun deregisterAllEventHandlers() { 606 synchronized(this) { 607 if (mDispatchInProgressCounter > 0) { 608 // To avoid ConcurrencyException, stash away the event handler for now. 609 mToBeRemovedEventHandlers.addAll(eventHandlers.keys) 610 } else { 611 eventHandlers.clear() 612 mFirstEventHandler = null 613 } 614 } 615 } 616 617 // FRAG_TODO doesn't work yet filterBroadcastsnull618 fun filterBroadcasts(sender: Object?, eventTypes: Long) { 619 filters.put(sender, eventTypes) 620 } 621 /** 622 * @return the time that this controller is currently pointed at 623 */ 624 /** 625 * Set the time this controller is currently pointed at 626 * 627 * @param millisTime Time since epoch in millis 628 */ 629 var time: Long? 630 get() = mTime?.toMillis(false) 631 set(millisTime) { 632 mTime?.set(millisTime as Long) 633 } 634 launchViewEventnull635 fun launchViewEvent(eventId: Long, startMillis: Long, endMillis: Long, response: Int) { 636 val intent = Intent(Intent.ACTION_VIEW) 637 val eventUri: Uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId) 638 intent.setData(eventUri) 639 intent.setClass(mContext as Context, AllInOneActivity::class.java) 640 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis) 641 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis) 642 intent.putExtra(ATTENDEE_STATUS, response) 643 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 644 mContext?.startActivity(intent) 645 } 646 eventInfoToStringnull647 private fun eventInfoToString(eventInfo: EventInfo): String { 648 var tmp = "Unknown" 649 val builder = StringBuilder() 650 if (eventInfo.eventType and EventType.GO_TO != 0L) { 651 tmp = "Go to time/event" 652 } else if (eventInfo.eventType and EventType.VIEW_EVENT != 0L) { 653 tmp = "View event" 654 } else if (eventInfo.eventType and EventType.VIEW_EVENT_DETAILS != 0L) { 655 tmp = "View details" 656 } else if (eventInfo.eventType and EventType.EVENTS_CHANGED != 0L) { 657 tmp = "Refresh events" 658 } else if (eventInfo.eventType and EventType.USER_HOME != 0L) { 659 tmp = "Gone home" 660 } else if (eventInfo.eventType and EventType.UPDATE_TITLE != 0L) { 661 tmp = "Update title" 662 } 663 builder.append(tmp) 664 builder.append(": id=") 665 builder.append(eventInfo.id) 666 builder.append(", selected=") 667 builder.append(eventInfo.selectedTime) 668 builder.append(", start=") 669 builder.append(eventInfo.startTime) 670 builder.append(", end=") 671 builder.append(eventInfo.endTime) 672 builder.append(", viewType=") 673 builder.append(eventInfo.viewType) 674 builder.append(", x=") 675 builder.append(eventInfo.x) 676 builder.append(", y=") 677 builder.append(eventInfo.y) 678 return builder.toString() 679 } 680 681 companion object { 682 private const val DEBUG = false 683 private const val TAG = "CalendarController" 684 const val EVENT_EDIT_ON_LAUNCH = "editMode" 685 const val MIN_CALENDAR_YEAR = 1970 686 const val MAX_CALENDAR_YEAR = 2036 687 const val MIN_CALENDAR_WEEK = 0 688 const val MAX_CALENDAR_WEEK = 3497 // weeks between 1/1/1970 and 1/1/2037 689 private val instances: WeakHashMap<Context, WeakReference<CalendarController>> = 690 WeakHashMap<Context, WeakReference<CalendarController>>() 691 692 /** 693 * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time 694 * can be ignored 695 */ 696 const val EXTRA_GOTO_DATE: Long = 1 697 const val EXTRA_GOTO_TIME: Long = 2 698 const val EXTRA_GOTO_BACK_TO_PREVIOUS: Long = 4 699 const val EXTRA_GOTO_TODAY: Long = 8 700 701 /** 702 * Creates and/or returns an instance of CalendarController associated with 703 * the supplied context. It is best to pass in the current Activity. 704 * 705 * @param context The activity if at all possible. 706 */ getInstancenull707 @JvmStatic fun getInstance(context: Context?): CalendarController? { 708 synchronized(instances) { 709 var controller: CalendarController? = null 710 val weakController: WeakReference<CalendarController>? = instances.get(context) 711 if (weakController != null) { 712 controller = weakController.get() 713 } 714 if (controller == null) { 715 controller = CalendarController(context) 716 instances.put(context, WeakReference(controller)) 717 } 718 return controller 719 } 720 } 721 722 /** 723 * Removes an instance when it is no longer needed. This should be called in 724 * an activity's onDestroy method. 725 * 726 * @param context The activity used to create the controller 727 */ removeInstancenull728 @JvmStatic fun removeInstance(context: Context?) { 729 instances.remove(context) 730 } 731 } 732 733 init { 734 mContext = context 735 mUpdateTimezone.run() 736 mTime?.setToNow() 737 mDetailViewType = Utils.getSharedPreference( 738 mContext, 739 GeneralPreferences.KEY_DETAILED_VIEW, 740 GeneralPreferences.DEFAULT_DETAILED_VIEW 741 ) 742 } 743 }