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