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