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