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