Adds time zone functionality into fw as a hidden api

This is the start of work towards pushing api that is needed for
Calendar to become unbundled into frameworks. This adds functions
for getting and setting time zones for the Calendar app. The new
methods are currently hidden.

Change-Id: I1b4265b23630c46d9730a179ec02da8d6c0e03ea
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 9a09805..6355a9c 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -943,6 +943,80 @@
     }
 
     /**
+     * CalendarCache stores some settings for calendar including the current
+     * time zone for the app. These settings are stored using a key/value
+     * scheme.
+     */
+    public interface CalendarCacheColumns {
+        /**
+         * The key for the setting. Keys are defined in CalendarChache in the
+         * Calendar provider.
+         * TODO Add keys to this file
+         */
+        public static final String KEY = "key";
+
+        /**
+         * The value of the given setting.
+         */
+        public static final String VALUE = "value";
+    }
+
+    public static class CalendarCache implements CalendarCacheColumns {
+        /**
+         * The URI to use for retrieving the properties from the Calendar db.
+         */
+        public static final Uri URI =
+                Uri.parse("content://" + AUTHORITY + "/properties");
+        public static final String[] POJECTION = { KEY, VALUE };
+
+        /**
+         * If updating a property, this must be provided as the selection. All
+         * other selections will fail. For queries this field can be omitted to
+         * retrieve all properties or used to query a single property. Valid
+         * keys include {@link #TIMEZONE_KEY_TYPE},
+         * {@link #TIMEZONE_KEY_INSTANCES}, and
+         * {@link #TIMEZONE_KEY_INSTANCES_PREVIOUS}, though the last one can
+         * only be read, not written.
+         */
+        public static final String WHERE = "key=?";
+
+        /**
+         * They key for updating the use of auto/home time zones in Calendar.
+         * Valid values are {@link #TIMEZONE_TYPE_AUTO} or
+         * {@link #TIMEZONE_TYPE_HOME}.
+         */
+        public static final String TIMEZONE_KEY_TYPE = "timezoneType";
+
+        /**
+         * The key for updating the time zone used by the provider when it
+         * generates the instances table. This should only be written if the
+         * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id
+         * should be written to this field.
+         */
+        public static final String TIMEZONE_KEY_INSTANCES = "timezoneInstances";
+
+        /**
+         * The key for reading the last time zone set by the user. This should
+         * only be read by apps and it will be automatically updated whenever
+         * {@link #TIMEZONE_KEY_INSTANCES} is updated with
+         * {@link #TIMEZONE_TYPE_HOME} set.
+         */
+        public static final String TIMEZONE_KEY_INSTANCES_PREVIOUS = "timezoneInstancesPrevious";
+
+        /**
+         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
+         * should stay in sync with the device's time zone.
+         */
+        public static final String TIMEZONE_TYPE_AUTO = "auto";
+
+        /**
+         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
+         * should use a fixed time zone set by the user.
+         */
+        public static final String TIMEZONE_TYPE_HOME = "home";
+    }
+
+    /**
      * A few Calendar globals are needed in the CalendarProvider for expanding
      * the Instances table and these are all stored in the first (and only)
      * row of the CalendarMetaData table.
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
new file mode 100644
index 0000000..81709d7
--- /dev/null
+++ b/core/java/android/util/CalendarUtils.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.provider.Calendar.CalendarCache;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+
+import java.util.Formatter;
+import java.util.HashSet;
+import java.util.Locale;
+
+/**
+ * A class containing utility methods related to Calendar apps.
+ *
+ * @hide
+ */
+public class CalendarUtils {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "CalendarUtils";
+
+    /**
+     * This class contains methods specific to reading and writing time zone
+     * values.
+     */
+    public static class TimeZoneUtils {
+        private static StringBuilder mSB = new StringBuilder(50);
+        private static Formatter mF = new Formatter(mSB, Locale.getDefault());
+        private volatile static boolean mFirstTZRequest = true;
+        private volatile static boolean mTZQueryInProgress = false;
+
+        private volatile static boolean mUseHomeTZ = false;
+        private volatile static String mHomeTZ = Time.getCurrentTimezone();
+
+        private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
+        private static int mToken = 1;
+        private static AsyncTZHandler mHandler;
+
+        // The name of the shared preferences file. This name must be maintained for historical
+        // reasons, as it's what PreferenceManager assigned the first time the file was created.
+        private final String mPrefsName;
+
+        /**
+         * This is the key used for writing whether or not a home time zone should
+         * be used in the Calendar app to the Calendar Preferences.
+         */
+        public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
+        /**
+         * This is the key used for writing the time zone that should be used if
+         * home time zones are enabled for the Calendar app.
+         */
+        public static final String KEY_HOME_TZ = "preferences_home_tz";
+
+        /**
+         * This is a helper class for handling the async queries and updates for the
+         * time zone settings in Calendar.
+         */
+        private class AsyncTZHandler extends AsyncQueryHandler {
+            public AsyncTZHandler(ContentResolver cr) {
+                super(cr);
+            }
+
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                synchronized (mTZCallbacks) {
+                    boolean writePrefs = false;
+                    // Check the values in the db
+                    int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
+                    int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
+                    while(cursor.moveToNext()) {
+                        String key = cursor.getString(keyColumn);
+                        String value = cursor.getString(valueColumn);
+                        if (TextUtils.equals(key, CalendarCache.TIMEZONE_KEY_TYPE)) {
+                            boolean useHomeTZ = !TextUtils.equals(
+                                    value, CalendarCache.TIMEZONE_TYPE_AUTO);
+                            if (useHomeTZ != mUseHomeTZ) {
+                                writePrefs = true;
+                                mUseHomeTZ = useHomeTZ;
+                            }
+                        } else if (TextUtils.equals(
+                                key, CalendarCache.TIMEZONE_KEY_INSTANCES_PREVIOUS)) {
+                            if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
+                                writePrefs = true;
+                                mHomeTZ = value;
+                            }
+                        }
+                    }
+                    if (writePrefs) {
+                        SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
+                        // Write the prefs
+                        setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
+                        setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
+                    }
+
+                    mTZQueryInProgress = false;
+                    for (Runnable callback : mTZCallbacks) {
+                        if (callback != null) {
+                            callback.run();
+                        }
+                    }
+                    mTZCallbacks.clear();
+                }
+            }
+        }
+
+        /**
+         * The name of the file where the shared prefs for Calendar are stored
+         * must be provided. All activities within an app should provide the
+         * same preferences name or behavior may become erratic.
+         *
+         * @param prefsName
+         */
+        public TimeZoneUtils(String prefsName) {
+            mPrefsName = prefsName;
+        }
+
+        /**
+         * Formats a date or a time range according to the local conventions.
+         *
+         * This formats a date/time range using Calendar's time zone and the
+         * local conventions for the region of the device.
+         *
+         * @param context the context is required only if the time is shown
+         * @param startMillis the start time in UTC milliseconds
+         * @param endMillis the end time in UTC milliseconds
+         * @param flags a bit mask of options See
+         * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+         * @return a string containing the formatted date/time range.
+         */
+        public String formatDateRange(Context context, long startMillis,
+                long endMillis, int flags) {
+            String date;
+            synchronized (mSB) {
+                mSB.setLength(0);
+                date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
+                        getTimeZone(context, null)).toString();
+            }
+            return date;
+        }
+
+        /**
+         * Writes a new home time zone to the db.
+         *
+         * Updates the home time zone in the db asynchronously and updates
+         * the local cache. Sending a time zone of
+         * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
+         * to the device's time zone. null or empty tz will be ignored.
+         *
+         * @param context The calling activity
+         * @param timeZone The time zone to set Calendar to, or
+         * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
+         */
+        public void setTimeZone(Context context, String timeZone) {
+            if (TextUtils.isEmpty(timeZone)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Empty time zone, nothing to be done.");
+                }
+                return;
+            }
+            boolean updatePrefs = false;
+            synchronized (mTZCallbacks) {
+                if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
+                    if (mUseHomeTZ) {
+                        updatePrefs = true;
+                    }
+                    mUseHomeTZ = false;
+                } else {
+                    if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
+                        updatePrefs = true;
+                    }
+                    mUseHomeTZ = true;
+                    mHomeTZ = timeZone;
+                }
+            }
+            if (updatePrefs) {
+                // Write the prefs
+                SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
+                setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
+                setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
+
+                // Update the db
+                ContentValues values = new ContentValues();
+                if (mHandler == null) {
+                    mHandler = new AsyncTZHandler(context.getContentResolver());
+                }
+
+                mHandler.cancelOperation(mToken);
+
+                // skip 0 so query can use it
+                if (++mToken == 0) {
+                    mToken = 1;
+                }
+
+                // Write the use home tz setting
+                String[] selArgs = new String[] { CalendarCache.TIMEZONE_KEY_TYPE };
+                values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
+                        : CalendarCache.TIMEZONE_TYPE_AUTO);
+                mHandler.startUpdate(mToken, null, CalendarCache.URI, values, CalendarCache.WHERE,
+                        selArgs);
+
+                // If using a home tz write it to the db
+                if (mUseHomeTZ) {
+                    selArgs[0] = CalendarCache.TIMEZONE_KEY_INSTANCES;
+                    values.clear();
+                    values.put(CalendarCache.VALUE, mHomeTZ);
+                    mHandler.startUpdate(
+                            mToken, null, CalendarCache.URI, values, CalendarCache.WHERE, selArgs);
+                }
+            }
+        }
+
+        /**
+         * Gets the time zone that Calendar should be displayed in
+         *
+         * This is a helper method to get the appropriate time zone for Calendar. If this
+         * is the first time this method has been called it will initiate an asynchronous
+         * query to verify that the data in preferences is correct. The callback supplied
+         * will only be called if this query returns a value other than what is stored in
+         * preferences and should cause the calling activity to refresh anything that
+         * depends on calling this method.
+         *
+         * @param context The calling activity
+         * @param callback The runnable that should execute if a query returns new values
+         * @return The string value representing the time zone Calendar should display
+         */
+        public String getTimeZone(Context context, Runnable callback) {
+            synchronized (mTZCallbacks){
+                if (mFirstTZRequest) {
+                    mTZQueryInProgress = true;
+                    mFirstTZRequest = false;
+
+                    SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
+                    mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
+                    mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
+
+                    // When the async query returns it should synchronize on
+                    // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
+                    // preferences, set mTZQueryInProgress to false, and call all
+                    // the runnables in mTZCallbacks.
+                    if (mHandler == null) {
+                        mHandler = new AsyncTZHandler(context.getContentResolver());
+                    }
+                    mHandler.startQuery(0, context, CalendarCache.URI, CalendarCache.POJECTION,
+                            null, null, null);
+                }
+                if (mTZQueryInProgress) {
+                    mTZCallbacks.add(callback);
+                }
+            }
+            return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
+        }
+    }
+
+        /**
+         * A helper method for writing a String value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
+//            SharedPreferences prefs = getSharedPreferences(context);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putString(key, value);
+            editor.apply();
+        }
+
+        /**
+         * A helper method for writing a boolean value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
+//            SharedPreferences prefs = getSharedPreferences(context, prefsName);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putBoolean(key, value);
+            editor.apply();
+        }
+
+        /** Return a properly configured SharedPreferences instance */
+        public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
+            return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
+        }
+}