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.app.PendingIntent; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Resources; 31 import android.graphics.Color; 32 import android.graphics.Paint; 33 import android.graphics.PorterDuff; 34 import android.graphics.PorterDuffColorFilter; 35 import android.net.Uri; 36 import android.os.Handler; 37 import android.os.SystemClock; 38 import android.preference.PreferenceManager; 39 import android.provider.Settings; 40 import android.text.TextUtils; 41 import android.text.format.DateFormat; 42 import android.view.MenuItem; 43 import android.view.View; 44 import android.view.animation.AccelerateInterpolator; 45 import android.view.animation.DecelerateInterpolator; 46 import android.widget.TextView; 47 48 import com.android.deskclock.stopwatch.Stopwatches; 49 import com.android.deskclock.timer.Timers; 50 import com.android.deskclock.worldclock.CityObj; 51 52 import java.text.Collator; 53 import java.util.Arrays; 54 import java.util.Calendar; 55 import java.util.Comparator; 56 import java.util.Locale; 57 58 59 public class Utils { 60 private final static String TAG = Utils.class.getName(); 61 62 private final static String PARAM_LANGUAGE_CODE = "hl"; 63 64 /** 65 * Help URL query parameter key for the app version. 66 */ 67 private final static String PARAM_VERSION = "version"; 68 69 /** 70 * Cached version code to prevent repeated calls to the package manager. 71 */ 72 private static String sCachedVersionCode = null; 73 74 /** 75 * Intent to be used for checking if a clock's date has changed. Must be every fifteen 76 * minutes because not all time zones are hour-locked. 77 **/ 78 public static final String ACTION_ON_QUARTER_HOUR = "com.android.deskclock.ON_QUARTER_HOUR"; 79 80 /** Types that may be used for clock displays. **/ 81 public static final String CLOCK_TYPE_DIGITAL = "digital"; 82 public static final String CLOCK_TYPE_ANALOG = "analog"; 83 84 /** 85 * time format constants 86 */ 87 public final static String HOURS_24 = "kk"; 88 public final static String HOURS = "h"; 89 public final static String MINUTES = ":mm"; 90 91 prepareHelpMenuItem(Context context, MenuItem helpMenuItem)92 public static void prepareHelpMenuItem(Context context, MenuItem helpMenuItem) { 93 String helpUrlString = context.getResources().getString(R.string.desk_clock_help_url); 94 if (TextUtils.isEmpty(helpUrlString)) { 95 // The help url string is empty or null, so set the help menu item to be invisible. 96 helpMenuItem.setVisible(false); 97 return; 98 } 99 // The help url string exists, so first add in some extra query parameters. 87 100 final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUrlString)); 101 102 // Then, create an intent that will be fired when the user 103 // selects this help menu item. 104 Intent intent = new Intent(Intent.ACTION_VIEW, fullUri); 105 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 106 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 107 108 // Set the intent to the help menu item, show the help menu item in the overflow 109 // menu, and make it visible. 110 helpMenuItem.setIntent(intent); 111 helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 112 helpMenuItem.setVisible(true); 113 } 114 115 /** 116 * Adds two query parameters into the Uri, namely the language code and the version code 117 * of the app's package as gotten via the context. 118 * @return the uri with added query parameters 119 */ uriWithAddedParameters(Context context, Uri baseUri)120 private static Uri uriWithAddedParameters(Context context, Uri baseUri) { 121 Uri.Builder builder = baseUri.buildUpon(); 122 123 // Add in the preferred language 124 builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString()); 125 126 // Add in the package version code 127 if (sCachedVersionCode == null) { 128 // There is no cached version code, so try to get it from the package manager. 129 try { 130 // cache the version code 131 PackageInfo info = context.getPackageManager().getPackageInfo( 132 context.getPackageName(), 0); 133 sCachedVersionCode = Integer.toString(info.versionCode); 134 135 // append the version code to the uri 136 builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); 137 } catch (NameNotFoundException e) { 138 // Cannot find the package name, so don't add in the version parameter 139 // This shouldn't happen. 140 Log.wtf("Invalid package name for context " + e); 141 } 142 } else { 143 builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); 144 } 145 146 // Build the full uri and return it 147 return builder.build(); 148 } 149 getTimeNow()150 public static long getTimeNow() { 151 return SystemClock.elapsedRealtime(); 152 } 153 154 /** 155 * Calculate the amount by which the radius of a CircleTimerView should be offset by the any 156 * of the extra painted objects. 157 */ calculateRadiusOffset( float strokeSize, float diamondStrokeSize, float markerStrokeSize)158 public static float calculateRadiusOffset( 159 float strokeSize, float diamondStrokeSize, float markerStrokeSize) { 160 return Math.max(strokeSize, Math.max(diamondStrokeSize, markerStrokeSize)); 161 } 162 163 /** The pressed color used throughout the app. If this method is changed, it will not have 164 * any effect on the button press states, and those must be changed separately. 165 **/ getPressedColorId()166 public static int getPressedColorId() { 167 return R.color.clock_red; 168 } 169 170 /** The un-pressed color used throughout the app. If this method is changed, it will not have 171 * any effect on the button press states, and those must be changed separately. 172 **/ getGrayColorId()173 public static int getGrayColorId() { 174 return R.color.clock_gray; 175 } 176 177 /** 178 * Clears the persistent data of stopwatch (start time, state, laps, etc...). 179 */ clearSwSharedPref(SharedPreferences prefs)180 public static void clearSwSharedPref(SharedPreferences prefs) { 181 SharedPreferences.Editor editor = prefs.edit(); 182 editor.remove (Stopwatches.PREF_START_TIME); 183 editor.remove (Stopwatches.PREF_ACCUM_TIME); 184 editor.remove (Stopwatches.PREF_STATE); 185 int lapNum = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET); 186 for (int i = 0; i < lapNum; i++) { 187 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(i); 188 editor.remove(key); 189 } 190 editor.remove(Stopwatches.PREF_LAP_NUM); 191 editor.apply(); 192 } 193 194 /** 195 * Broadcast a message to show the in-use timers in the notifications 196 */ showInUseNotifications(Context context)197 public static void showInUseNotifications(Context context) { 198 Intent timerIntent = new Intent(); 199 timerIntent.setAction(Timers.NOTIF_IN_USE_SHOW); 200 context.sendBroadcast(timerIntent); 201 } 202 203 /** Runnable for use with screensaver and dream, to move the clock every minute. 204 * registerViews() must be called prior to posting. 205 */ 206 public static class ScreensaverMoveSaverRunnable implements Runnable { 207 static final long MOVE_DELAY = 60000; // DeskClock.SCREEN_SAVER_MOVE_DELAY; 208 static final long SLIDE_TIME = 10000; 209 static final long FADE_TIME = 3000; 210 211 static final boolean SLIDE = false; 212 213 private View mContentView, mSaverView; 214 private final Handler mHandler; 215 216 private static TimeInterpolator mSlowStartWithBrakes; 217 218 ScreensaverMoveSaverRunnable(Handler handler)219 public ScreensaverMoveSaverRunnable(Handler handler) { 220 mHandler = handler; 221 mSlowStartWithBrakes = new TimeInterpolator() { 222 @Override 223 public float getInterpolation(float x) { 224 return (float)(Math.cos((Math.pow(x,3) + 1) * Math.PI) / 2.0f) + 0.5f; 225 } 226 }; 227 } 228 registerViews(View contentView, View saverView)229 public void registerViews(View contentView, View saverView) { 230 mContentView = contentView; 231 mSaverView = saverView; 232 } 233 234 @Override run()235 public void run() { 236 long delay = MOVE_DELAY; 237 if (mContentView == null || mSaverView == null) { 238 mHandler.removeCallbacks(this); 239 mHandler.postDelayed(this, delay); 240 return; 241 } 242 243 final float xrange = mContentView.getWidth() - mSaverView.getWidth(); 244 final float yrange = mContentView.getHeight() - mSaverView.getHeight(); 245 Log.v("xrange: "+xrange+" yrange: "+yrange); 246 247 if (xrange == 0 && yrange == 0) { 248 delay = 500; // back in a split second 249 } else { 250 final int nextx = (int) (Math.random() * xrange); 251 final int nexty = (int) (Math.random() * yrange); 252 253 if (mSaverView.getAlpha() == 0f) { 254 // jump right there 255 mSaverView.setX(nextx); 256 mSaverView.setY(nexty); 257 ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f) 258 .setDuration(FADE_TIME) 259 .start(); 260 } else { 261 AnimatorSet s = new AnimatorSet(); 262 Animator xMove = ObjectAnimator.ofFloat(mSaverView, 263 "x", mSaverView.getX(), nextx); 264 Animator yMove = ObjectAnimator.ofFloat(mSaverView, 265 "y", mSaverView.getY(), nexty); 266 267 Animator xShrink = ObjectAnimator.ofFloat(mSaverView, "scaleX", 1f, 0.85f); 268 Animator xGrow = ObjectAnimator.ofFloat(mSaverView, "scaleX", 0.85f, 1f); 269 270 Animator yShrink = ObjectAnimator.ofFloat(mSaverView, "scaleY", 1f, 0.85f); 271 Animator yGrow = ObjectAnimator.ofFloat(mSaverView, "scaleY", 0.85f, 1f); 272 AnimatorSet shrink = new AnimatorSet(); shrink.play(xShrink).with(yShrink); 273 AnimatorSet grow = new AnimatorSet(); grow.play(xGrow).with(yGrow); 274 275 Animator fadeout = ObjectAnimator.ofFloat(mSaverView, "alpha", 1f, 0f); 276 Animator fadein = ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f); 277 278 279 if (SLIDE) { 280 s.play(xMove).with(yMove); 281 s.setDuration(SLIDE_TIME); 282 283 s.play(shrink.setDuration(SLIDE_TIME/2)); 284 s.play(grow.setDuration(SLIDE_TIME/2)).after(shrink); 285 s.setInterpolator(mSlowStartWithBrakes); 286 } else { 287 AccelerateInterpolator accel = new AccelerateInterpolator(); 288 DecelerateInterpolator decel = new DecelerateInterpolator(); 289 290 shrink.setDuration(FADE_TIME).setInterpolator(accel); 291 fadeout.setDuration(FADE_TIME).setInterpolator(accel); 292 grow.setDuration(FADE_TIME).setInterpolator(decel); 293 fadein.setDuration(FADE_TIME).setInterpolator(decel); 294 s.play(shrink); 295 s.play(fadeout); 296 s.play(xMove.setDuration(0)).after(FADE_TIME); 297 s.play(yMove.setDuration(0)).after(FADE_TIME); 298 s.play(fadein).after(FADE_TIME); 299 s.play(grow).after(FADE_TIME); 300 } 301 s.start(); 302 } 303 304 long now = System.currentTimeMillis(); 305 long adjust = (now % 60000); 306 delay = delay 307 + (MOVE_DELAY - adjust) // minute aligned 308 - (SLIDE ? 0 : FADE_TIME) // start moving before the fade 309 ; 310 } 311 312 mHandler.removeCallbacks(this); 313 mHandler.postDelayed(this, delay); 314 } 315 } 316 317 /** Setup to find out when the quarter-hour changes (e.g. Kathmandu is GMT+5:45) **/ getAlarmOnQuarterHour()318 private static long getAlarmOnQuarterHour() { 319 Calendar nextQuarter = Calendar.getInstance(); 320 // Set 1 second to ensure quarter-hour threshold passed. 321 nextQuarter.set(Calendar.SECOND, 1); 322 int minute = nextQuarter.get(Calendar.MINUTE); 323 nextQuarter.add(Calendar.MINUTE, 15 - (minute % 15)); 324 long alarmOnQuarterHour = nextQuarter.getTimeInMillis(); 325 if (0 >= (alarmOnQuarterHour - System.currentTimeMillis()) 326 || (alarmOnQuarterHour - System.currentTimeMillis()) > 901000) { 327 Log.wtf("quarterly alarm calculation error"); 328 } 329 return alarmOnQuarterHour; 330 } 331 332 /** Setup alarm refresh when the quarter-hour changes **/ startAlarmOnQuarterHour(Context context)333 public static PendingIntent startAlarmOnQuarterHour(Context context) { 334 if (context != null) { 335 PendingIntent quarterlyIntent = PendingIntent.getBroadcast( 336 context, 0, new Intent(Utils.ACTION_ON_QUARTER_HOUR), 0); 337 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setRepeating( 338 AlarmManager.RTC, getAlarmOnQuarterHour(), 339 AlarmManager.INTERVAL_FIFTEEN_MINUTES, quarterlyIntent); 340 return quarterlyIntent; 341 } else { 342 return null; 343 } 344 } 345 cancelAlarmOnQuarterHour(Context context, PendingIntent quarterlyIntent)346 public static void cancelAlarmOnQuarterHour(Context context, PendingIntent quarterlyIntent) { 347 if (quarterlyIntent != null && context != null) { 348 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel( 349 quarterlyIntent); 350 } 351 } 352 refreshAlarmOnQuarterHour( Context context, PendingIntent quarterlyIntent)353 public static PendingIntent refreshAlarmOnQuarterHour( 354 Context context, PendingIntent quarterlyIntent) { 355 cancelAlarmOnQuarterHour(context, quarterlyIntent); 356 return startAlarmOnQuarterHour(context); 357 } 358 359 /** 360 * For screensavers to set whether the digital or analog clock should be displayed. 361 * Returns the view to be displayed. 362 */ setClockStyle(Context context, View digitalClock, View analogClock, String clockStyleKey)363 public static View setClockStyle(Context context, View digitalClock, View analogClock, 364 String clockStyleKey) { 365 SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); 366 String defaultClockStyle = context.getResources().getString(R.string.default_clock_style); 367 String style = sharedPref.getString(clockStyleKey, defaultClockStyle); 368 View returnView; 369 if (style.equals(CLOCK_TYPE_ANALOG)) { 370 digitalClock.setVisibility(View.GONE); 371 analogClock.setVisibility(View.VISIBLE); 372 returnView = analogClock; 373 } else { 374 digitalClock.setVisibility(View.VISIBLE); 375 analogClock.setVisibility(View.GONE); 376 returnView = digitalClock; 377 } 378 379 return returnView; 380 } 381 382 /** 383 * For screensavers to dim the lights if necessary. 384 */ dimClockView(boolean dim, View clockView)385 public static void dimClockView(boolean dim, View clockView) { 386 Paint paint = new Paint(); 387 paint.setColor(Color.WHITE); 388 paint.setColorFilter(new PorterDuffColorFilter( 389 (dim ? 0x60FFFFFF : 0xC0FFFFFF), 390 PorterDuff.Mode.MULTIPLY)); 391 clockView.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 392 } 393 394 /** Clock views can call this to refresh their alarm to the next upcoming value. **/ refreshAlarm(Context context, View clock)395 public static void refreshAlarm(Context context, View clock) { 396 String nextAlarm = Settings.System.getString(context.getContentResolver(), 397 Settings.System.NEXT_ALARM_FORMATTED); 398 TextView nextAlarmView; 399 nextAlarmView = (TextView) clock.findViewById(R.id.nextAlarm); 400 if (!TextUtils.isEmpty(nextAlarm) && nextAlarmView != null) { 401 nextAlarmView.setText( 402 context.getString(R.string.control_set_alarm_with_existing, nextAlarm)); 403 nextAlarmView.setContentDescription(context.getResources().getString( 404 R.string.next_alarm_description, nextAlarm)); 405 nextAlarmView.setVisibility(View.VISIBLE); 406 } else { 407 nextAlarmView.setVisibility(View.GONE); 408 } 409 } 410 411 /** Clock views can call this to refresh their date. **/ updateDate( String dateFormat, String dateFormatForAccessibility, View clock)412 public static void updateDate( 413 String dateFormat, String dateFormatForAccessibility, View clock) { 414 Calendar cal = Calendar.getInstance(); 415 cal.setTimeInMillis(System.currentTimeMillis()); 416 417 CharSequence newDate = DateFormat.format(dateFormat, cal); 418 TextView dateDisplay; 419 dateDisplay = (TextView) clock.findViewById(R.id.date); 420 if (dateDisplay != null) { 421 dateDisplay.setVisibility(View.VISIBLE); 422 dateDisplay.setText(newDate); 423 dateDisplay.setContentDescription(DateFormat.format(dateFormatForAccessibility, cal)); 424 } 425 } 426 loadCitiesDataBase(Context c)427 public static CityObj[] loadCitiesDataBase(Context c) { 428 final Collator collator = Collator.getInstance(); 429 Resources r = c.getResources(); 430 // Read strings array of name,timezone, id 431 // make sure the list are the same length 432 String[] cities = r.getStringArray(R.array.cities_names); 433 String[] timezones = r.getStringArray(R.array.cities_tz); 434 String[] ids = r.getStringArray(R.array.cities_id); 435 if (cities.length != timezones.length || ids.length != cities.length) { 436 Log.wtf("City lists sizes are not the same, cannot use the data"); 437 return null; 438 } 439 CityObj[] tempList = new CityObj[cities.length]; 440 for (int i = 0; i < cities.length; i++) { 441 tempList[i] = new CityObj(cities[i], timezones[i], ids[i]); 442 } 443 // Sort alphabetically 444 Arrays.sort(tempList, new Comparator<CityObj> () { 445 @Override 446 public int compare(CityObj c1, CityObj c2) { 447 Comparator<CityObj> mCollator; 448 return collator.compare(c1.mCityName, c2.mCityName); 449 } 450 }); 451 return tempList; 452 } 453 getCityName(CityObj city, CityObj dbCity)454 public static String getCityName(CityObj city, CityObj dbCity) { 455 return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName; 456 } 457 } 458