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