Utils.java revision cfa204ba5c59eb6ebd4b54788ceb31010e4a37a0
1/*
2 * Copyright (C) 2006 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 static android.provider.Calendar.EVENT_BEGIN_TIME;
20
21import com.android.calendar.CalendarController.ViewType;
22
23import android.app.Activity;
24import android.content.Context;
25import android.content.Intent;
26import android.content.SharedPreferences;
27import android.database.Cursor;
28import android.database.MatrixCursor;
29import android.net.Uri;
30import android.os.Bundle;
31import android.text.TextUtils;
32import android.text.format.DateUtils;
33import android.text.format.Time;
34import android.util.CalendarUtils.TimeZoneUtils;
35import android.util.Log;
36
37import java.util.Calendar;
38import java.util.Formatter;
39import java.util.List;
40import java.util.Map;
41import java.util.TimeZone;
42
43public class Utils {
44    private static final boolean DEBUG = true;
45    private static final String TAG = "CalUtils";
46    // Set to 0 until we have UI to perform undo
47    public static final long UNDO_DELAY = 0;
48
49    // For recurring events which instances of the series are being modified
50    public static final int MODIFY_UNINITIALIZED = 0;
51    public static final int MODIFY_SELECTED = 1;
52    public static final int MODIFY_ALL_FOLLOWING = 2;
53    public static final int MODIFY_ALL = 3;
54
55    // When the edit event view finishes it passes back the appropriate exit
56    // code.
57    public static final int DONE_REVERT = 1 << 0;
58    public static final int DONE_SAVE = 1 << 1;
59    public static final int DONE_DELETE = 1 << 2;
60    // And should re run with DONE_EXIT if it should also leave the view, just
61    // exiting is identical to reverting
62    public static final int DONE_EXIT = 1 << 0;
63
64    private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF;
65    private static final int HIGH_ALPHA = 255 << 24;
66    private static final int MED_ALPHA = 180 << 24;
67    private static final int LOW_ALPHA = 150 << 24;
68
69    protected static final String OPEN_EMAIL_MARKER = " <";
70    protected static final String CLOSE_EMAIL_MARKER = ">";
71    /* The corner should be rounded on the top right and bottom right */
72    private static final float[] CORNERS = new float[] { 0, 0, 5, 5, 5, 5, 0, 0 };
73
74    public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW";
75    public static final String INTENT_KEY_VIEW_TYPE = "VIEW";
76    public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY";
77
78    public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
79
80    // The name of the shared preferences file. This name must be maintained for
81    // historical
82    // reasons, as it's what PreferenceManager assigned the first time the file
83    // was created.
84    private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
85
86    private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME);
87    private static boolean mAllowWeekForDetailView = false;
88    private static long mTardis = 0;
89
90    public static int getViewTypeFromIntentAndSharedPref(Activity activity) {
91        Intent intent = activity.getIntent();
92        Bundle extras = intent.getExtras();
93        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(activity);
94
95        if (TextUtils.equals(intent.getAction(), Intent.ACTION_EDIT)) {
96            return ViewType.EDIT;
97        }
98        if (extras != null) {
99            if (extras.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
100                // This is the "detail" view which is either agenda or day view
101                return prefs.getInt(GeneralPreferences.KEY_DETAILED_VIEW,
102                        GeneralPreferences.DEFAULT_DETAILED_VIEW);
103            } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras.getString(INTENT_KEY_VIEW_TYPE))) {
104                // Not sure who uses this. This logic came from LaunchActivity
105                return ViewType.DAY;
106            }
107        }
108
109        // Default to the last view
110        return prefs.getInt(
111                GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
112    }
113
114    /**
115     * Writes a new home time zone to the db. Updates the home time zone in the
116     * db asynchronously and updates the local cache. Sending a time zone of
117     * **tbd** will cause it to be set to the device's time zone. null or empty
118     * tz will be ignored.
119     *
120     * @param context The calling activity
121     * @param timeZone The time zone to set Calendar to, or **tbd**
122     */
123    public static void setTimeZone(Context context, String timeZone) {
124        mTZUtils.setTimeZone(context, timeZone);
125    }
126
127    /**
128     * Gets the time zone that Calendar should be displayed in This is a helper
129     * method to get the appropriate time zone for Calendar. If this is the
130     * first time this method has been called it will initiate an asynchronous
131     * query to verify that the data in preferences is correct. The callback
132     * supplied will only be called if this query returns a value other than
133     * what is stored in preferences and should cause the calling activity to
134     * refresh anything that depends on calling this method.
135     *
136     * @param context The calling activity
137     * @param callback The runnable that should execute if a query returns new
138     *            values
139     * @return The string value representing the time zone Calendar should
140     *         display
141     */
142    public static String getTimeZone(Context context, Runnable callback) {
143        return mTZUtils.getTimeZone(context, callback);
144    }
145
146    /**
147     * Formats a date or a time range according to the local conventions.
148     *
149     * @param context the context is required only if the time is shown
150     * @param startMillis the start time in UTC milliseconds
151     * @param endMillis the end time in UTC milliseconds
152     * @param flags a bit mask of options See {@link DateUtils#formatDateRange(Context, Formatter,
153     * long, long, int, String) formatDateRange}
154     * @return a string containing the formatted date/time range.
155     */
156    public static String formatDateRange(
157            Context context, long startMillis, long endMillis, int flags) {
158        return mTZUtils.formatDateRange(context, startMillis, endMillis, flags);
159    }
160
161    public static String getSharedPreference(Context context, String key, String defaultValue) {
162        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
163        return prefs.getString(key, defaultValue);
164    }
165
166    public static int getSharedPreference(Context context, String key, int defaultValue) {
167        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
168        return prefs.getInt(key, defaultValue);
169    }
170
171    public static boolean getSharedPreference(Context context, String key, boolean defaultValue) {
172        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
173        return prefs.getBoolean(key, defaultValue);
174    }
175
176    /**
177     * Asynchronously sets the preference with the given key to the given value
178     *
179     * @param context the context to use to get preferences from
180     * @param key the key of the preference to set
181     * @param value the value to set
182     */
183    public static void setSharedPreference(Context context, String key, String value) {
184        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
185        prefs.edit().putString(key, value).apply();
186    }
187
188    protected static void tardis() {
189        mTardis = System.currentTimeMillis();
190    }
191
192    protected static long getTardis() {
193        return mTardis;
194    }
195
196    static void setSharedPreference(Context context, String key, boolean value) {
197        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
198        SharedPreferences.Editor editor = prefs.edit();
199        editor.putBoolean(key, value);
200        editor.apply();
201    }
202
203    static void setSharedPreference(Context context, String key, int value) {
204        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
205        SharedPreferences.Editor editor = prefs.edit();
206        editor.putInt(key, value);
207        editor.apply();
208    }
209
210    /**
211     * Save default agenda/day/week/month view for next time
212     *
213     * @param context
214     * @param viewId {@link CalendarController.ViewType}
215     */
216    static void setDefaultView(Context context, int viewId) {
217        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
218        SharedPreferences.Editor editor = prefs.edit();
219
220        boolean validDetailView = false;
221        if (mAllowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) {
222            validDetailView = true;
223        } else {
224            validDetailView = viewId == CalendarController.ViewType.AGENDA
225                    || viewId == CalendarController.ViewType.DAY;
226        }
227
228        if (validDetailView) {
229            // Record the detail start view
230            editor.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId);
231        }
232
233        // Record the (new) start view
234        editor.putInt(GeneralPreferences.KEY_START_VIEW, viewId);
235        editor.apply();
236    }
237
238    public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
239        MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames());
240        int numColumns = cursor.getColumnCount();
241        String data[] = new String[numColumns];
242        cursor.moveToPosition(-1);
243        while (cursor.moveToNext()) {
244            for (int i = 0; i < numColumns; i++) {
245                data[i] = cursor.getString(i);
246            }
247            newCursor.addRow(data);
248        }
249        return newCursor;
250    }
251
252    /**
253     * Compares two cursors to see if they contain the same data.
254     *
255     * @return Returns true of the cursors contain the same data and are not
256     *         null, false otherwise
257     */
258    public static boolean compareCursors(Cursor c1, Cursor c2) {
259        if (c1 == null || c2 == null) {
260            return false;
261        }
262
263        int numColumns = c1.getColumnCount();
264        if (numColumns != c2.getColumnCount()) {
265            return false;
266        }
267
268        if (c1.getCount() != c2.getCount()) {
269            return false;
270        }
271
272        c1.moveToPosition(-1);
273        c2.moveToPosition(-1);
274        while (c1.moveToNext() && c2.moveToNext()) {
275            for (int i = 0; i < numColumns; i++) {
276                if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
277                    return false;
278                }
279            }
280        }
281
282        return true;
283    }
284
285    /**
286     * If the given intent specifies a time (in milliseconds since the epoch),
287     * then that time is returned. Otherwise, the current time is returned.
288     */
289    public static final long timeFromIntentInMillis(Intent intent) {
290        // If the time was specified, then use that. Otherwise, use the current
291        // time.
292        Uri data = intent.getData();
293        long millis = intent.getLongExtra(EVENT_BEGIN_TIME, -1);
294        if (millis == -1 && data != null && data.isHierarchical()) {
295            List<String> path = data.getPathSegments();
296            if (path.size() == 2 && path.get(0).equals("time")) {
297                try {
298                    millis = Long.valueOf(data.getLastPathSegment());
299                } catch (NumberFormatException e) {
300                    Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time "
301                            + "found. Using current time.");
302                }
303            }
304        }
305        if (millis <= 0) {
306            millis = System.currentTimeMillis();
307        }
308        return millis;
309    }
310
311    /**
312     * Formats the given Time object so that it gives the month and year (for
313     * example, "September 2007").
314     *
315     * @param time the time to format
316     * @return the string containing the weekday and the date
317     */
318    public static String formatMonthYear(Context context, Time time) {
319        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
320                | DateUtils.FORMAT_SHOW_YEAR;
321        long millis = time.toMillis(true);
322        return formatDateRange(context, millis, millis, flags);
323    }
324
325    /**
326     * Returns a list joined together by the provided delimiter, for example,
327     * ["a", "b", "c"] could be joined into "a,b,c"
328     *
329     * @param things the things to join together
330     * @param delim the delimiter to use
331     * @return a string contained the things joined together
332     */
333    public static String join(List<?> things, String delim) {
334        StringBuilder builder = new StringBuilder();
335        boolean first = true;
336        for (Object thing : things) {
337            if (first) {
338                first = false;
339            } else {
340                builder.append(delim);
341            }
342            builder.append(thing.toString());
343        }
344        return builder.toString();
345    }
346
347    /**
348     * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
349     * adjusted for first day of week.
350     *
351     * This takes a julian day and the week start day and calculates which
352     * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting
353     * at 0. *Do not* use this to compute the ISO week number for the year.
354     *
355     * @param julianDay The julian day to calculate the week number for
356     * @param firstDayOfWeek Which week day is the first day of the week,
357     *          see {@link Time#SUNDAY}
358     * @return Weeks since the epoch
359     */
360    public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
361        int diff = Time.THURSDAY - firstDayOfWeek;
362        if (diff < 0) {
363            diff += 7;
364        }
365        int refDay = Time.EPOCH_JULIAN_DAY - diff;
366        return (julianDay - refDay) / 7;
367    }
368
369    /**
370     * Takes a number of weeks since the epoch and calculates the Julian day of
371     * the Monday for that week.
372     *
373     * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY}
374     * is considered week 0. It returns the Julian day for the Monday
375     * {@code week} weeks after the Monday of the week containing the epoch.
376     *
377     * @param week Number of weeks since the epoch
378     * @return The julian day for the Monday of the given week since the epoch
379     */
380    public static int getJulianMondayFromWeeksSinceEpoch(int week) {
381        return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
382    }
383
384    /**
385     * Get first day of week as android.text.format.Time constant.
386     *
387     * @return the first day of week in android.text.format.Time
388     */
389    public static int getFirstDayOfWeek(Context context) {
390        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
391        String pref = prefs.getString(
392                GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT);
393
394        int startDay;
395        if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
396            startDay = Calendar.getInstance().getFirstDayOfWeek();
397        } else {
398            startDay = Integer.parseInt(pref);
399        }
400
401        if (startDay == Calendar.SATURDAY) {
402            return Time.SATURDAY;
403        } else if (startDay == Calendar.MONDAY) {
404            return Time.MONDAY;
405        } else {
406            return Time.SUNDAY;
407        }
408    }
409
410    /**
411     * @return true when week number should be shown.
412     */
413    public static boolean getShowWeekNumber(Context context) {
414        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
415        return prefs.getBoolean(
416                GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM);
417    }
418
419    /**
420     * @return true when declined events should be hidden.
421     */
422    public static boolean getHideDeclinedEvents(Context context) {
423        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
424        return prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false);
425    }
426
427    public static int getDaysPerWeek(Context context) {
428        final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
429        return prefs.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7);
430    }
431
432    /**
433     * Determine whether the column position is Saturday or not.
434     *
435     * @param column the column position
436     * @param firstDayOfWeek the first day of week in android.text.format.Time
437     * @return true if the column is Saturday position
438     */
439    public static boolean isSaturday(int column, int firstDayOfWeek) {
440        return (firstDayOfWeek == Time.SUNDAY && column == 6)
441                || (firstDayOfWeek == Time.MONDAY && column == 5)
442                || (firstDayOfWeek == Time.SATURDAY && column == 0);
443    }
444
445    /**
446     * Determine whether the column position is Sunday or not.
447     *
448     * @param column the column position
449     * @param firstDayOfWeek the first day of week in android.text.format.Time
450     * @return true if the column is Sunday position
451     */
452    public static boolean isSunday(int column, int firstDayOfWeek) {
453        return (firstDayOfWeek == Time.SUNDAY && column == 0)
454                || (firstDayOfWeek == Time.MONDAY && column == 6)
455                || (firstDayOfWeek == Time.SATURDAY && column == 1);
456    }
457
458    /**
459     * Convert given UTC time into current local time.
460     *
461     * @param recycle Time object to recycle, otherwise null.
462     * @param utcTime Time to convert, in UTC.
463     */
464    public static long convertUtcToLocal(Time recycle, long utcTime) {
465        if (recycle == null) {
466            recycle = new Time();
467        }
468        recycle.timezone = Time.TIMEZONE_UTC;
469        recycle.set(utcTime);
470        recycle.timezone = TimeZone.getDefault().getID();
471        return recycle.normalize(true);
472    }
473
474    /**
475     * Scan through a cursor of calendars and check if names are duplicated.
476     * This travels a cursor containing calendar display names and fills in the
477     * provided map with whether or not each name is repeated.
478     *
479     * @param isDuplicateName The map to put the duplicate check results in.
480     * @param cursor The query of calendars to check
481     * @param nameIndex The column of the query that contains the display name
482     */
483    public static void checkForDuplicateNames(
484            Map<String, Boolean> isDuplicateName, Cursor cursor, int nameIndex) {
485        isDuplicateName.clear();
486        cursor.moveToPosition(-1);
487        while (cursor.moveToNext()) {
488            String displayName = cursor.getString(nameIndex);
489            // Set it to true if we've seen this name before, false otherwise
490            if (displayName != null) {
491                isDuplicateName.put(displayName, isDuplicateName.containsKey(displayName));
492            }
493        }
494    }
495
496    /**
497     * Null-safe object comparison
498     *
499     * @param s1
500     * @param s2
501     * @return
502     */
503    public static boolean equals(Object o1, Object o2) {
504        return o1 == null ? o2 == null : o1.equals(o2);
505    }
506
507    public static void setAllowWeekForDetailView(boolean allowWeekView) {
508        mAllowWeekForDetailView  = allowWeekView;
509    }
510
511    public static boolean getAllowWeekForDetailView() {
512        return mAllowWeekForDetailView;
513    }
514}
515