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