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