diff options
Diffstat (limited to 'src/com/android/calendar/CalendarUtils.kt')
-rw-r--r-- | src/com/android/calendar/CalendarUtils.kt | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/com/android/calendar/CalendarUtils.kt b/src/com/android/calendar/CalendarUtils.kt new file mode 100644 index 00000000..94ca7234 --- /dev/null +++ b/src/com/android/calendar/CalendarUtils.kt @@ -0,0 +1,354 @@ +/* + * 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.content.AsyncQueryHandler +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.content.SharedPreferences +import android.database.Cursor +import android.os.Looper +import android.provider.CalendarContract.CalendarCache +import android.text.TextUtils +import android.text.format.DateUtils +import android.text.format.Time +import android.util.Log + +import java.util.Formatter +import java.util.HashSet +import java.util.Locale + +/** + * A class containing utility methods related to Calendar apps. + * + * This class is expected to move into the app framework eventually. + */ +class CalendarUtils { + + companion object { + private const val DEBUG = false + private const val TAG = "CalendarUtils" + + /** + * A helper method for writing a boolean value to the preferences + * asynchronously. + * + * @param context A context with access to the correct preferences + * @param key The preference to write to + * @param value The value to write + */ + @JvmStatic + fun setSharedPreference(prefs: SharedPreferences, key: String?, value: Boolean) { + val editor: SharedPreferences.Editor = prefs.edit() + editor.putBoolean(key, value) + editor.apply() + } + + /** Return a properly configured SharedPreferences instance */ + @JvmStatic + fun getSharedPreferences(context: Context, prefsName: String?): SharedPreferences { + return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE) + } + + /** + * A helper method for writing a String value to the preferences + * asynchronously. + * + * @param context A context with access to the correct preferences + * @param key The preference to write to + * @param value The value to write + */ + @JvmStatic + fun setSharedPreference(prefs: SharedPreferences, key: String?, value: String?) { + val editor: SharedPreferences.Editor = prefs.edit() + editor.putString(key, value) + editor.apply() + } + } + + /** + * This class contains methods specific to reading and writing time zone + * values. + */ + class TimeZoneUtils + /** + * The name of the file where the shared prefs for Calendar are stored + * must be provided. All activities within an app should provide the + * same preferences name or behavior may become erratic. + * + * @param prefsName + */( // 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. + private val mPrefsName: String) { + /** + * This is a helper class for handling the async queries and updates for the + * time zone settings in Calendar. + */ + private inner class AsyncTZHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) { + protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) { + synchronized(mTZCallbacks) { + if (cursor == null) { + mTZQueryInProgress = false + mFirstTZRequest = true + return + } + var writePrefs = false + // Check the values in the db + val keyColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.KEY) + val valueColumn: Int = cursor.getColumnIndexOrThrow(CalendarCache.VALUE) + while (cursor.moveToNext()) { + val key: String = cursor.getString(keyColumn) + val value: String = cursor.getString(valueColumn) + if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) { + val useHomeTZ: Boolean = !TextUtils.equals( + value, CalendarCache.TIMEZONE_TYPE_AUTO) + if (useHomeTZ != mUseHomeTZ) { + writePrefs = true + mUseHomeTZ = useHomeTZ + } + } else if (TextUtils.equals( + key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) { + if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) { + writePrefs = true + mHomeTZ = value + } + } + } + cursor.close() + if (writePrefs) { + val prefs: SharedPreferences = + getSharedPreferences(cookie as Context, mPrefsName) + // Write the prefs + setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ) + setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ) + } + mTZQueryInProgress = false + for (callback in mTZCallbacks) { + if (callback != null) { + callback.run() + } + } + mTZCallbacks.clear() + } + } + } + + /** + * Formats a date or a time range according to the local conventions. + * + * This formats a date/time range using Calendar's time zone and the + * local conventions for the region of the device. + * + * If the [DateUtils.FORMAT_UTC] flag is used it will pass in + * the UTC time zone instead. + * + * @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. + */ + fun formatDateRange(context: Context, startMillis: Long, + endMillis: Long, flags: Int): String { + var date: String + val tz: String + tz = if (flags and DateUtils.FORMAT_UTC !== 0) { + Time.TIMEZONE_UTC + } else { + getTimeZone(context, null) + } + synchronized(mSB) { + mSB.setLength(0) + date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags, + tz).toString() + } + return date + } + + /** + * 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 + * [CalendarCache.TIMEZONE_TYPE_AUTO] 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 + * [CalendarCache.TIMEZONE_TYPE_AUTO] + */ + fun setTimeZone(context: Context, timeZone: String) { + if (TextUtils.isEmpty(timeZone)) { + if (DEBUG) { + Log.d(TAG, "Empty time zone, nothing to be done.") + } + return + } + var updatePrefs = false + synchronized(mTZCallbacks) { + if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) { + if (mUseHomeTZ) { + updatePrefs = true + } + mUseHomeTZ = false + } else { + if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) { + updatePrefs = true + } + mUseHomeTZ = true + mHomeTZ = timeZone + } + } + if (updatePrefs) { + // Write the prefs + val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName) + setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ) + setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ) + + // Update the db + val values = ContentValues() + if (mHandler != null) { + mHandler?.cancelOperation(mToken) + } + mHandler = AsyncTZHandler(context.getContentResolver()) + + // skip 0 so query can use it + if (++mToken == 0) { + mToken = 1 + } + + // Write the use home tz setting + values.put(CalendarCache.VALUE, if (mUseHomeTZ) CalendarCache.TIMEZONE_TYPE_HOME + else CalendarCache.TIMEZONE_TYPE_AUTO) + mHandler?.startUpdate(mToken, null, CalendarCache.URI, values, "key=?", + TIMEZONE_TYPE_ARGS) + + // If using a home tz write it to the db + if (mUseHomeTZ) { + val values2 = ContentValues() + values2.put(CalendarCache.VALUE, mHomeTZ) + mHandler?.startUpdate(mToken, null, CalendarCache.URI, values2, + "key=?", TIMEZONE_INSTANCES_ARGS) + } + } + } + + /** + * 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 + */ + fun getTimeZone(context: Context, callback: Runnable?): String { + synchronized(mTZCallbacks) { + if (mFirstTZRequest) { + val prefs: SharedPreferences = getSharedPreferences(context, mPrefsName) + mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false) + mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone()) ?: String() + + // Only check content resolver if we have a looper to attach to use + if (Looper.myLooper() != null) { + mTZQueryInProgress = true + mFirstTZRequest = false + + // When the async query returns it should synchronize on + // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the + // preferences, set mTZQueryInProgress to false, and call all + // the runnables in mTZCallbacks. + if (mHandler == null) { + mHandler = AsyncTZHandler(context.getContentResolver()) + } + mHandler?.startQuery(0, context, CalendarCache.URI, + CALENDAR_CACHE_POJECTION, null, null, null) + } + } + if (mTZQueryInProgress && callback != null) { + mTZCallbacks.add(callback) + } + } + return if (mUseHomeTZ) mHomeTZ else Time.getCurrentTimezone() + } + + /** + * Forces a query of the database to check for changes to the time zone. + * This should be called if another app may have modified the db. If a + * query is already in progress the callback will be added to the list + * of callbacks to be called when it returns. + * + * @param context The calling activity + * @param callback The runnable that should execute if a query returns + * new values + */ + fun forceDBRequery(context: Context, callback: Runnable) { + synchronized(mTZCallbacks) { + if (mTZQueryInProgress) { + mTZCallbacks.add(callback) + return + } + mFirstTZRequest = true + getTimeZone(context, callback) + } + } + + companion object { + private val TIMEZONE_TYPE_ARGS = arrayOf<String>(CalendarCache.KEY_TIMEZONE_TYPE) + private val TIMEZONE_INSTANCES_ARGS = + arrayOf<String>(CalendarCache.KEY_TIMEZONE_INSTANCES) + val CALENDAR_CACHE_POJECTION = arrayOf<String>( + CalendarCache.KEY, CalendarCache.VALUE + ) + private val mSB: StringBuilder = StringBuilder(50) + private val mF: Formatter = Formatter(mSB, Locale.getDefault()) + + @Volatile + private var mFirstTZRequest = true + + @Volatile + private var mTZQueryInProgress = false + + @Volatile + private var mUseHomeTZ = false + + @Volatile + private var mHomeTZ: String = Time.getCurrentTimezone() + private val mTZCallbacks: HashSet<Runnable> = HashSet<Runnable>() + private var mToken = 1 + private var mHandler: AsyncTZHandler? = null + + /** + * This is the key used for writing whether or not a home time zone should + * be used in the Calendar app to the Calendar Preferences. + */ + const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled" + + /** + * This is the key used for writing the time zone that should be used if + * home time zones are enabled for the Calendar app. + */ + const val KEY_HOME_TZ = "preferences_home_tz" + } + } +}
\ No newline at end of file |