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