1 /* 2 * Copyright (C) 2012 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.deskclock; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorSet; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.app.AlarmManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.res.Resources; 30 import android.graphics.Color; 31 import android.graphics.Paint; 32 import android.graphics.PorterDuff; 33 import android.graphics.PorterDuffColorFilter; 34 import android.graphics.Typeface; 35 import android.net.Uri; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.SystemClock; 39 import android.preference.PreferenceManager; 40 import android.text.Spannable; 41 import android.text.SpannableString; 42 import android.text.TextUtils; 43 import android.text.format.DateFormat; 44 import android.text.format.DateUtils; 45 import android.text.format.Time; 46 import android.text.style.AbsoluteSizeSpan; 47 import android.text.style.StyleSpan; 48 import android.text.style.TypefaceSpan; 49 import android.view.MenuItem; 50 import android.view.View; 51 import android.view.animation.AccelerateInterpolator; 52 import android.view.animation.DecelerateInterpolator; 53 import android.widget.TextClock; 54 import android.widget.TextView; 55 56 import com.android.deskclock.stopwatch.Stopwatches; 57 import com.android.deskclock.timer.Timers; 58 import com.android.deskclock.worldclock.CityObj; 59 60 import java.text.SimpleDateFormat; 61 import java.util.Calendar; 62 import java.util.Date; 63 import java.util.GregorianCalendar; 64 import java.util.Locale; 65 import java.util.TimeZone; 66 67 68 public class Utils { 69 private final static String PARAM_LANGUAGE_CODE = "hl"; 70 71 /** 72 * Help URL query parameter key for the app version. 73 */ 74 private final static String PARAM_VERSION = "version"; 75 76 /** 77 * Cached version code to prevent repeated calls to the package manager. 78 */ 79 private static String sCachedVersionCode = null; 80 81 /** 82 * Array of single-character day of week symbols {'S', 'M', 'T', 'W', 'T', 'F', 'S'} 83 */ 84 private static String[] sShortWeekdays = null; 85 86 /** Types that may be used for clock displays. **/ 87 public static final String CLOCK_TYPE_DIGITAL = "digital"; 88 public static final String CLOCK_TYPE_ANALOG = "analog"; 89 90 /** The background colors of the app, it changes thru out the day to mimic the sky. **/ 91 public static final String[] BACKGROUND_SPECTRUM = { "#212121", "#27232e", "#2d253a", 92 "#332847", "#382a53", "#3e2c5f", "#442e6c", "#393a7a", "#2e4687", "#235395", "#185fa2", 93 "#0d6baf", "#0277bd", "#0d6cb1", "#1861a6", "#23569b", "#2d4a8f", "#383f84", "#433478", 94 "#3d3169", "#382e5b", "#322b4d", "#2c273e", "#272430" }; 95 96 /** 97 * Returns whether the SDK is KitKat or later 98 */ isKitKatOrLater()99 public static boolean isKitKatOrLater() { 100 return Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2; 101 } 102 103 prepareHelpMenuItem(Context context, MenuItem helpMenuItem)104 public static void prepareHelpMenuItem(Context context, MenuItem helpMenuItem) { 105 String helpUrlString = context.getResources().getString(R.string.desk_clock_help_url); 106 if (TextUtils.isEmpty(helpUrlString)) { 107 // The help url string is empty or null, so set the help menu item to be invisible. 108 helpMenuItem.setVisible(false); 109 return; 110 } 111 // The help url string exists, so first add in some extra query parameters. 87 112 final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUrlString)); 113 114 // Then, create an intent that will be fired when the user 115 // selects this help menu item. 116 Intent intent = new Intent(Intent.ACTION_VIEW, fullUri); 117 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 118 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 119 120 // Set the intent to the help menu item, show the help menu item in the overflow 121 // menu, and make it visible. 122 helpMenuItem.setIntent(intent); 123 helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 124 helpMenuItem.setVisible(true); 125 } 126 127 /** 128 * Adds two query parameters into the Uri, namely the language code and the version code 129 * of the application's package as gotten via the context. 130 * @return the uri with added query parameters 131 */ uriWithAddedParameters(Context context, Uri baseUri)132 private static Uri uriWithAddedParameters(Context context, Uri baseUri) { 133 Uri.Builder builder = baseUri.buildUpon(); 134 135 // Add in the preferred language 136 builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString()); 137 138 // Add in the package version code 139 if (sCachedVersionCode == null) { 140 // There is no cached version code, so try to get it from the package manager. 141 try { 142 // cache the version code 143 PackageInfo info = context.getPackageManager().getPackageInfo( 144 context.getPackageName(), 0); 145 sCachedVersionCode = Integer.toString(info.versionCode); 146 147 // append the version code to the uri 148 builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); 149 } catch (NameNotFoundException e) { 150 // Cannot find the package name, so don't add in the version parameter 151 // This shouldn't happen. 152 LogUtils.wtf("Invalid package name for context " + e); 153 } 154 } else { 155 builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); 156 } 157 158 // Build the full uri and return it 159 return builder.build(); 160 } 161 getTimeNow()162 public static long getTimeNow() { 163 return SystemClock.elapsedRealtime(); 164 } 165 166 /** 167 * Calculate the amount by which the radius of a CircleTimerView should be offset by the any 168 * of the extra painted objects. 169 */ calculateRadiusOffset( float strokeSize, float dotStrokeSize, float markerStrokeSize)170 public static float calculateRadiusOffset( 171 float strokeSize, float dotStrokeSize, float markerStrokeSize) { 172 return Math.max(strokeSize, Math.max(dotStrokeSize, markerStrokeSize)); 173 } 174 175 /** 176 * Uses {@link Utils#calculateRadiusOffset(float, float, float)} after fetching the values 177 * from the resources just as {@link CircleTimerView#init(android.content.Context)} does. 178 */ calculateRadiusOffset(Resources resources)179 public static float calculateRadiusOffset(Resources resources) { 180 if (resources != null) { 181 float strokeSize = resources.getDimension(R.dimen.circletimer_circle_size); 182 float dotStrokeSize = resources.getDimension(R.dimen.circletimer_dot_size); 183 float markerStrokeSize = resources.getDimension(R.dimen.circletimer_marker_size); 184 return calculateRadiusOffset(strokeSize, dotStrokeSize, markerStrokeSize); 185 } else { 186 return 0f; 187 } 188 } 189 190 /** The pressed color used throughout the app. If this method is changed, it will not have 191 * any effect on the button press states, and those must be changed separately. 192 **/ getPressedColorId()193 public static int getPressedColorId() { 194 return R.color.hot_pink; 195 } 196 197 /** The un-pressed color used throughout the app. If this method is changed, it will not have 198 * any effect on the button press states, and those must be changed separately. 199 **/ getGrayColorId()200 public static int getGrayColorId() { 201 return R.color.clock_gray; 202 } 203 204 /** 205 * Clears the persistent data of stopwatch (start time, state, laps, etc...). 206 */ clearSwSharedPref(SharedPreferences prefs)207 public static void clearSwSharedPref(SharedPreferences prefs) { 208 SharedPreferences.Editor editor = prefs.edit(); 209 editor.remove (Stopwatches.PREF_START_TIME); 210 editor.remove (Stopwatches.PREF_ACCUM_TIME); 211 editor.remove (Stopwatches.PREF_STATE); 212 int lapNum = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET); 213 for (int i = 0; i < lapNum; i++) { 214 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(i); 215 editor.remove(key); 216 } 217 editor.remove(Stopwatches.PREF_LAP_NUM); 218 editor.apply(); 219 } 220 221 /** 222 * Broadcast a message to show the in-use timers in the notifications 223 */ showInUseNotifications(Context context)224 public static void showInUseNotifications(Context context) { 225 Intent timerIntent = new Intent(); 226 timerIntent.setAction(Timers.NOTIF_IN_USE_SHOW); 227 context.sendBroadcast(timerIntent); 228 } 229 230 /** 231 * Broadcast a message to show the in-use timers in the notifications 232 */ showTimesUpNotifications(Context context)233 public static void showTimesUpNotifications(Context context) { 234 Intent timerIntent = new Intent(); 235 timerIntent.setAction(Timers.NOTIF_TIMES_UP_SHOW); 236 context.sendBroadcast(timerIntent); 237 } 238 239 /** 240 * Broadcast a message to cancel the in-use timers in the notifications 241 */ cancelTimesUpNotifications(Context context)242 public static void cancelTimesUpNotifications(Context context) { 243 Intent timerIntent = new Intent(); 244 timerIntent.setAction(Timers.NOTIF_TIMES_UP_CANCEL); 245 context.sendBroadcast(timerIntent); 246 } 247 248 /** Runnable for use with screensaver and dream, to move the clock every minute. 249 * registerViews() must be called prior to posting. 250 */ 251 public static class ScreensaverMoveSaverRunnable implements Runnable { 252 static final long MOVE_DELAY = 60000; // DeskClock.SCREEN_SAVER_MOVE_DELAY; 253 static final long SLIDE_TIME = 10000; 254 static final long FADE_TIME = 3000; 255 256 static final boolean SLIDE = false; 257 258 private View mContentView, mSaverView; 259 private final Handler mHandler; 260 261 private static TimeInterpolator mSlowStartWithBrakes; 262 263 ScreensaverMoveSaverRunnable(Handler handler)264 public ScreensaverMoveSaverRunnable(Handler handler) { 265 mHandler = handler; 266 mSlowStartWithBrakes = new TimeInterpolator() { 267 @Override 268 public float getInterpolation(float x) { 269 return (float)(Math.cos((Math.pow(x,3) + 1) * Math.PI) / 2.0f) + 0.5f; 270 } 271 }; 272 } 273 registerViews(View contentView, View saverView)274 public void registerViews(View contentView, View saverView) { 275 mContentView = contentView; 276 mSaverView = saverView; 277 } 278 279 @Override run()280 public void run() { 281 long delay = MOVE_DELAY; 282 if (mContentView == null || mSaverView == null) { 283 mHandler.removeCallbacks(this); 284 mHandler.postDelayed(this, delay); 285 return; 286 } 287 288 final float xrange = mContentView.getWidth() - mSaverView.getWidth(); 289 final float yrange = mContentView.getHeight() - mSaverView.getHeight(); 290 291 if (xrange == 0 && yrange == 0) { 292 delay = 500; // back in a split second 293 } else { 294 final int nextx = (int) (Math.random() * xrange); 295 final int nexty = (int) (Math.random() * yrange); 296 297 if (mSaverView.getAlpha() == 0f) { 298 // jump right there 299 mSaverView.setX(nextx); 300 mSaverView.setY(nexty); 301 ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f) 302 .setDuration(FADE_TIME) 303 .start(); 304 } else { 305 AnimatorSet s = new AnimatorSet(); 306 Animator xMove = ObjectAnimator.ofFloat(mSaverView, 307 "x", mSaverView.getX(), nextx); 308 Animator yMove = ObjectAnimator.ofFloat(mSaverView, 309 "y", mSaverView.getY(), nexty); 310 311 Animator xShrink = ObjectAnimator.ofFloat(mSaverView, "scaleX", 1f, 0.85f); 312 Animator xGrow = ObjectAnimator.ofFloat(mSaverView, "scaleX", 0.85f, 1f); 313 314 Animator yShrink = ObjectAnimator.ofFloat(mSaverView, "scaleY", 1f, 0.85f); 315 Animator yGrow = ObjectAnimator.ofFloat(mSaverView, "scaleY", 0.85f, 1f); 316 AnimatorSet shrink = new AnimatorSet(); shrink.play(xShrink).with(yShrink); 317 AnimatorSet grow = new AnimatorSet(); grow.play(xGrow).with(yGrow); 318 319 Animator fadeout = ObjectAnimator.ofFloat(mSaverView, "alpha", 1f, 0f); 320 Animator fadein = ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f); 321 322 323 if (SLIDE) { 324 s.play(xMove).with(yMove); 325 s.setDuration(SLIDE_TIME); 326 327 s.play(shrink.setDuration(SLIDE_TIME/2)); 328 s.play(grow.setDuration(SLIDE_TIME/2)).after(shrink); 329 s.setInterpolator(mSlowStartWithBrakes); 330 } else { 331 AccelerateInterpolator accel = new AccelerateInterpolator(); 332 DecelerateInterpolator decel = new DecelerateInterpolator(); 333 334 shrink.setDuration(FADE_TIME).setInterpolator(accel); 335 fadeout.setDuration(FADE_TIME).setInterpolator(accel); 336 grow.setDuration(FADE_TIME).setInterpolator(decel); 337 fadein.setDuration(FADE_TIME).setInterpolator(decel); 338 s.play(shrink); 339 s.play(fadeout); 340 s.play(xMove.setDuration(0)).after(FADE_TIME); 341 s.play(yMove.setDuration(0)).after(FADE_TIME); 342 s.play(fadein).after(FADE_TIME); 343 s.play(grow).after(FADE_TIME); 344 } 345 s.start(); 346 } 347 348 long now = System.currentTimeMillis(); 349 long adjust = (now % 60000); 350 delay = delay 351 + (MOVE_DELAY - adjust) // minute aligned 352 - (SLIDE ? 0 : FADE_TIME) // start moving before the fade 353 ; 354 } 355 356 mHandler.removeCallbacks(this); 357 mHandler.postDelayed(this, delay); 358 } 359 } 360 361 /** Setup to find out when the quarter-hour changes (e.g. Kathmandu is GMT+5:45) **/ getAlarmOnQuarterHour()362 public static long getAlarmOnQuarterHour() { 363 Calendar nextQuarter = Calendar.getInstance(); 364 // Set 1 second to ensure quarter-hour threshold passed. 365 nextQuarter.set(Calendar.SECOND, 1); 366 nextQuarter.set(Calendar.MILLISECOND, 0); 367 int minute = nextQuarter.get(Calendar.MINUTE); 368 nextQuarter.add(Calendar.MINUTE, 15 - (minute % 15)); 369 long alarmOnQuarterHour = nextQuarter.getTimeInMillis(); 370 long now = System.currentTimeMillis(); 371 long delta = alarmOnQuarterHour - now; 372 if (0 >= delta || delta > 901000) { 373 // Something went wrong in the calculation, schedule something that is 374 // about 15 minutes. Next time , it will align with the 15 minutes border. 375 alarmOnQuarterHour = now + 901000; 376 } 377 return alarmOnQuarterHour; 378 } 379 380 // Setup a thread that starts at midnight plus one second. The extra second is added to ensure 381 // the date has changed. setMidnightUpdater(Handler handler, Runnable runnable)382 public static void setMidnightUpdater(Handler handler, Runnable runnable) { 383 String timezone = TimeZone.getDefault().getID(); 384 if (handler == null || runnable == null || timezone == null) { 385 return; 386 } 387 long now = System.currentTimeMillis(); 388 Time time = new Time(timezone); 389 time.set(now); 390 long runInMillis = ((24 - time.hour) * 3600 - time.minute * 60 - time.second + 1) * 1000; 391 handler.removeCallbacks(runnable); 392 handler.postDelayed(runnable, runInMillis); 393 } 394 395 // Stop the midnight update thread cancelMidnightUpdater(Handler handler, Runnable runnable)396 public static void cancelMidnightUpdater(Handler handler, Runnable runnable) { 397 if (handler == null || runnable == null) { 398 return; 399 } 400 handler.removeCallbacks(runnable); 401 } 402 403 // Setup a thread that starts at the quarter-hour plus one second. The extra second is added to 404 // ensure dates have changed. setQuarterHourUpdater(Handler handler, Runnable runnable)405 public static void setQuarterHourUpdater(Handler handler, Runnable runnable) { 406 String timezone = TimeZone.getDefault().getID(); 407 if (handler == null || runnable == null || timezone == null) { 408 return; 409 } 410 long runInMillis = getAlarmOnQuarterHour() - System.currentTimeMillis(); 411 // Ensure the delay is at least one second. 412 if (runInMillis < 1000) { 413 runInMillis = 1000; 414 } 415 handler.removeCallbacks(runnable); 416 handler.postDelayed(runnable, runInMillis); 417 } 418 419 // Stop the quarter-hour update thread cancelQuarterHourUpdater(Handler handler, Runnable runnable)420 public static void cancelQuarterHourUpdater(Handler handler, Runnable runnable) { 421 if (handler == null || runnable == null) { 422 return; 423 } 424 handler.removeCallbacks(runnable); 425 } 426 427 /** 428 * For screensavers to set whether the digital or analog clock should be displayed. 429 * Returns the view to be displayed. 430 */ setClockStyle(Context context, View digitalClock, View analogClock, String clockStyleKey)431 public static View setClockStyle(Context context, View digitalClock, View analogClock, 432 String clockStyleKey) { 433 SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); 434 String defaultClockStyle = context.getResources().getString(R.string.default_clock_style); 435 String style = sharedPref.getString(clockStyleKey, defaultClockStyle); 436 View returnView; 437 if (style.equals(CLOCK_TYPE_ANALOG)) { 438 digitalClock.setVisibility(View.GONE); 439 analogClock.setVisibility(View.VISIBLE); 440 returnView = analogClock; 441 } else { 442 digitalClock.setVisibility(View.VISIBLE); 443 analogClock.setVisibility(View.GONE); 444 returnView = digitalClock; 445 } 446 447 return returnView; 448 } 449 450 /** 451 * For screensavers to dim the lights if necessary. 452 */ dimClockView(boolean dim, View clockView)453 public static void dimClockView(boolean dim, View clockView) { 454 Paint paint = new Paint(); 455 paint.setColor(Color.WHITE); 456 paint.setColorFilter(new PorterDuffColorFilter( 457 (dim ? 0x40FFFFFF : 0xC0FFFFFF), 458 PorterDuff.Mode.MULTIPLY)); 459 clockView.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 460 } 461 462 /** 463 * @return The next alarm from {@link AlarmManager} 464 */ getNextAlarm(Context context)465 public static String getNextAlarm(Context context) { 466 String timeString = null; 467 final AlarmManager.AlarmClockInfo info = ((AlarmManager) context.getSystemService( 468 Context.ALARM_SERVICE)).getNextAlarmClock(); 469 if (info != null) { 470 final long triggerTime = info.getTriggerTime(); 471 final Calendar alarmTime = Calendar.getInstance(); 472 alarmTime.setTimeInMillis(triggerTime); 473 timeString = AlarmUtils.getFormattedTime(context, alarmTime); 474 } 475 return timeString; 476 } 477 478 /** Clock views can call this to refresh their alarm to the next upcoming value. **/ refreshAlarm(Context context, View clock)479 public static void refreshAlarm(Context context, View clock) { 480 final String nextAlarm = getNextAlarm(context); 481 TextView nextAlarmView; 482 nextAlarmView = (TextView) clock.findViewById(R.id.nextAlarm); 483 if (!TextUtils.isEmpty(nextAlarm) && nextAlarmView != null) { 484 nextAlarmView.setText( 485 context.getString(R.string.control_set_alarm_with_existing, nextAlarm)); 486 nextAlarmView.setContentDescription(context.getResources().getString( 487 R.string.next_alarm_description, nextAlarm)); 488 nextAlarmView.setVisibility(View.VISIBLE); 489 } else { 490 nextAlarmView.setVisibility(View.GONE); 491 } 492 } 493 494 /** Clock views can call this to refresh their date. **/ updateDate( String dateFormat, String dateFormatForAccessibility, View clock)495 public static void updateDate( 496 String dateFormat, String dateFormatForAccessibility, View clock) { 497 498 Date now = new Date(); 499 TextView dateDisplay; 500 dateDisplay = (TextView) clock.findViewById(R.id.date); 501 if (dateDisplay != null) { 502 final Locale l = Locale.getDefault(); 503 String fmt = DateFormat.getBestDateTimePattern(l, dateFormat); 504 SimpleDateFormat sdf = new SimpleDateFormat(fmt, l); 505 dateDisplay.setText(sdf.format(now)); 506 dateDisplay.setVisibility(View.VISIBLE); 507 fmt = DateFormat.getBestDateTimePattern(l, dateFormatForAccessibility); 508 sdf = new SimpleDateFormat(fmt, l); 509 dateDisplay.setContentDescription(sdf.format(now)); 510 } 511 } 512 513 /*** 514 * Formats the time in the TextClock according to the Locale with a special 515 * formatting treatment for the am/pm label. 516 * @param clock - TextClock to format 517 * @param amPmFontSize - size of the am/pm label since it is usually smaller 518 * than the clock time size. 519 */ setTimeFormat(TextClock clock, int amPmFontSize)520 public static void setTimeFormat(TextClock clock, int amPmFontSize) { 521 if (clock != null) { 522 // Get the best format for 12 hours mode according to the locale 523 clock.setFormat12Hour(get12ModeFormat(amPmFontSize)); 524 // Get the best format for 24 hours mode according to the locale 525 clock.setFormat24Hour(get24ModeFormat()); 526 } 527 } 528 /*** 529 * @param amPmFontSize - size of am/pm label (label removed is size is 0). 530 * @return format string for 12 hours mode time 531 */ get12ModeFormat(int amPmFontSize)532 public static CharSequence get12ModeFormat(int amPmFontSize) { 533 String skeleton = "hma"; 534 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 535 // Remove the am/pm 536 if (amPmFontSize <= 0) { 537 pattern.replaceAll("a", "").trim(); 538 } 539 // Replace spaces with "Hair Space" 540 pattern = pattern.replaceAll(" ", "\u200A"); 541 // Build a spannable so that the am/pm will be formatted 542 int amPmPos = pattern.indexOf('a'); 543 if (amPmPos == -1) { 544 return pattern; 545 } 546 Spannable sp = new SpannableString(pattern); 547 sp.setSpan(new StyleSpan(Typeface.NORMAL), amPmPos, amPmPos + 1, 548 Spannable.SPAN_POINT_MARK); 549 sp.setSpan(new AbsoluteSizeSpan(amPmFontSize), amPmPos, amPmPos + 1, 550 Spannable.SPAN_POINT_MARK); 551 sp.setSpan(new TypefaceSpan("sans-serif"), amPmPos, amPmPos + 1, 552 Spannable.SPAN_POINT_MARK); 553 return sp; 554 } 555 get24ModeFormat()556 public static CharSequence get24ModeFormat() { 557 String skeleton = "Hm"; 558 return DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 559 } 560 loadCitiesFromXml(Context c)561 public static CityObj[] loadCitiesFromXml(Context c) { 562 Resources r = c.getResources(); 563 // Read strings array of name,timezone, id 564 // make sure the list are the same length 565 String[] cities = r.getStringArray(R.array.cities_names); 566 String[] timezones = r.getStringArray(R.array.cities_tz); 567 String[] ids = r.getStringArray(R.array.cities_id); 568 int minLength = cities.length; 569 if (cities.length != timezones.length || ids.length != cities.length) { 570 minLength = Math.min(cities.length, Math.min(timezones.length, ids.length)); 571 LogUtils.e("City lists sizes are not the same, truncating"); 572 } 573 CityObj[] tempList = new CityObj[minLength]; 574 for (int i = 0; i < cities.length; i++) { 575 tempList[i] = new CityObj(cities[i], timezones[i], ids[i]); 576 } 577 return tempList; 578 } 579 580 /** 581 * Returns string denoting the timezone hour offset (e.g. GMT-8:00) 582 */ getGMTHourOffset(TimeZone timezone, boolean showMinutes)583 public static String getGMTHourOffset(TimeZone timezone, boolean showMinutes) { 584 StringBuilder sb = new StringBuilder(); 585 sb.append("GMT "); 586 int gmtOffset = timezone.getRawOffset(); 587 if (gmtOffset < 0) { 588 sb.append('-'); 589 } else { 590 sb.append('+'); 591 } 592 sb.append(Math.abs(gmtOffset) / DateUtils.HOUR_IN_MILLIS); // Hour 593 594 if (showMinutes) { 595 final int min = (Math.abs(gmtOffset) / (int) DateUtils.MINUTE_IN_MILLIS) % 60; 596 sb.append(':'); 597 if (min < 10) { 598 sb.append('0'); 599 } 600 sb.append(min); 601 } 602 603 return sb.toString(); 604 } 605 getCityName(CityObj city, CityObj dbCity)606 public static String getCityName(CityObj city, CityObj dbCity) { 607 return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName; 608 } 609 getCurrentHourColor()610 public static int getCurrentHourColor() { 611 final int hourOfDay = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); 612 return Color.parseColor(BACKGROUND_SPECTRUM[hourOfDay]); 613 } 614 getNextHourColor()615 public static int getNextHourColor() { 616 final int currHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); 617 return Color.parseColor(BACKGROUND_SPECTRUM[currHour < 24 ? currHour + 1 : 1]); 618 } 619 620 /** 621 * To get an array of single-character day of week symbols {'S', 'M', 'T', 'W', 'T', 'F', 'S'} 622 * @return the array of symbols 623 */ getShortWeekdays()624 public static String[] getShortWeekdays() { 625 if (sShortWeekdays == null) { 626 final String[] shortWeekdays = new String[7]; 627 final SimpleDateFormat format = new SimpleDateFormat("EEEEE"); 628 // Create a date (2014/07/20) that is a Sunday 629 long aSunday = new GregorianCalendar(2014, Calendar.JULY, 20).getTimeInMillis(); 630 for (int day = 0; day < 7; day++) { 631 shortWeekdays[day] = format.format(new Date(aSunday + day * DateUtils.DAY_IN_MILLIS)); 632 } 633 sShortWeekdays = shortWeekdays; 634 } 635 return sShortWeekdays; 636 } 637 } 638