blob: 0238c321338ae30f0ba5101768603dc6c8ee72d0 [file] [log] [blame]
/*
* Copyright (C) 2010 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.
*/
public class CalendarUtils {
private static final boolean DEBUG = false;
private static final String TAG = "CalendarUtils";
/**
* This class contains methods specific to reading and writing time zone
* values.
*/
public static class TimeZoneUtils {
private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.KEY_TIMEZONE_TYPE };
private static final String[] TIMEZONE_INSTANCES_ARGS =
{ CalendarCache.KEY_TIMEZONE_INSTANCES };
public static final String[] CALENDAR_CACHE_POJECTION = {
CalendarCache.KEY, CalendarCache.VALUE
};
private static StringBuilder mSB = new StringBuilder(50);
private static Formatter mF = new Formatter(mSB, Locale.getDefault());
private volatile static boolean mFirstTZRequest = true;
private volatile static boolean mTZQueryInProgress = false;
private volatile static boolean mUseHomeTZ = false;
private volatile static String mHomeTZ = Time.getCurrentTimezone();
private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
private static int mToken = 1;
private static AsyncTZHandler mHandler;
// 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 final String mPrefsName;
/**
* 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.
*/
public static final String 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.
*/
public static final String KEY_HOME_TZ = "preferences_home_tz";
/**
* This is a helper class for handling the async queries and updates for the
* time zone settings in Calendar.
*/
private class AsyncTZHandler extends AsyncQueryHandler {
public AsyncTZHandler(ContentResolver cr) {
super(cr);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
synchronized (mTZCallbacks) {
if (cursor == null) {
mTZQueryInProgress = false;
mFirstTZRequest = true;
return;
}
boolean writePrefs = false;
// Check the values in the db
int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
while(cursor.moveToNext()) {
String key = cursor.getString(keyColumn);
String value = cursor.getString(valueColumn);
if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
boolean useHomeTZ = !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) {
SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
// Write the prefs
setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
}
mTZQueryInProgress = false;
for (Runnable callback : mTZCallbacks) {
if (callback != null) {
callback.run();
}
}
mTZCallbacks.clear();
}
}
}
/**
* 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
*/
public TimeZoneUtils(String prefsName) {
mPrefsName = prefsName;
}
/**
* 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 {@link 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
* {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
* @return a string containing the formatted date/time range.
*/
public String formatDateRange(Context context, long startMillis,
long endMillis, int flags) {
String date;
String tz;
if ((flags & DateUtils.FORMAT_UTC) != 0) {
tz = Time.TIMEZONE_UTC;
} else {
tz = 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
* {@link 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
* {@link CalendarCache#TIMEZONE_TYPE_AUTO}
*/
public void setTimeZone(Context context, String timeZone) {
if (TextUtils.isEmpty(timeZone)) {
if (DEBUG) {
Log.d(TAG, "Empty time zone, nothing to be done.");
}
return;
}
boolean 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
SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
// Update the db
ContentValues values = new ContentValues();
if (mHandler != null) {
mHandler.cancelOperation(mToken);
}
mHandler = new 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, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
: 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) {
ContentValues values2 = new 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
*/
public String getTimeZone(Context context, Runnable callback) {
synchronized (mTZCallbacks){
if (mFirstTZRequest) {
SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
// 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 = new AsyncTZHandler(context.getContentResolver());
}
mHandler.startQuery(0, context, CalendarCache.URI, CALENDAR_CACHE_POJECTION,
null, null, null);
}
}
if (mTZQueryInProgress) {
mTZCallbacks.add(callback);
}
}
return mUseHomeTZ ? mHomeTZ : 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
*/
public void forceDBRequery(Context context, Runnable callback) {
synchronized (mTZCallbacks){
if (mTZQueryInProgress) {
mTZCallbacks.add(callback);
return;
}
mFirstTZRequest = true;
getTimeZone(context, callback);
}
}
}
/**
* 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
*/
public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
// SharedPreferences prefs = getSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(key, value);
editor.apply();
}
/**
* 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
*/
public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
// SharedPreferences prefs = getSharedPreferences(context, prefsName);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(key, value);
editor.apply();
}
/** Return a properly configured SharedPreferences instance */
public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
}
}