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