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