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