• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
AsyncTZHandler(ContentResolver cr)88             public AsyncTZHandler(ContentResolver cr) {
89                 super(cr);
90             }
91 
92             @Override
onQueryComplete(int token, Object cookie, Cursor cursor)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          */
TimeZoneUtils(String prefsName)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          */
formatDateRange(Context context, long startMillis, long endMillis, int flags)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          */
setTimeZone(Context context, String timeZone)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          */
getTimeZone(Context context, Runnable callback)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          */
forceDBRequery(Context context, Runnable callback)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          */
setSharedPreference(SharedPreferences prefs, String key, String value)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          */
setSharedPreference(SharedPreferences prefs, String key, boolean value)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 */
getSharedPreferences(Context context, String prefsName)349         public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
350             return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
351         }
352 }
353