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 17 package com.android.calendar; 18 19 import static android.provider.Calendar.EVENT_BEGIN_TIME; 20 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.database.Cursor; 25 import android.database.MatrixCursor; 26 import android.graphics.drawable.Drawable; 27 import android.graphics.drawable.GradientDrawable; 28 import android.net.Uri; 29 import android.text.TextUtils; 30 import android.text.format.Time; 31 import android.util.CalendarUtils.TimeZoneUtils; 32 import android.util.Log; 33 import android.view.animation.AlphaAnimation; 34 import android.widget.ViewFlipper; 35 36 import java.util.Calendar; 37 import java.util.Formatter; 38 import java.util.List; 39 import java.util.Map; 40 41 public class Utils { 42 private static final boolean DEBUG = true; 43 private static final String TAG = "CalUtils"; 44 private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF; 45 private static final int HIGH_ALPHA = 255 << 24; 46 private static final int MED_ALPHA = 180 << 24; 47 private static final int LOW_ALPHA = 150 << 24; 48 49 protected static final String OPEN_EMAIL_MARKER = " <"; 50 protected static final String CLOSE_EMAIL_MARKER = ">"; 51 /* The corner should be rounded on the top right and bottom right */ 52 private static final float[] CORNERS = new float[] {0, 0, 5, 5, 5, 5, 0, 0}; 53 54 // The name of the shared preferences file. This name must be maintained for historical 55 // reasons, as it's what PreferenceManager assigned the first time the file was created. 56 private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences"; 57 58 private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME); 59 60 public static void startActivity(Context context, String className, long time) { 61 Intent intent = new Intent(Intent.ACTION_VIEW); 62 63 intent.setClassName(context, className); 64 intent.putExtra(EVENT_BEGIN_TIME, time); 65 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); 66 67 context.startActivity(intent); 68 } 69 70 static String getSharedPreference(Context context, String key, String defaultValue) { 71 SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context); 72 return prefs.getString(key, defaultValue); 73 } 74 75 /** 76 * Writes a new home time zone to the db. 77 * 78 * Updates the home time zone in the db asynchronously and updates 79 * the local cache. Sending a time zone of **tbd** will cause it to 80 * be set to the device's time zone. null or empty tz will be ignored. 81 * 82 * @param context The calling activity 83 * @param timeZone The time zone to set Calendar to, or **tbd** 84 */ 85 public static void setTimeZone(Context context, String timeZone) { 86 mTZUtils.setTimeZone(context, timeZone); 87 } 88 89 /** 90 * Gets the time zone that Calendar should be displayed in 91 * 92 * This is a helper method to get the appropriate time zone for Calendar. If this 93 * is the first time this method has been called it will initiate an asynchronous 94 * query to verify that the data in preferences is correct. The callback supplied 95 * will only be called if this query returns a value other than what is stored in 96 * preferences and should cause the calling activity to refresh anything that 97 * depends on calling this method. 98 * 99 * @param context The calling activity 100 * @param callback The runnable that should execute if a query returns new values 101 * @return The string value representing the time zone Calendar should display 102 */ 103 public static String getTimeZone(Context context, Runnable callback) { 104 return mTZUtils.getTimeZone(context, callback); 105 } 106 107 /** 108 * Formats a date or a time range according to the local conventions. 109 * 110 * @param context the context is required only if the time is shown 111 * @param startMillis the start time in UTC milliseconds 112 * @param endMillis the end time in UTC milliseconds 113 * @param flags a bit mask of options See 114 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 115 * @return a string containing the formatted date/time range. 116 */ 117 public static String formatDateRange(Context context, long startMillis, 118 long endMillis, int flags) { 119 return mTZUtils.formatDateRange(context, startMillis, endMillis, flags); 120 } 121 122 static void setSharedPreference(Context context, String key, String value) { 123 SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context); 124 SharedPreferences.Editor editor = prefs.edit(); 125 editor.putString(key, value); 126 editor.apply(); 127 } 128 129 static void setDefaultView(Context context, int viewId) { 130 String activityString = CalendarApplication.ACTIVITY_NAMES[viewId]; 131 132 SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context); 133 SharedPreferences.Editor editor = prefs.edit(); 134 if (viewId == CalendarApplication.AGENDA_VIEW_ID || 135 viewId == CalendarApplication.DAY_VIEW_ID) { 136 // Record the (new) detail start view only for Agenda and Day 137 editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString); 138 } 139 140 // Record the (new) start view 141 editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString); 142 editor.apply(); 143 } 144 145 public static final Time timeFromIntent(Intent intent) { 146 Time time = new Time(); 147 time.set(timeFromIntentInMillis(intent)); 148 return time; 149 } 150 151 public static MatrixCursor matrixCursorFromCursor(Cursor cursor) { 152 MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames()); 153 int numColumns = cursor.getColumnCount(); 154 String data[] = new String[numColumns]; 155 cursor.moveToPosition(-1); 156 while (cursor.moveToNext()) { 157 for (int i = 0; i < numColumns; i++) { 158 data[i] = cursor.getString(i); 159 } 160 newCursor.addRow(data); 161 } 162 return newCursor; 163 } 164 165 /** 166 * Compares two cursors to see if they contain the same data. 167 * 168 * @return Returns true of the cursors contain the same data and are not null, false 169 * otherwise 170 */ 171 public static boolean compareCursors(Cursor c1, Cursor c2) { 172 if(c1 == null || c2 == null) { 173 return false; 174 } 175 176 int numColumns = c1.getColumnCount(); 177 if (numColumns != c2.getColumnCount()) { 178 return false; 179 } 180 181 if (c1.getCount() != c2.getCount()) { 182 return false; 183 } 184 185 c1.moveToPosition(-1); 186 c2.moveToPosition(-1); 187 while(c1.moveToNext() && c2.moveToNext()) { 188 for(int i = 0; i < numColumns; i++) { 189 if(!TextUtils.equals(c1.getString(i), c2.getString(i))) { 190 return false; 191 } 192 } 193 } 194 195 return true; 196 } 197 198 /** 199 * If the given intent specifies a time (in milliseconds since the epoch), 200 * then that time is returned. Otherwise, the current time is returned. 201 */ 202 public static final long timeFromIntentInMillis(Intent intent) { 203 // If the time was specified, then use that. Otherwise, use the current time. 204 Uri data = intent.getData(); 205 long millis = intent.getLongExtra(EVENT_BEGIN_TIME, -1); 206 if (millis == -1 && data != null && data.isHierarchical()) { 207 List<String> path = data.getPathSegments(); 208 if(path.size() == 2 && path.get(0).equals("time")) { 209 try { 210 millis = Long.valueOf(data.getLastPathSegment()); 211 } catch (NumberFormatException e) { 212 Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time " + 213 "found. Using current time."); 214 } 215 } 216 } 217 if (millis <= 0) { 218 millis = System.currentTimeMillis(); 219 } 220 return millis; 221 } 222 223 public static final void applyAlphaAnimation(ViewFlipper v) { 224 AlphaAnimation in = new AlphaAnimation(0.0f, 1.0f); 225 226 in.setStartOffset(0); 227 in.setDuration(500); 228 229 AlphaAnimation out = new AlphaAnimation(1.0f, 0.0f); 230 231 out.setStartOffset(0); 232 out.setDuration(500); 233 234 v.setInAnimation(in); 235 v.setOutAnimation(out); 236 } 237 238 public static Drawable getColorChip(int color) { 239 /* 240 * We want the color chip to have a nice gradient using 241 * the color of the calendar. To do this we use a GradientDrawable. 242 * The color supplied has an alpha of FF so we first do: 243 * color & 0x00FFFFFF 244 * to clear the alpha. Then we add our alpha to it. 245 * We use 3 colors to get a step effect where it starts off very 246 * light and quickly becomes dark and then a slow transition to 247 * be even darker. 248 */ 249 color &= CLEAR_ALPHA_MASK; 250 int startColor = color | HIGH_ALPHA; 251 int middleColor = color | MED_ALPHA; 252 int endColor = color | LOW_ALPHA; 253 int[] colors = new int[] {startColor, middleColor, endColor}; 254 GradientDrawable d = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors); 255 d.setCornerRadii(CORNERS); 256 return d; 257 } 258 259 /** 260 * Formats the given Time object so that it gives the month and year 261 * (for example, "September 2007"). 262 * 263 * @param time the time to format 264 * @return the string containing the weekday and the date 265 */ 266 public static String formatMonthYear(Context context, Time time) { 267 return time.format(context.getResources().getString(R.string.month_year)); 268 } 269 270 // TODO: replace this with the correct i18n way to do this 271 public static final String englishNthDay[] = { 272 "", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", 273 "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", 274 "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th", 275 "30th", "31st" 276 }; 277 278 public static String formatNth(int nth) { 279 return "the " + englishNthDay[nth]; 280 } 281 282 /** 283 * Returns a list joined together by the provided delimiter, for example, 284 * ["a", "b", "c"] could be joined into "a,b,c" 285 * 286 * @param things the things to join together 287 * @param delim the delimiter to use 288 * @return a string contained the things joined together 289 */ 290 public static String join(List<?> things, String delim) { 291 StringBuilder builder = new StringBuilder(); 292 boolean first = true; 293 for (Object thing : things) { 294 if (first) { 295 first = false; 296 } else { 297 builder.append(delim); 298 } 299 builder.append(thing.toString()); 300 } 301 return builder.toString(); 302 } 303 304 /** 305 * Sets the time to the beginning of the day (midnight) by clearing the 306 * hour, minute, and second fields. 307 */ 308 static void setTimeToStartOfDay(Time time) { 309 time.second = 0; 310 time.minute = 0; 311 time.hour = 0; 312 } 313 314 /** 315 * Get first day of week as android.text.format.Time constant. 316 * @return the first day of week in android.text.format.Time 317 */ 318 public static int getFirstDayOfWeek() { 319 int startDay = Calendar.getInstance().getFirstDayOfWeek(); 320 if (startDay == Calendar.SATURDAY) { 321 return Time.SATURDAY; 322 } else if (startDay == Calendar.MONDAY) { 323 return Time.MONDAY; 324 } else { 325 return Time.SUNDAY; 326 } 327 } 328 329 /** 330 * Determine whether the column position is Saturday or not. 331 * @param column the column position 332 * @param firstDayOfWeek the first day of week in android.text.format.Time 333 * @return true if the column is Saturday position 334 */ 335 public static boolean isSaturday(int column, int firstDayOfWeek) { 336 return (firstDayOfWeek == Time.SUNDAY && column == 6) 337 || (firstDayOfWeek == Time.MONDAY && column == 5) 338 || (firstDayOfWeek == Time.SATURDAY && column == 0); 339 } 340 341 /** 342 * Determine whether the column position is Sunday or not. 343 * @param column the column position 344 * @param firstDayOfWeek the first day of week in android.text.format.Time 345 * @return true if the column is Sunday position 346 */ 347 public static boolean isSunday(int column, int firstDayOfWeek) { 348 return (firstDayOfWeek == Time.SUNDAY && column == 0) 349 || (firstDayOfWeek == Time.MONDAY && column == 6) 350 || (firstDayOfWeek == Time.SATURDAY && column == 1); 351 } 352 353 /** 354 * Scan through a cursor of calendars and check if names are duplicated. 355 * 356 * This travels a cursor containing calendar display names and fills in the provided map with 357 * whether or not each name is repeated. 358 * @param isDuplicateName The map to put the duplicate check results in. 359 * @param cursor The query of calendars to check 360 * @param nameIndex The column of the query that contains the display name 361 */ 362 public static void checkForDuplicateNames(Map<String, Boolean> isDuplicateName, Cursor cursor, 363 int nameIndex) { 364 isDuplicateName.clear(); 365 cursor.moveToPosition(-1); 366 while (cursor.moveToNext()) { 367 String displayName = cursor.getString(nameIndex); 368 // Set it to true if we've seen this name before, false otherwise 369 if (displayName != null) { 370 isDuplicateName.put(displayName, isDuplicateName.containsKey(displayName)); 371 } 372 } 373 } 374 } 375