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