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