blob: 0238c321338ae30f0ba5101768603dc6c8ee72d0 [file] [log] [blame]
Andy McFadden636269c2011-06-09 13:15:55 -07001/*
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
17package com.android.calendar;
18
19import android.content.AsyncQueryHandler;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.database.Cursor;
Paul Sliwowski2dc59842013-06-12 16:03:37 -070025import android.os.Looper;
RoboErika7c03902011-06-14 11:06:44 -070026import android.provider.CalendarContract.CalendarCache;
Andy McFadden636269c2011-06-09 13:15:55 -070027import android.text.TextUtils;
28import android.text.format.DateUtils;
29import android.text.format.Time;
30import android.util.Log;
31
32import java.util.Formatter;
33import java.util.HashSet;
34import 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 */
41public 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 {
RoboErikfa292a02011-06-30 12:49:42 -070050 private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.KEY_TIMEZONE_TYPE };
Andy McFadden636269c2011-06-09 13:15:55 -070051 private static final String[] TIMEZONE_INSTANCES_ARGS =
RoboErikfa292a02011-06-30 12:49:42 -070052 { CalendarCache.KEY_TIMEZONE_INSTANCES };
RoboErika27a8862011-06-23 15:26:23 -070053 public static final String[] CALENDAR_CACHE_POJECTION = {
54 CalendarCache.KEY, CalendarCache.VALUE
55 };
Andy McFadden636269c2011-06-09 13:15:55 -070056
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);
RoboErikfa292a02011-06-30 12:49:42 -0700109 if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
Andy McFadden636269c2011-06-09 13:15:55 -0700110 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(
RoboErikfa292a02011-06-30 12:49:42 -0700117 key, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
Andy McFadden636269c2011-06-09 13:15:55 -0700118 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);
RoboErikfa292a02011-06-30 12:49:42 -0700243 mHandler.startUpdate(mToken, null, CalendarCache.URI, values, "key=?",
Andy McFadden636269c2011-06-09 13:15:55 -0700244 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,
RoboErikfa292a02011-06-30 12:49:42 -0700251 "key=?", TIMEZONE_INSTANCES_ARGS);
Andy McFadden636269c2011-06-09 13:15:55 -0700252 }
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) {
Andy McFadden636269c2011-06-09 13:15:55 -0700273 SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
274 mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
275 mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
276
Paul Sliwowski2dc59842013-06-12 16:03:37 -0700277 // 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);
Andy McFadden636269c2011-06-09 13:15:55 -0700291 }
Andy McFadden636269c2011-06-09 13:15:55 -0700292 }
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}