summaryrefslogtreecommitdiff
path: root/src/com/android/calendar/Utils.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/Utils.kt')
-rw-r--r--src/com/android/calendar/Utils.kt1577
1 files changed, 0 insertions, 1577 deletions
diff --git a/src/com/android/calendar/Utils.kt b/src/com/android/calendar/Utils.kt
deleted file mode 100644
index ef780485..00000000
--- a/src/com/android/calendar/Utils.kt
+++ /dev/null
@@ -1,1577 +0,0 @@
-/*
- * 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
-
-import android.app.Activity
-import android.content.ComponentName
-import android.content.ContentResolver
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.content.res.Resources
-import android.database.Cursor
-import android.database.MatrixCursor
-import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.LayerDrawable
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.os.Handler
-import android.provider.CalendarContract.Calendars
-import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME
-import android.text.TextUtils
-import android.text.format.DateFormat
-import android.text.format.DateUtils
-import android.text.format.Time
-import android.util.Log
-import com.android.calendar.CalendarController.ViewType
-import com.android.calendar.CalendarUtils.TimeZoneUtils
-import java.util.ArrayList
-import java.util.Arrays
-import java.util.Calendar
-import java.util.Formatter
-import java.util.HashMap
-import java.util.LinkedHashSet
-import java.util.LinkedList
-import java.util.List
-import java.util.Locale
-import java.util.TimeZone
-import java.util.regex.Pattern
-
-object Utils {
- private const val DEBUG = false
- private const val TAG = "CalUtils"
-
- // Set to 0 until we have UI to perform undo
- const val UNDO_DELAY: Long = 0
-
- // For recurring events which instances of the series are being modified
- const val MODIFY_UNINITIALIZED = 0
- const val MODIFY_SELECTED = 1
- const val MODIFY_ALL_FOLLOWING = 2
- const val MODIFY_ALL = 3
-
- // When the edit event view finishes it passes back the appropriate exit
- // code.
- const val DONE_REVERT = 1 shl 0
- const val DONE_SAVE = 1 shl 1
- const val DONE_DELETE = 1 shl 2
-
- // And should re run with DONE_EXIT if it should also leave the view, just
- // exiting is identical to reverting
- const val DONE_EXIT = 1 shl 0
- const val OPEN_EMAIL_MARKER = " <"
- const val CLOSE_EMAIL_MARKER = ">"
- const val INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW"
- const val INTENT_KEY_VIEW_TYPE = "VIEW"
- const val INTENT_VALUE_VIEW_TYPE_DAY = "DAY"
- const val INTENT_KEY_HOME = "KEY_HOME"
- val MONDAY_BEFORE_JULIAN_EPOCH: Int = Time.EPOCH_JULIAN_DAY - 3
- const val DECLINED_EVENT_ALPHA = 0x66
- const val DECLINED_EVENT_TEXT_ALPHA = 0xC0
- private const val SATURATION_ADJUST = 1.3f
- private const val INTENSITY_ADJUST = 0.8f
-
- // Defines used by the DNA generation code
- const val DAY_IN_MINUTES = 60 * 24
- const val WEEK_IN_MINUTES = DAY_IN_MINUTES * 7
-
- // The work day is being counted as 6am to 8pm
- var WORK_DAY_MINUTES = 14 * 60
- var WORK_DAY_START_MINUTES = 6 * 60
- var WORK_DAY_END_MINUTES = 20 * 60
- var WORK_DAY_END_LENGTH = 24 * 60 - WORK_DAY_END_MINUTES
- var CONFLICT_COLOR = -0x1000000
- var mMinutesLoaded = false
- const val YEAR_MIN = 1970
- const val YEAR_MAX = 2036
-
- // The name of the shared preferences file. This name must be maintained for
- // historical
- // reasons, as it's what PreferenceManager assigned the first time the file
- // was created.
- const val SHARED_PREFS_NAME = "com.android.calendar_preferences"
- const val KEY_QUICK_RESPONSES = "preferences_quick_responses"
- const val KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen"
- const val APPWIDGET_DATA_TYPE = "vnd.android.data/update"
- const val MACHINE_GENERATED_ADDRESS = "calendar.google.com"
- private val mTZUtils: TimeZoneUtils? = TimeZoneUtils(SHARED_PREFS_NAME)
- @JvmField var allowWeekForDetailView = false
- internal var tardis: Long = 0
- private set
- private var sVersion: String? = null
- private val mWildcardPattern: Pattern = Pattern.compile("^.*$")
-
- /**
- * A coordinate must be of the following form for Google Maps to correctly use it:
- * Latitude, Longitude
- *
- * This may be in decimal form:
- * Latitude: {-90 to 90}
- * Longitude: {-180 to 180}
- *
- * Or, in degrees, minutes, and seconds:
- * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
- * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
- * + or - degrees may also be represented with N or n, S or s for latitude, and with
- * E or e, W or w for longitude, where the direction may either precede or follow the value.
- *
- * Some examples of coordinates that will be accepted by the regex:
- * 37.422081°, -122.084576°
- * 37.422081,-122.084576
- * +37°25'19.49", -122°5'4.47"
- * 37°25'19.49"N, 122°5'4.47"W
- * N 37° 25' 19.49", W 122° 5' 4.47"
- */
- private const val COORD_DEGREES_LATITUDE = ("([-+NnSs]" + "(\\s)*)?" +
- "[1-9]?[0-9](\u00B0)" + "(\\s)*" +
- "([1-5]?[0-9]\')?" + "(\\s)*" +
- "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
- "((\\s)*" + "[NnSs])?")
- private const val COORD_DEGREES_LONGITUDE = ("([-+EeWw]" + "(\\s)*)?" +
- "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*" +
- "([1-5]?[0-9]\')?" + "(\\s)*" +
- "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" +
- "((\\s)*" + "[EeWw])?")
- private const val COORD_DEGREES_PATTERN = (COORD_DEGREES_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
- COORD_DEGREES_LONGITUDE)
- private const val COORD_DECIMAL_LATITUDE = ("[+-]?" +
- "[1-9]?[0-9]" + "(\\.[0-9]+)" +
- "(\u00B0)?")
- private const val COORD_DECIMAL_LONGITUDE = ("[+-]?" +
- "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)" +
- "(\u00B0)?")
- private const val COORD_DECIMAL_PATTERN = (COORD_DECIMAL_LATITUDE + "(\\s)*" + "," + "(\\s)*" +
- COORD_DECIMAL_LONGITUDE)
- private val COORD_PATTERN: Pattern =
- Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN)
- private const val NANP_ALLOWED_SYMBOLS = "()+-*#."
- private const val NANP_MIN_DIGITS = 7
- private const val NANP_MAX_DIGITS = 11
-
- /**
- * Returns whether the SDK is the KeyLimePie release or later.
- */
- @JvmStatic fun isKeyLimePieOrLater(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
- }
-
- /**
- * Returns whether the SDK is the Jellybean release or later.
- */
- @JvmStatic fun isJellybeanOrLater(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
- }
-
- @JvmStatic fun getViewTypeFromIntentAndSharedPref(activity: Activity): Int {
- val intent: Intent? = activity.getIntent()
- val extras: Bundle? = intent?.getExtras()
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(activity)
- if (TextUtils.equals(intent?.getAction(), Intent.ACTION_EDIT)) {
- return ViewType.EDIT
- }
- if (extras != null) {
- if (extras?.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
- // This is the "detail" view which is either agenda or day view
- return prefs?.getInt(
- GeneralPreferences.KEY_DETAILED_VIEW,
- GeneralPreferences.DEFAULT_DETAILED_VIEW
- ) as Int
- } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras?.getString(INTENT_KEY_VIEW_TYPE))) {
- // Not sure who uses this. This logic came from LaunchActivity
- return ViewType.DAY
- }
- }
-
- // Default to the last view
- return prefs?.getInt(
- GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW
- ) as Int
- }
-
- /**
- * Gets the intent action for telling the widget to update.
- */
- @JvmStatic fun getWidgetUpdateAction(context: Context): String {
- return context.getPackageName().toString() + ".APPWIDGET_UPDATE"
- }
-
- /**
- * Gets the intent action for telling the widget to update.
- */
- @JvmStatic fun getWidgetScheduledUpdateAction(context: Context): String {
- return context.getPackageName().toString() + ".APPWIDGET_SCHEDULED_UPDATE"
- }
-
- /**
- * Writes a new home time zone to the db. Updates the home time zone in the
- * db asynchronously and updates the local cache. Sending a time zone of
- * **tbd** will cause it to be set to the device's time zone. null or empty
- * tz will be ignored.
- *
- * @param context The calling activity
- * @param timeZone The time zone to set Calendar to, or **tbd**
- */
- @JvmStatic fun setTimeZone(context: Context?, timeZone: String?) {
- mTZUtils?.setTimeZone(context as Context, timeZone as String)
- }
-
- /**
- * Gets the time zone that Calendar should be displayed in This is a helper
- * method to get the appropriate time zone for Calendar. If this is the
- * first time this method has been called it will initiate an asynchronous
- * query to verify that the data in preferences is correct. The callback
- * supplied will only be called if this query returns a value other than
- * what is stored in preferences and should cause the calling activity to
- * refresh anything that depends on calling this method.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns new
- * values
- * @return The string value representing the time zone Calendar should
- * display
- */
- @JvmStatic fun getTimeZone(context: Context?, callback: Runnable?): String? {
- return mTZUtils?.getTimeZone(context as Context, callback)
- }
-
- /**
- * Formats a date or a time range according to the local conventions.
- *
- * @param context the context is required only if the time is shown
- * @param startMillis the start time in UTC milliseconds
- * @param endMillis the end time in UTC milliseconds
- * @param flags a bit mask of options See [formatDateRange][DateUtils.formatDateRange]
- * @return a string containing the formatted date/time range.
- */
- @JvmStatic fun formatDateRange(
- context: Context?,
- startMillis: Long,
- endMillis: Long,
- flags: Int
- ): String? {
- return mTZUtils?.formatDateRange(context as Context, startMillis, endMillis, flags)
- }
-
- @JvmStatic fun getDefaultVibrate(context: Context, prefs: SharedPreferences?): Boolean {
- val vibrate: Boolean
- if (prefs?.contains(KEY_ALERTS_VIBRATE_WHEN) == true) {
- // Migrate setting to new 4.2 behavior
- //
- // silent and never -> off
- // always -> on
- val vibrateWhen: String? = prefs?.getString(KEY_ALERTS_VIBRATE_WHEN, null)
- vibrate = vibrateWhen != null && vibrateWhen.equals(
- context
- .getString(R.string.prefDefault_alerts_vibrate_true)
- )
- prefs?.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit()
- Log.d(
- TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" +
- vibrateWhen + ") to KEY_ALERTS_VIBRATE = " + vibrate
- )
- } else {
- vibrate = prefs?.getBoolean(
- GeneralPreferences.KEY_ALERTS_VIBRATE,
- false
- ) as Boolean
- }
- return vibrate
- }
-
- @JvmStatic fun getSharedPreference(
- context: Context?,
- key: String?,
- defaultValue: Array<String>?
- ): Array<String>? {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val ss = prefs?.getStringSet(key, null)
- if (ss != null) {
- val strings = arrayOfNulls<String>(ss?.size)
- return ss?.toTypedArray()
- }
- return defaultValue
- }
-
- @JvmStatic fun getSharedPreference(
- context: Context?,
- key: String?,
- defaultValue: String?
- ): String? {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getString(key, defaultValue)
- }
-
- @JvmStatic fun getSharedPreference(context: Context?, key: String?, defaultValue: Int): Int {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getInt(key, defaultValue) as Int
- }
-
- @JvmStatic fun getSharedPreference(
- context: Context?,
- key: String?,
- defaultValue: Boolean
- ): Boolean {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getBoolean(key, defaultValue) as Boolean
- }
-
- /**
- * Asynchronously sets the preference with the given key to the given value
- *
- * @param context the context to use to get preferences from
- * @param key the key of the preference to set
- * @param value the value to set
- */
- @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: String?) {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- prefs?.edit()?.putString(key, value)?.apply()
- }
-
- @JvmStatic fun setSharedPreference(context: Context?, key: String?, values: Array<String?>) {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val set: LinkedHashSet<String?> = LinkedHashSet<String?>()
- for (value in values) {
- set.add(value)
- }
- prefs?.edit()?.putStringSet(key, set)?.apply()
- }
-
- internal fun tardis() {
- tardis = System.currentTimeMillis()
- }
-
- @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Boolean) {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val editor: SharedPreferences.Editor? = prefs?.edit()
- editor?.putBoolean(key, value)
- editor?.apply()
- }
-
- @JvmStatic fun setSharedPreference(context: Context?, key: String?, value: Int) {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val editor: SharedPreferences.Editor? = prefs?.edit()
- editor?.putInt(key, value)
- editor?.apply()
- }
-
- @JvmStatic fun removeSharedPreference(context: Context?, key: String?) {
- val prefs: SharedPreferences? = context?.getSharedPreferences(
- GeneralPreferences.SHARED_PREFS_NAME, Context.MODE_PRIVATE
- )
- prefs?.edit()?.remove(key)?.apply()
- }
-
- /**
- * Save default agenda/day/week/month view for next time
- *
- * @param context
- * @param viewId [CalendarController.ViewType]
- */
- @JvmStatic fun setDefaultView(context: Context?, viewId: Int) {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val editor: SharedPreferences.Editor? = prefs?.edit()
- var validDetailView = false
- validDetailView =
- if (allowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
- true
- } else {
- (viewId == CalendarController.ViewType.AGENDA ||
- viewId == CalendarController.ViewType.DAY)
- }
- if (validDetailView) {
- // Record the detail start view
- editor?.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId)
- }
-
- // Record the (new) start view
- editor?.putInt(GeneralPreferences.KEY_START_VIEW, viewId)
- editor?.apply()
- }
-
- @JvmStatic fun matrixCursorFromCursor(cursor: Cursor?): MatrixCursor? {
- if (cursor == null) {
- return null
- }
- var columnNames: Array<String?> = cursor.getColumnNames()
- if (columnNames == null) {
- columnNames = arrayOf()
- }
- val newCursor = MatrixCursor(columnNames)
- val numColumns: Int = cursor.getColumnCount()
- val data = arrayOfNulls<String>(numColumns)
- cursor.moveToPosition(-1)
- while (cursor.moveToNext()) {
- for (i in 0 until numColumns) {
- data[i] = cursor.getString(i)
- }
- newCursor.addRow(data)
- }
- return newCursor
- }
-
- /**
- * Compares two cursors to see if they contain the same data.
- *
- * @return Returns true of the cursors contain the same data and are not
- * null, false otherwise
- */
- @JvmStatic fun compareCursors(c1: Cursor?, c2: Cursor?): Boolean {
- if (c1 == null || c2 == null) {
- return false
- }
- val numColumns: Int = c1.getColumnCount()
- if (numColumns != c2.getColumnCount()) {
- return false
- }
- if (c1.getCount() !== c2.getCount()) {
- return false
- }
- c1.moveToPosition(-1)
- c2.moveToPosition(-1)
- while (c1.moveToNext() && c2.moveToNext()) {
- for (i in 0 until numColumns) {
- if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
- return false
- }
- }
- }
- return true
- }
-
- /**
- * If the given intent specifies a time (in milliseconds since the epoch),
- * then that time is returned. Otherwise, the current time is returned.
- */
- @JvmStatic fun timeFromIntentInMillis(intent: Intent?): Long? {
- // If the time was specified, then use that. Otherwise, use the current
- // time.
- val data: Uri? = intent?.getData()
- var millis: Long? = intent?.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1)?.toLong()
- if (millis == -1L && data != null && data?.isHierarchical()) {
- val path: List<String> = data?.getPathSegments() as List<String>
- if (path.size == 2 && path[0].equals("time")) {
- try {
- millis = (data?.getLastPathSegment()?.toLong())
- } catch (e: NumberFormatException) {
- Log.i(
- "Calendar", "timeFromIntentInMillis: Data existed but no valid time " +
- "found. Using current time."
- )
- }
- }
- }
- if ((millis ?: 0L) <= 0) {
- millis = System.currentTimeMillis()
- }
- return millis
- }
-
- /**
- * Formats the given Time object so that it gives the month and year (for
- * example, "September 2007").
- *
- * @param time the time to format
- * @return the string containing the weekday and the date
- */
- @JvmStatic fun formatMonthYear(context: Context?, time: Time): String? {
- val flags: Int = (DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY
- or DateUtils.FORMAT_SHOW_YEAR)
- val millis: Long = time.toMillis(true)
- return formatDateRange(context, millis, millis, flags)
- }
-
- /**
- * Returns a list joined together by the provided delimiter, for example,
- * ["a", "b", "c"] could be joined into "a,b,c"
- *
- * @param things the things to join together
- * @param delim the delimiter to use
- * @return a string contained the things joined together
- */
- @JvmStatic fun join(things: List<*>, delim: String?): String {
- val builder = StringBuilder()
- var first = true
- for (thing in things) {
- if (first) {
- first = false
- } else {
- builder.append(delim)
- }
- builder.append(thing.toString())
- }
- return builder.toString()
- }
-
- /**
- * Returns the week since [Time.EPOCH_JULIAN_DAY] (Jan 1, 1970)
- * adjusted for first day of week.
- *
- * This takes a julian day and the week start day and calculates which
- * week since [Time.EPOCH_JULIAN_DAY] that day occurs in, starting
- * at 0. *Do not* use this to compute the ISO week number for the year.
- *
- * @param julianDay The julian day to calculate the week number for
- * @param firstDayOfWeek Which week day is the first day of the week,
- * see [Time.SUNDAY]
- * @return Weeks since the epoch
- */
- @JvmStatic fun getWeeksSinceEpochFromJulianDay(julianDay: Int, firstDayOfWeek: Int): Int {
- var diff: Int = Time.THURSDAY - firstDayOfWeek
- if (diff < 0) {
- diff += 7
- }
- val refDay: Int = Time.EPOCH_JULIAN_DAY - diff
- return (julianDay - refDay) / 7
- }
-
- /**
- * Takes a number of weeks since the epoch and calculates the Julian day of
- * the Monday for that week.
- *
- * This assumes that the week containing the [Time.EPOCH_JULIAN_DAY]
- * is considered week 0. It returns the Julian day for the Monday
- * `week` weeks after the Monday of the week containing the epoch.
- *
- * @param week Number of weeks since the epoch
- * @return The julian day for the Monday of the given week since the epoch
- */
- @JvmStatic fun getJulianMondayFromWeeksSinceEpoch(week: Int): Int {
- return MONDAY_BEFORE_JULIAN_EPOCH + week * 7
- }
-
- /**
- * Get first day of week as android.text.format.Time constant.
- *
- * @return the first day of week in android.text.format.Time
- */
- @JvmStatic fun getFirstDayOfWeek(context: Context?): Int {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- val pref: String? = prefs?.getString(
- GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT
- )
- val startDay: Int
- startDay = if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
- Calendar.getInstance().getFirstDayOfWeek()
- } else {
- Integer.parseInt(pref)
- }
- return if (startDay == Calendar.SATURDAY) {
- Time.SATURDAY
- } else if (startDay == Calendar.MONDAY) {
- Time.MONDAY
- } else {
- Time.SUNDAY
- }
- }
-
- /**
- * Get first day of week as java.util.Calendar constant.
- *
- * @return the first day of week as a java.util.Calendar constant
- */
- @JvmStatic fun getFirstDayOfWeekAsCalendar(context: Context?): Int {
- return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context))
- }
-
- /**
- * Converts the day of the week from android.text.format.Time to java.util.Calendar
- */
- @JvmStatic fun convertDayOfWeekFromTimeToCalendar(timeDayOfWeek: Int): Int {
- return when (timeDayOfWeek) {
- Time.MONDAY -> Calendar.MONDAY
- Time.TUESDAY -> Calendar.TUESDAY
- Time.WEDNESDAY -> Calendar.WEDNESDAY
- Time.THURSDAY -> Calendar.THURSDAY
- Time.FRIDAY -> Calendar.FRIDAY
- Time.SATURDAY -> Calendar.SATURDAY
- Time.SUNDAY -> Calendar.SUNDAY
- else -> throw IllegalArgumentException(
- "Argument must be between Time.SUNDAY and " +
- "Time.SATURDAY"
- )
- }
- }
-
- /**
- * @return true when week number should be shown.
- */
- @JvmStatic fun getShowWeekNumber(context: Context?): Boolean {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getBoolean(
- GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM
- ) as Boolean
- }
-
- /**
- * @return true when declined events should be hidden.
- */
- @JvmStatic fun getHideDeclinedEvents(context: Context?): Boolean {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false) as Boolean
- }
-
- @JvmStatic fun getDaysPerWeek(context: Context?): Int {
- val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(context)
- return prefs?.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7) as Int
- }
-
- /**
- * Determine whether the column position is Saturday or not.
- *
- * @param column the column position
- * @param firstDayOfWeek the first day of week in android.text.format.Time
- * @return true if the column is Saturday position
- */
- @JvmStatic fun isSaturday(column: Int, firstDayOfWeek: Int): Boolean {
- return (firstDayOfWeek == Time.SUNDAY && column == 6 ||
- firstDayOfWeek == Time.MONDAY && column == 5 ||
- firstDayOfWeek == Time.SATURDAY && column == 0)
- }
-
- /**
- * Determine whether the column position is Sunday or not.
- *
- * @param column the column position
- * @param firstDayOfWeek the first day of week in android.text.format.Time
- * @return true if the column is Sunday position
- */
- @JvmStatic fun isSunday(column: Int, firstDayOfWeek: Int): Boolean {
- return (firstDayOfWeek == Time.SUNDAY && column == 0 ||
- firstDayOfWeek == Time.MONDAY && column == 6 ||
- firstDayOfWeek == Time.SATURDAY && column == 1)
- }
-
- /**
- * Convert given UTC time into current local time. This assumes it is for an
- * allday event and will adjust the time to be on a midnight boundary.
- *
- * @param recycle Time object to recycle, otherwise null.
- * @param utcTime Time to convert, in UTC.
- * @param tz The time zone to convert this time to.
- */
- @JvmStatic fun convertAlldayUtcToLocal(recycle: Time?, utcTime: Long, tz: String): Long {
- var recycle: Time? = recycle
- if (recycle == null) {
- recycle = Time()
- }
- recycle.timezone = Time.TIMEZONE_UTC
- recycle.set(utcTime)
- recycle.timezone = tz
- return recycle.normalize(true)
- }
-
- @JvmStatic fun convertAlldayLocalToUTC(recycle: Time?, localTime: Long, tz: String): Long {
- var recycle: Time? = recycle
- if (recycle == null) {
- recycle = Time()
- }
- recycle.timezone = tz
- recycle.set(localTime)
- recycle.timezone = Time.TIMEZONE_UTC
- return recycle.normalize(true)
- }
-
- /**
- * Finds and returns the next midnight after "theTime" in milliseconds UTC
- *
- * @param recycle - Time object to recycle, otherwise null.
- * @param theTime - Time used for calculations (in UTC)
- * @param tz The time zone to convert this time to.
- */
- @JvmStatic fun getNextMidnight(recycle: Time?, theTime: Long, tz: String): Long {
- var recycle: Time? = recycle
- if (recycle == null) {
- recycle = Time()
- }
- recycle.timezone = tz
- recycle.set(theTime)
- recycle.monthDay++
- recycle.hour = 0
- recycle.minute = 0
- recycle.second = 0
- return recycle.normalize(true)
- }
-
- @JvmStatic fun setAllowWeekForDetailView(allowWeekView: Boolean) {
- this.allowWeekForDetailView = allowWeekView
- }
-
- @JvmStatic fun getAllowWeekForDetailView(): Boolean {
- return this.allowWeekForDetailView
- }
-
- @JvmStatic fun getConfigBool(c: Context, key: Int): Boolean {
- return c.getResources().getBoolean(key)
- }
-
- /**
- * For devices with Jellybean or later, darkens the given color to ensure that white text is
- * clearly visible on top of it. For devices prior to Jellybean, does nothing, as the
- * sync adapter handles the color change.
- *
- * @param color
- */
- @JvmStatic fun getDisplayColorFromColor(color: Int): Int {
- if (!isJellybeanOrLater()) {
- return color
- }
- val hsv = FloatArray(3)
- Color.colorToHSV(color, hsv)
- hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f)
- hsv[2] = hsv[2] * INTENSITY_ADJUST
- return Color.HSVToColor(hsv)
- }
-
- // This takes a color and computes what it would look like blended with
- // white. The result is the color that should be used for declined events.
- @JvmStatic fun getDeclinedColorFromColor(color: Int): Int {
- val bg = -0x1
- val a = DECLINED_EVENT_ALPHA
- val r = (color and 0x00ff0000) * a + (bg and 0x00ff0000) * (0xff - a) and -0x1000000
- val g = (color and 0x0000ff00) * a + (bg and 0x0000ff00) * (0xff - a) and 0x00ff0000
- val b = (color and 0x000000ff) * a + (bg and 0x000000ff) * (0xff - a) and 0x0000ff00
- return -0x1000000 or (r or g or b shr 8)
- }
-
- @JvmStatic fun trySyncAndDisableUpgradeReceiver(context: Context?) {
- val pm: PackageManager? = context?.getPackageManager()
- val upgradeComponent = ComponentName(context as Context, UpgradeReceiver::class.java)
- if (pm?.getComponentEnabledSetting(upgradeComponent) ===
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- ) {
- // The upgrade receiver has been disabled, which means this code has been run before,
- // so no need to sync.
- return
- }
- val extras = Bundle()
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
- ContentResolver.requestSync(
- null /* no account */,
- Calendars.CONTENT_URI.getAuthority(),
- extras
- )
-
- // Now unregister the receiver so that we won't continue to sync every time.
- pm?.setComponentEnabledSetting(
- upgradeComponent,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
- )
- }
-
- /**
- * Converts a list of events to a list of segments to draw. Assumes list is
- * ordered by start time of the events. The function processes events for a
- * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1.
- * The algorithm goes over all the events and creates a set of segments
- * ordered by start time. This list of segments is then converted into a
- * HashMap of strands which contain the draw points and are organized by
- * color. The strands can then be drawn by setting the paint color to each
- * strand's color and calling drawLines on its set of points. The points are
- * set up using the following parameters.
- *
- * * Events between midnight and WORK_DAY_START_MINUTES are compressed
- * into the first 1/8th of the space between top and bottom.
- * * Events between WORK_DAY_END_MINUTES and the following midnight are
- * compressed into the last 1/8th of the space between top and bottom
- * * Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use
- * the remaining 3/4ths of the space
- * * All segments drawn will maintain at least minPixels height, except
- * for conflicts in the first or last 1/8th, which may be smaller
- *
- *
- * @param firstJulianDay The julian day of the first day of events
- * @param events A list of events sorted by start time
- * @param top The lowest y value the dna should be drawn at
- * @param bottom The highest y value the dna should be drawn at
- * @param dayXs An array of x values to draw the dna at, one for each day
- * @param conflictColor the color to use for conflicts
- * @return
- */
- @JvmStatic fun createDNAStrands(
- firstJulianDay: Int,
- events: ArrayList<Event?>?,
- top: Int,
- bottom: Int,
- minPixels: Int,
- dayXs: IntArray?,
- context: Context?
- ): HashMap<Int, DNAStrand>? {
- if (!mMinutesLoaded) {
- if (context == null) {
- Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA.")
- }
- val res: Resources? = context?.getResources()
- CONFLICT_COLOR = res?.getColor(R.color.month_dna_conflict_time_color) as Int
- WORK_DAY_START_MINUTES = res?.getInteger(R.integer.work_start_minutes) as Int
- WORK_DAY_END_MINUTES = res?.getInteger(R.integer.work_end_minutes) as Int
- WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES
- WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES
- mMinutesLoaded = true
- }
- if (events == null || events.isEmpty() || dayXs == null || dayXs.size < 1 ||
- bottom - top < 8 || minPixels < 0) {
- Log.e(
- TAG,
- "Bad values for createDNAStrands! events:" + events + " dayXs:" +
- Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:" +
- minPixels
- )
- return null
- }
- val segments: LinkedList<DNASegment> = LinkedList<DNASegment>()
- val strands: HashMap<Int, DNAStrand> = HashMap<Int, DNAStrand>()
- // add a black strand by default, other colors will get added in
- // the loop
- val blackStrand = DNAStrand()
- blackStrand.color = CONFLICT_COLOR
- strands.put(CONFLICT_COLOR, blackStrand)
- // the min length is the number of minutes that will occupy
- // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the
- // minutes/pixel * minpx where the number of pixels are 3/4 the total
- // dna height: 4*(mins/(px * 3/4))
- val minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top))
-
- // There are slightly fewer than half as many pixels in 1/6 the space,
- // so round to 2.5x for the min minutes in the non-work area
- val minOtherMinutes = minMinutes * 5 / 2
- val lastJulianDay = firstJulianDay + dayXs.size - 1
- val event = Event()
- // Go through all the events for the week
- for (currEvent in events) {
- // if this event is outside the weeks range skip it
- if (currEvent != null &&
- (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay)) {
- continue
- }
- if (currEvent?.drawAsAllday() == true) {
- addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.size)
- continue
- }
- // Copy the event over so we can clip its start and end to our range
- currEvent?.copyTo(event)
- if (event.startDay < firstJulianDay) {
- event.startDay = firstJulianDay
- event.startTime = 0
- }
- // If it starts after the work day make sure the start is at least
- // minPixels from midnight
- if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) {
- event.startTime = DAY_IN_MINUTES - minOtherMinutes
- }
- if (event.endDay > lastJulianDay) {
- event.endDay = lastJulianDay
- event.endTime = DAY_IN_MINUTES - 1
- }
- // If the end time is before the work day make sure it ends at least
- // minPixels after midnight
- if (event.endTime < minOtherMinutes) {
- event.endTime = minOtherMinutes
- }
- // If the start and end are on the same day make sure they are at
- // least minPixels apart. This only needs to be done for times
- // outside the work day as the min distance for within the work day
- // is enforced in the segment code.
- if (event.startDay === event.endDay &&
- event.endTime - event.startTime < minOtherMinutes
- ) {
- // If it's less than minPixels in an area before the work
- // day
- if (event.startTime < WORK_DAY_START_MINUTES) {
- // extend the end to the first easy guarantee that it's
- // minPixels
- event.endTime = Math.min(
- event.startTime + minOtherMinutes,
- WORK_DAY_START_MINUTES + minMinutes
- )
- // if it's in the area after the work day
- } else if (event.endTime > WORK_DAY_END_MINUTES) {
- // First try shifting the end but not past midnight
- event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1)
- // if it's still too small move the start back
- if (event.endTime - event.startTime < minOtherMinutes) {
- event.startTime = event.endTime - minOtherMinutes
- }
- }
- }
-
- // This handles adding the first segment
- if (segments.size == 0) {
- addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes)
- continue
- }
- // Now compare our current start time to the end time of the last
- // segment in the list
- val lastSegment: DNASegment = segments.getLast()
- var startMinute: Int =
- (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime
- var endMinute: Int = Math.max(
- (event.endDay - firstJulianDay) * DAY_IN_MINUTES +
- event.endTime, startMinute + minMinutes
- )
- if (startMinute < 0) {
- startMinute = 0
- }
- if (endMinute >= WEEK_IN_MINUTES) {
- endMinute = WEEK_IN_MINUTES - 1
- }
- // If we start before the last segment in the list ends we need to
- // start going through the list as this may conflict with other
- // events
- if (startMinute < lastSegment.endMinute) {
- var i: Int = segments.size
- // find the last segment this event intersects with
- while (--i >= 0 && endMinute < segments.get(i).startMinute) {}
-
- var currSegment: DNASegment = DNASegment()
- // for each segment this event intersects with
- while (i >= 0 && startMinute <= segments.get(i)
- .also { currSegment = it }.endMinute) {
-
- // if the segment is already a conflict ignore it
- if (currSegment.color == CONFLICT_COLOR) {
- i--
- continue
- }
- // if the event ends before the segment and wouldn't create
- // a segment that is too small split off the right side
- if (endMinute < currSegment.endMinute - minMinutes) {
- val rhs = DNASegment()
- rhs.endMinute = currSegment.endMinute
- rhs.color = currSegment.color
- rhs.startMinute = endMinute + 1
- rhs.day = currSegment.day
- currSegment.endMinute = endMinute
- segments.add(i + 1, rhs)
- // Equivalent to strands.get(rhs.color)?.count++
- // but there is no null safe invocation for ++
- strands.get(rhs.color)?.count = strands.get(rhs.color)?.count?.inc() as Int
- if (DEBUG) {
- Log.d(
- TAG, "Added rhs, curr:" + currSegment.toString() + " i:" +
- segments.get(i).toString()
- )
- }
- }
- // if the event starts after the segment and wouldn't create
- // a segment that is too small split off the left side
- if (startMinute > currSegment.startMinute + minMinutes) {
- val lhs = DNASegment()
- lhs.startMinute = currSegment.startMinute
- lhs.color = currSegment.color
- lhs.endMinute = startMinute - 1
- lhs.day = currSegment.day
- currSegment.startMinute = startMinute
- // increment i so that we are at the right position when
- // referencing the segments to the right and left of the
- // current segment.
- segments.add(i++, lhs)
- strands.get(lhs.color)?.count = strands.get(lhs.color)?.count?.inc() as Int
- if (DEBUG) {
- Log.d(
- TAG, "Added lhs, curr:" + currSegment.toString() + " i:" +
- segments.get(i).toString()
- )
- }
- }
- // if the right side is black merge this with the segment to
- // the right if they're on the same day and overlap
- if (i + 1 < segments.size) {
- val rhs: DNASegment = segments.get(i + 1)
- if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day &&
- rhs.startMinute <= currSegment.endMinute + 1) {
- rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute)
- segments.remove(currSegment)
- strands.get(currSegment.color)?.count =
- strands.get(currSegment.color)?.count?.dec() as Int
- // point at the new current segment
- currSegment = rhs
- }
- }
- // if the left side is black merge this with the segment to
- // the left if they're on the same day and overlap
- if (i - 1 >= 0) {
- val lhs: DNASegment = segments.get(i - 1)
- if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day &&
- lhs.endMinute >= currSegment.startMinute - 1) {
- lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute)
- segments.remove(currSegment)
- strands.get(currSegment.color)?.count =
- strands.get(currSegment.color)?.count?.dec() as Int
- // point at the new current segment
- currSegment = lhs
- // point i at the new current segment in case new
- // code is added
- i--
- }
- }
- // if we're still not black, decrement the count for the
- // color being removed, change this to black, and increment
- // the black count
- if (currSegment.color != CONFLICT_COLOR) {
- strands.get(currSegment.color)?.count =
- strands.get(currSegment.color)?.count?.dec() as Int
- currSegment.color = CONFLICT_COLOR
- strands.get(CONFLICT_COLOR)?.count =
- strands.get(CONFLICT_COLOR)?.count?.inc() as Int
- }
- i--
- }
- }
- // If this event extends beyond the last segment add a new segment
- if (endMinute > lastSegment.endMinute) {
- addNewSegment(
- segments, event, strands, firstJulianDay, lastSegment.endMinute,
- minMinutes
- )
- }
- }
- weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs)
- return strands
- }
-
- // This figures out allDay colors as allDay events are found
- private fun addAllDayToStrands(
- event: Event?,
- strands: HashMap<Int, DNAStrand>,
- firstJulianDay: Int,
- numDays: Int
- ) {
- val strand = getOrCreateStrand(strands, CONFLICT_COLOR)
- // if we haven't initialized the allDay portion create it now
- if (strand?.allDays == null) {
- strand?.allDays = IntArray(numDays)
- }
-
- // For each day this event is on update the color
- val end: Int = Math.min((event?.endDay ?: 0) - firstJulianDay, numDays - 1)
- for (i in Math.max((event?.startDay ?: 0) - firstJulianDay, 0)..end) {
- if (strand?.allDays!![i] != 0) {
- // if this day already had a color, it is now a conflict
- strand?.allDays!![i] = CONFLICT_COLOR
- } else {
- // else it's just the color of the event
- strand?.allDays!![i] = event?.color as Int
- }
- }
- }
-
- // This processes all the segments, sorts them by color, and generates a
- // list of points to draw
- private fun weaveDNAStrands(
- segments: LinkedList<DNASegment>,
- firstJulianDay: Int,
- strands: HashMap<Int, DNAStrand>,
- top: Int,
- bottom: Int,
- dayXs: IntArray
- ) {
- // First, get rid of any colors that ended up with no segments
- val strandIterator = strands.values.iterator()
- while (strandIterator.hasNext()) {
- val strand = strandIterator.next()
- if (strand?.count < 1 && strand.allDays == null) {
- strandIterator.remove()
- continue
- }
- strand.points = FloatArray(strand.count * 4)
- strand.position = 0
- }
- // Go through each segment and compute its points
- for (segment in segments) {
- // Add the points to the strand of that color
- val strand: DNAStrand? = strands.get(segment.color)
- val dayIndex = segment.day - firstJulianDay
- val dayStartMinute = segment.startMinute % DAY_IN_MINUTES
- val dayEndMinute = segment.endMinute % DAY_IN_MINUTES
- val height = bottom - top
- val workDayHeight = height * 3 / 4
- val remainderHeight = (height - workDayHeight) / 2
- val x = dayXs[dayIndex]
- var y0 = 0
- var y1 = 0
- y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight)
- y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight)
- if (DEBUG) {
- Log.d(
- TAG,
- "Adding " + Integer.toHexString(segment.color).toString() + " at x,y0,y1: " + x
- .toString() + " " + y0.toString() + " " + y1.toString() +
- " for " + dayStartMinute.toString() + " " + dayEndMinute
- )
- }
- strand?.points!![strand?.position] = x.toFloat()
- strand?.position = strand?.position?.inc() as Int
-
- strand?.points!![strand?.position] = y0.toFloat()
- strand?.position = strand?.position?.inc() as Int
-
- strand?.points!![strand?.position] = x.toFloat()
- strand?.position = strand?.position.inc() as Int
-
- strand?.points!![strand?.position] = y1.toFloat()
- strand?.position = strand?.position.inc() as Int
- }
- }
-
- /**
- * Compute a pixel offset from the top for a given minute from the work day
- * height and the height of the top area.
- */
- private fun getPixelOffsetFromMinutes(
- minute: Int,
- workDayHeight: Int,
- remainderHeight: Int
- ): Int {
- val y: Int
- if (minute < WORK_DAY_START_MINUTES) {
- y = minute * remainderHeight / WORK_DAY_START_MINUTES
- } else if (minute < WORK_DAY_END_MINUTES) {
- y = remainderHeight + (minute - WORK_DAY_START_MINUTES) *
- workDayHeight / WORK_DAY_MINUTES
- } else {
- y = remainderHeight + workDayHeight +
- (minute - WORK_DAY_END_MINUTES) * remainderHeight / WORK_DAY_END_LENGTH
- }
- return y
- }
-
- /**
- * Add a new segment based on the event provided. This will handle splitting
- * segments across day boundaries and ensures a minimum size for segments.
- */
- private fun addNewSegment(
- segments: LinkedList<DNASegment>,
- event: Event,
- strands: HashMap<Int, DNAStrand>,
- firstJulianDay: Int,
- minStart: Int,
- minMinutes: Int
- ) {
- var event: Event = event
- var minStart = minStart
- if (event.startDay > event.endDay) {
- Log.wtf(TAG, "Event starts after it ends: " + event.toString())
- }
- // If this is a multiday event split it up by day
- if (event.startDay !== event.endDay) {
- val lhs = Event()
- lhs.color = event.color
- lhs.startDay = event.startDay
- // the first day we want the start time to be the actual start time
- lhs.startTime = event.startTime
- lhs.endDay = lhs.startDay
- lhs.endTime = DAY_IN_MINUTES - 1
- // Nearly recursive iteration!
- while (lhs.startDay !== event.endDay) {
- addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes)
- // The days in between are all day, even though that shouldn't
- // actually happen due to the allday filtering
- lhs.startDay++
- lhs.endDay = lhs.startDay
- lhs.startTime = 0
- minStart = 0
- }
- // The last day we want the end time to be the actual end time
- lhs.endTime = event.endTime
- event = lhs
- }
- // Create the new segment and compute its fields
- val segment = DNASegment()
- val dayOffset: Int = (event.startDay - firstJulianDay) * DAY_IN_MINUTES
- val endOfDay = dayOffset + DAY_IN_MINUTES - 1
- // clip the start if needed
- segment.startMinute = Math.max(dayOffset + event.startTime, minStart)
- // and extend the end if it's too small, but not beyond the end of the
- // day
- val minEnd: Int = Math.min(segment.startMinute + minMinutes, endOfDay)
- segment.endMinute = Math.max(dayOffset + event.endTime, minEnd)
- if (segment.endMinute > endOfDay) {
- segment.endMinute = endOfDay
- }
- segment.color = event.color
- segment.day = event.startDay
- segments.add(segment)
- // increment the count for the correct color or add a new strand if we
- // don't have that color yet
- val strand = getOrCreateStrand(strands, segment.color)
- strand?.count
- strand?.count = strand?.count?.inc() as Int
- }
-
- /**
- * Try to get a strand of the given color. Create it if it doesn't exist.
- */
- private fun getOrCreateStrand(strands: HashMap<Int, DNAStrand>, color: Int): DNAStrand? {
- var strand: DNAStrand? = strands.get(color)
- if (strand == null) {
- strand = DNAStrand()
- strand?.color = color
- strand?.count = 0
- strands?.put(strand?.color, strand)
- }
- return strand
- }
-
- /**
- * Sends an intent to launch the top level Calendar view.
- *
- * @param context
- */
- @JvmStatic fun returnToCalendarHome(context: Context) {
- val launchIntent = Intent(context, AllInOneActivity::class.java)
- launchIntent.setAction(Intent.ACTION_DEFAULT)
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- launchIntent.putExtra(INTENT_KEY_HOME, true)
- context.startActivity(launchIntent)
- }
-
- /**
- * Given a context and a time in millis since unix epoch figures out the
- * correct week of the year for that time.
- *
- * @param millisSinceEpoch
- * @return
- */
- @JvmStatic fun getWeekNumberFromTime(millisSinceEpoch: Long, context: Context?): Int {
- val weekTime = Time(getTimeZone(context, null))
- weekTime.set(millisSinceEpoch)
- weekTime.normalize(true)
- val firstDayOfWeek = getFirstDayOfWeek(context)
- // if the date is on Saturday or Sunday and the start of the week
- // isn't Monday we may need to shift the date to be in the correct
- // week
- if (weekTime.weekDay === Time.SUNDAY &&
- (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)
- ) {
- weekTime.monthDay++
- weekTime.normalize(true)
- } else if (weekTime.weekDay === Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) {
- weekTime.monthDay += 2
- weekTime.normalize(true)
- }
- return weekTime.getWeekNumber()
- }
-
- /**
- * Formats a day of the week string. This is either just the name of the day
- * or a combination of yesterday/today/tomorrow and the day of the week.
- *
- * @param julianDay The julian day to get the string for
- * @param todayJulianDay The julian day for today's date
- * @param millis A utc millis since epoch time that falls on julian day
- * @param context The calling context, used to get the timezone and do the
- * formatting
- * @return
- */
- @JvmStatic fun getDayOfWeekString(
- julianDay: Int,
- todayJulianDay: Int,
- millis: Long,
- context: Context
- ): String {
- getTimeZone(context, null)
- val flags: Int = DateUtils.FORMAT_SHOW_WEEKDAY
- var dayViewText: String
- dayViewText = if (julianDay == todayJulianDay) {
- context.getString(
- R.string.agenda_today,
- mTZUtils?.formatDateRange(context, millis, millis, flags)
- .toString()
- )
- } else if (julianDay == todayJulianDay - 1) {
- context.getString(
- R.string.agenda_yesterday,
- mTZUtils?.formatDateRange(context, millis, millis, flags)
- .toString()
- )
- } else if (julianDay == todayJulianDay + 1) {
- context.getString(
- R.string.agenda_tomorrow,
- mTZUtils?.formatDateRange(context, millis, millis, flags)
- .toString()
- )
- } else {
- mTZUtils?.formatDateRange(context, millis, millis, flags)
- .toString()
- }
- dayViewText = dayViewText.toUpperCase()
- return dayViewText
- }
-
- // Calculate the time until midnight + 1 second and set the handler to
- // do run the runnable
- @JvmStatic fun setMidnightUpdater(h: Handler?, r: Runnable?, timezone: String?) {
- if (h == null || r == null || timezone == null) {
- return
- }
- val now: Long = System.currentTimeMillis()
- val time = Time(timezone)
- time.set(now)
- val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 -
- time.second + 1) * 1000).toLong()
- h.removeCallbacks(r)
- h.postDelayed(r, runInMillis)
- }
-
- // Stop the midnight update thread
- @JvmStatic fun resetMidnightUpdater(h: Handler?, r: Runnable?) {
- if (h == null || r == null) {
- return
- }
- h.removeCallbacks(r)
- }
-
- /**
- * Returns a string description of the specified time interval.
- */
- @JvmStatic fun getDisplayedDatetime(
- startMillis: Long,
- endMillis: Long,
- currentMillis: Long,
- localTimezone: String,
- allDay: Boolean,
- context: Context
- ): String? {
- // Configure date/time formatting.
- val flagsDate: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY
- var flagsTime: Int = DateUtils.FORMAT_SHOW_TIME
- if (DateFormat.is24HourFormat(context)) {
- flagsTime = flagsTime or DateUtils.FORMAT_24HOUR
- }
- val currentTime = Time(localTimezone)
- currentTime.set(currentMillis)
- val resources: Resources = context.getResources()
- var datetimeString: String? = null
- if (allDay) {
- // All day events require special timezone adjustment.
- val localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone)
- val localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone)
- if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
- // If possible, use "Today" or "Tomorrow" instead of a full date string.
- val todayOrTomorrow = isTodayOrTomorrow(
- context.getResources(),
- localStartMillis, currentMillis, currentTime.gmtoff
- )
- if (TODAY == todayOrTomorrow) {
- datetimeString = resources.getString(R.string.today)
- } else if (TOMORROW == todayOrTomorrow) {
- datetimeString = resources.getString(R.string.tomorrow)
- }
- }
- if (datetimeString == null) {
- // For multi-day allday events or single-day all-day events that are not
- // today or tomorrow, use framework formatter.
- val f = Formatter(StringBuilder(50), Locale.getDefault())
- datetimeString = DateUtils.formatDateRange(
- context, f, startMillis,
- endMillis, flagsDate, Time.TIMEZONE_UTC
- ).toString()
- }
- } else {
- datetimeString = if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
- // Format the time.
- val timeString = formatDateRange(
- context, startMillis, endMillis,
- flagsTime
- )
-
- // If possible, use "Today" or "Tomorrow" instead of a full date string.
- val todayOrTomorrow = isTodayOrTomorrow(
- context.getResources(), startMillis,
- currentMillis, currentTime.gmtoff
- )
- if (TODAY == todayOrTomorrow) {
- // Example: "Today at 1:00pm - 2:00 pm"
- resources.getString(
- R.string.today_at_time_fmt,
- timeString
- )
- } else if (TOMORROW == todayOrTomorrow) {
- // Example: "Tomorrow at 1:00pm - 2:00 pm"
- resources.getString(
- R.string.tomorrow_at_time_fmt,
- timeString
- )
- } else {
- // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
- val dateString = formatDateRange(
- context, startMillis, endMillis,
- flagsDate
- )
- resources.getString(
- R.string.date_time_fmt, dateString,
- timeString
- )
- }
- } else {
- // For multiday events, shorten day/month names.
- // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
- val flagsDatetime = flagsDate or flagsTime or DateUtils.FORMAT_ABBREV_MONTH or
- DateUtils.FORMAT_ABBREV_WEEKDAY
- formatDateRange(
- context, startMillis, endMillis,
- flagsDatetime
- )
- }
- }
- return datetimeString
- }
-
- /**
- * Returns the timezone to display in the event info, if the local timezone is different
- * from the event timezone. Otherwise returns null.
- */
- @JvmStatic fun getDisplayedTimezone(
- startMillis: Long,
- localTimezone: String?,
- eventTimezone: String?
- ): String? {
- var tzDisplay: String? = null
- if (!TextUtils.equals(localTimezone, eventTimezone)) {
- // Figure out if this is in DST
- val tz: TimeZone = TimeZone.getTimeZone(localTimezone)
- tzDisplay = if (tz == null || tz.getID().equals("GMT")) {
- localTimezone
- } else {
- val startTime = Time(localTimezone)
- startTime.set(startMillis)
- tz.getDisplayName(startTime.isDst !== 0, TimeZone.SHORT)
- }
- }
- return tzDisplay
- }
-
- /**
- * Returns whether the specified time interval is in a single day.
- */
- private fun singleDayEvent(startMillis: Long, endMillis: Long, localGmtOffset: Long): Boolean {
- if (startMillis == endMillis) {
- return true
- }
-
- // An event ending at midnight should still be a single-day event, so check
- // time end-1.
- val startDay: Int = Time.getJulianDay(startMillis, localGmtOffset)
- val endDay: Int = Time.getJulianDay(endMillis - 1, localGmtOffset)
- return startDay == endDay
- }
-
- // Using int constants as a return value instead of an enum to minimize resources.
- private const val TODAY = 1
- private const val TOMORROW = 2
- private const val NONE = 0
-
- /**
- * Returns TODAY or TOMORROW if applicable. Otherwise returns NONE.
- */
- private fun isTodayOrTomorrow(
- r: Resources,
- dayMillis: Long,
- currentMillis: Long,
- localGmtOffset: Long
- ): Int {
- val startDay: Int = Time.getJulianDay(dayMillis, localGmtOffset)
- val currentDay: Int = Time.getJulianDay(currentMillis, localGmtOffset)
- val days = startDay - currentDay
- return if (days == 1) {
- TOMORROW
- } else if (days == 0) {
- TODAY
- } else {
- NONE
- }
- }
-
- /**
- * Inserts a drawable with today's day into the today's icon in the option menu
- * @param icon - today's icon from the options menu
- */
- @JvmStatic fun setTodayIcon(icon: LayerDrawable, c: Context?, timezone: String?) {
- val today: DayOfMonthDrawable
-
- // Reuse current drawable if possible
- val currentDrawable: Drawable? = icon.findDrawableByLayerId(R.id.today_icon_day)
- if (currentDrawable != null && currentDrawable is DayOfMonthDrawable) {
- today = currentDrawable as DayOfMonthDrawable
- } else {
- today = DayOfMonthDrawable(c as Context)
- }
- // Set the day and update the icon
- val now = Time(timezone)
- now.setToNow()
- now.normalize(false)
- today.setDayOfMonth(now.monthDay)
- icon.mutate()
- icon.setDrawableByLayerId(R.id.today_icon_day, today)
- }
-
- /**
- * Get a list of quick responses used for emailing guests from the
- * SharedPreferences. If not are found, get the hard coded ones that shipped
- * with the app
- *
- * @param context
- * @return a list of quick responses.
- */
- fun getQuickResponses(context: Context): Array<String> {
- var s = getSharedPreference(context, KEY_QUICK_RESPONSES, null as Array<String>?)
- if (s == null) {
- s = context.getResources().getStringArray(R.array.quick_response_defaults)
- }
- return s
- }
-
- /**
- * Return the app version code.
- */
- fun getVersionCode(context: Context): String? {
- if (sVersion == null) {
- try {
- sVersion = context.getPackageManager().getPackageInfo(
- context.getPackageName(), 0
- ).versionName
- } catch (e: PackageManager.NameNotFoundException) {
- // Can't find version; just leave it blank.
- Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName)
- }
- }
- return sVersion
- }
-
- // A single strand represents one color of events. Events are divided up by
- // color to make them convenient to draw. The black strand is special in
- // that it holds conflicting events as well as color settings for allday on
- // each day.
- class DNAStrand {
- @JvmField var points: FloatArray? = null
- @JvmField var allDays: IntArray? = null // color for the allday, 0 means no event
- @JvmField var position = 0
- @JvmField var color = 0
- @JvmField var count = 0
- }
-
- // A segment is a single continuous length of time occupied by a single
- // color. Segments should never span multiple days.
- private class DNASegment {
- var startMinute = 0 // in minutes since the start of the week =
- var endMinute = 0
- var color = 0 // Calendar color or black for conflicts =
- var day = 0 // quick reference to the day this segment is on =
- }
-} \ No newline at end of file