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