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