Home | History | Annotate | Download | only in calendar
      1 /*
      2  * Copyright (C) 2010 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 
     17 package com.android.calendar;
     18 
     19 import android.content.AsyncQueryHandler;
     20 import android.content.ContentResolver;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.SharedPreferences;
     24 import android.database.Cursor;
     25 import android.provider.CalendarContract.CalendarCache;
     26 import android.text.TextUtils;
     27 import android.text.format.DateUtils;
     28 import android.text.format.Time;
     29 import android.util.Log;
     30 
     31 import java.util.Formatter;
     32 import java.util.HashSet;
     33 import java.util.Locale;
     34 
     35 /**
     36  * A class containing utility methods related to Calendar apps.
     37  *
     38  * This class is expected to move into the app framework eventually.
     39  */
     40 public class CalendarUtils {
     41     private static final boolean DEBUG = false;
     42     private static final String TAG = "CalendarUtils";
     43 
     44     /**
     45      * This class contains methods specific to reading and writing time zone
     46      * values.
     47      */
     48     public static class TimeZoneUtils {
     49         private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.KEY_TIMEZONE_TYPE };
     50         private static final String[] TIMEZONE_INSTANCES_ARGS =
     51                 { CalendarCache.KEY_TIMEZONE_INSTANCES };
     52         public static final String[] CALENDAR_CACHE_POJECTION = {
     53                 CalendarCache.KEY, CalendarCache.VALUE
     54         };
     55 
     56         private static StringBuilder mSB = new StringBuilder(50);
     57         private static Formatter mF = new Formatter(mSB, Locale.getDefault());
     58         private volatile static boolean mFirstTZRequest = true;
     59         private volatile static boolean mTZQueryInProgress = false;
     60 
     61         private volatile static boolean mUseHomeTZ = false;
     62         private volatile static String mHomeTZ = Time.getCurrentTimezone();
     63 
     64         private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
     65         private static int mToken = 1;
     66         private static AsyncTZHandler mHandler;
     67 
     68         // The name of the shared preferences file. This name must be maintained for historical
     69         // reasons, as it's what PreferenceManager assigned the first time the file was created.
     70         private final String mPrefsName;
     71 
     72         /**
     73          * This is the key used for writing whether or not a home time zone should
     74          * be used in the Calendar app to the Calendar Preferences.
     75          */
     76         public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
     77         /**
     78          * This is the key used for writing the time zone that should be used if
     79          * home time zones are enabled for the Calendar app.
     80          */
     81         public static final String KEY_HOME_TZ = "preferences_home_tz";
     82 
     83         /**
     84          * This is a helper class for handling the async queries and updates for the
     85          * time zone settings in Calendar.
     86          */
     87         private class AsyncTZHandler extends AsyncQueryHandler {
     88             public AsyncTZHandler(ContentResolver cr) {
     89                 super(cr);
     90             }
     91 
     92             @Override
     93             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
     94                 synchronized (mTZCallbacks) {
     95                     if (cursor == null) {
     96                         mTZQueryInProgress = false;
     97                         mFirstTZRequest = true;
     98                         return;
     99                     }
    100 
    101                     boolean writePrefs = false;
    102                     // Check the values in the db
    103                     int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
    104                     int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
    105                     while(cursor.moveToNext()) {
    106                         String key = cursor.getString(keyColumn);
    107                         String value = cursor.getString(valueColumn);
    108                         if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
    109                             boolean useHomeTZ = !TextUtils.equals(
    110                                     value, CalendarCache.TIMEZONE_TYPE_AUTO);
    111                             if (useHomeTZ != mUseHomeTZ) {
    112                                 writePrefs = true;
    113                                 mUseHomeTZ = useHomeTZ;
    114                             }
    115                         } else if (TextUtils.equals(
    116                                 key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
    117                             if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
    118                                 writePrefs = true;
    119                                 mHomeTZ = value;
    120                             }
    121                         }
    122                     }
    123                     cursor.close();
    124                     if (writePrefs) {
    125                         SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
    126                         // Write the prefs
    127                         setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
    128                         setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
    129                     }
    130 
    131                     mTZQueryInProgress = false;
    132                     for (Runnable callback : mTZCallbacks) {
    133                         if (callback != null) {
    134                             callback.run();
    135                         }
    136                     }
    137                     mTZCallbacks.clear();
    138                 }
    139             }
    140         }
    141 
    142         /**
    143          * The name of the file where the shared prefs for Calendar are stored
    144          * must be provided. All activities within an app should provide the
    145          * same preferences name or behavior may become erratic.
    146          *
    147          * @param prefsName
    148          */
    149         public TimeZoneUtils(String prefsName) {
    150             mPrefsName = prefsName;
    151         }
    152 
    153         /**
    154          * Formats a date or a time range according to the local conventions.
    155          *
    156          * This formats a date/time range using Calendar's time zone and the
    157          * local conventions for the region of the device.
    158          *
    159          * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
    160          * the UTC time zone instead.
    161          *
    162          * @param context the context is required only if the time is shown
    163          * @param startMillis the start time in UTC milliseconds
    164          * @param endMillis the end time in UTC milliseconds
    165          * @param flags a bit mask of options See
    166          * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
    167          * @return a string containing the formatted date/time range.
    168          */
    169         public String formatDateRange(Context context, long startMillis,
    170                 long endMillis, int flags) {
    171             String date;
    172             String tz;
    173             if ((flags & DateUtils.FORMAT_UTC) != 0) {
    174                 tz = Time.TIMEZONE_UTC;
    175             } else {
    176                 tz = getTimeZone(context, null);
    177             }
    178             synchronized (mSB) {
    179                 mSB.setLength(0);
    180                 date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
    181                         tz).toString();
    182             }
    183             return date;
    184         }
    185 
    186         /**
    187          * Writes a new home time zone to the db.
    188          *
    189          * Updates the home time zone in the db asynchronously and updates
    190          * the local cache. Sending a time zone of
    191          * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
    192          * to the device's time zone. null or empty tz will be ignored.
    193          *
    194          * @param context The calling activity
    195          * @param timeZone The time zone to set Calendar to, or
    196          * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
    197          */
    198         public void setTimeZone(Context context, String timeZone) {
    199             if (TextUtils.isEmpty(timeZone)) {
    200                 if (DEBUG) {
    201                     Log.d(TAG, "Empty time zone, nothing to be done.");
    202                 }
    203                 return;
    204             }
    205             boolean updatePrefs = false;
    206             synchronized (mTZCallbacks) {
    207                 if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
    208                     if (mUseHomeTZ) {
    209                         updatePrefs = true;
    210                     }
    211                     mUseHomeTZ = false;
    212                 } else {
    213                     if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
    214                         updatePrefs = true;
    215                     }
    216                     mUseHomeTZ = true;
    217                     mHomeTZ = timeZone;
    218                 }
    219             }
    220             if (updatePrefs) {
    221                 // Write the prefs
    222                 SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
    223                 setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
    224                 setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
    225 
    226                 // Update the db
    227                 ContentValues values = new ContentValues();
    228                 if (mHandler != null) {
    229                     mHandler.cancelOperation(mToken);
    230                 }
    231 
    232                 mHandler = new AsyncTZHandler(context.getContentResolver());
    233 
    234                 // skip 0 so query can use it
    235                 if (++mToken == 0) {
    236                     mToken = 1;
    237                 }
    238 
    239                 // Write the use home tz setting
    240                 values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
    241                         : CalendarCache.TIMEZONE_TYPE_AUTO);
    242                 mHandler.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
    243                         TIMEZONE_TYPE_ARGS);
    244 
    245                 // If using a home tz write it to the db
    246                 if (mUseHomeTZ) {
    247                     ContentValues values2 = new ContentValues();
    248                     values2.put(CalendarCache.VALUE, mHomeTZ);
    249                     mHandler.startUpdate(mToken, null, CalendarCache.URI, values2,
    250                             "key=?", TIMEZONE_INSTANCES_ARGS);
    251                 }
    252             }
    253         }
    254 
    255         /**
    256          * Gets the time zone that Calendar should be displayed in
    257          *
    258          * This is a helper method to get the appropriate time zone for Calendar. If this
    259          * is the first time this method has been called it will initiate an asynchronous
    260          * query to verify that the data in preferences is correct. The callback supplied
    261          * will only be called if this query returns a value other than what is stored in
    262          * preferences and should cause the calling activity to refresh anything that
    263          * depends on calling this method.
    264          *
    265          * @param context The calling activity
    266          * @param callback The runnable that should execute if a query returns new values
    267          * @return The string value representing the time zone Calendar should display
    268          */
    269         public String getTimeZone(Context context, Runnable callback) {
    270             synchronized (mTZCallbacks){
    271                 if (mFirstTZRequest) {
    272                     mTZQueryInProgress = true;
    273                     mFirstTZRequest = false;
    274 
    275                     SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
    276                     mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
    277                     mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
    278 
    279                     // When the async query returns it should synchronize on
    280                     // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
    281                     // preferences, set mTZQueryInProgress to false, and call all
    282                     // the runnables in mTZCallbacks.
    283                     if (mHandler == null) {
    284                         mHandler = new AsyncTZHandler(context.getContentResolver());
    285                     }
    286                     mHandler.startQuery(0, context, CalendarCache.URI, CALENDAR_CACHE_POJECTION,
    287                             null, null, null);
    288                 }
    289                 if (mTZQueryInProgress) {
    290                     mTZCallbacks.add(callback);
    291                 }
    292             }
    293             return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
    294         }
    295 
    296         /**
    297          * Forces a query of the database to check for changes to the time zone.
    298          * This should be called if another app may have modified the db. If a
    299          * query is already in progress the callback will be added to the list
    300          * of callbacks to be called when it returns.
    301          *
    302          * @param context The calling activity
    303          * @param callback The runnable that should execute if a query returns
    304          *            new values
    305          */
    306         public void forceDBRequery(Context context, Runnable callback) {
    307             synchronized (mTZCallbacks){
    308                 if (mTZQueryInProgress) {
    309                     mTZCallbacks.add(callback);
    310                     return;
    311                 }
    312                 mFirstTZRequest = true;
    313                 getTimeZone(context, callback);
    314             }
    315         }
    316     }
    317 
    318         /**
    319          * A helper method for writing a String value to the preferences
    320          * asynchronously.
    321          *
    322          * @param context A context with access to the correct preferences
    323          * @param key The preference to write to
    324          * @param value The value to write
    325          */
    326         public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
    327 //            SharedPreferences prefs = getSharedPreferences(context);
    328             SharedPreferences.Editor editor = prefs.edit();
    329             editor.putString(key, value);
    330             editor.apply();
    331         }
    332 
    333         /**
    334          * A helper method for writing a boolean value to the preferences
    335          * asynchronously.
    336          *
    337          * @param context A context with access to the correct preferences
    338          * @param key The preference to write to
    339          * @param value The value to write
    340          */
    341         public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
    342 //            SharedPreferences prefs = getSharedPreferences(context, prefsName);
    343             SharedPreferences.Editor editor = prefs.edit();
    344             editor.putBoolean(key, value);
    345             editor.apply();
    346         }
    347 
    348         /** Return a properly configured SharedPreferences instance */
    349         public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
    350             return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
    351         }
    352 }
    353