| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.deskclock; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.TimeInterpolator; |
| import android.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Resources; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffColorFilter; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.preference.PreferenceManager; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.text.format.DateFormat; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.animation.AccelerateInterpolator; |
| import android.view.animation.DecelerateInterpolator; |
| import android.widget.TextView; |
| |
| import com.android.deskclock.stopwatch.Stopwatches; |
| import com.android.deskclock.timer.Timers; |
| import com.android.deskclock.worldclock.CityObj; |
| |
| import java.text.Collator; |
| import java.text.SimpleDateFormat; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Locale; |
| |
| |
| public class Utils { |
| private final static String TAG = Utils.class.getName(); |
| |
| private final static String PARAM_LANGUAGE_CODE = "hl"; |
| |
| /** |
| * Help URL query parameter key for the app version. |
| */ |
| private final static String PARAM_VERSION = "version"; |
| |
| /** |
| * Cached version code to prevent repeated calls to the package manager. |
| */ |
| private static String sCachedVersionCode = null; |
| |
| /** |
| * Intent to be used for checking if a clock's date has changed. Must be every fifteen |
| * minutes because not all time zones are hour-locked. |
| **/ |
| public static final String ACTION_ON_QUARTER_HOUR = "com.android.deskclock.ON_QUARTER_HOUR"; |
| |
| /** Types that may be used for clock displays. **/ |
| public static final String CLOCK_TYPE_DIGITAL = "digital"; |
| public static final String CLOCK_TYPE_ANALOG = "analog"; |
| |
| /** |
| * time format constants |
| */ |
| public final static String HOURS_24 = "kk"; |
| public final static String HOURS = "h"; |
| public final static String MINUTES = ":mm"; |
| |
| |
| public static void prepareHelpMenuItem(Context context, MenuItem helpMenuItem) { |
| String helpUrlString = context.getResources().getString(R.string.desk_clock_help_url); |
| if (TextUtils.isEmpty(helpUrlString)) { |
| // The help url string is empty or null, so set the help menu item to be invisible. |
| helpMenuItem.setVisible(false); |
| return; |
| } |
| // The help url string exists, so first add in some extra query parameters. 87 |
| final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUrlString)); |
| |
| // Then, create an intent that will be fired when the user |
| // selects this help menu item. |
| Intent intent = new Intent(Intent.ACTION_VIEW, fullUri); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); |
| |
| // Set the intent to the help menu item, show the help menu item in the overflow |
| // menu, and make it visible. |
| helpMenuItem.setIntent(intent); |
| helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); |
| helpMenuItem.setVisible(true); |
| } |
| |
| /** |
| * Adds two query parameters into the Uri, namely the language code and the version code |
| * of the app's package as gotten via the context. |
| * @return the uri with added query parameters |
| */ |
| private static Uri uriWithAddedParameters(Context context, Uri baseUri) { |
| Uri.Builder builder = baseUri.buildUpon(); |
| |
| // Add in the preferred language |
| builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString()); |
| |
| // Add in the package version code |
| if (sCachedVersionCode == null) { |
| // There is no cached version code, so try to get it from the package manager. |
| try { |
| // cache the version code |
| PackageInfo info = context.getPackageManager().getPackageInfo( |
| context.getPackageName(), 0); |
| sCachedVersionCode = Integer.toString(info.versionCode); |
| |
| // append the version code to the uri |
| builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); |
| } catch (NameNotFoundException e) { |
| // Cannot find the package name, so don't add in the version parameter |
| // This shouldn't happen. |
| Log.wtf("Invalid package name for context " + e); |
| } |
| } else { |
| builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); |
| } |
| |
| // Build the full uri and return it |
| return builder.build(); |
| } |
| |
| public static long getTimeNow() { |
| return SystemClock.elapsedRealtime(); |
| } |
| |
| /** |
| * Calculate the amount by which the radius of a CircleTimerView should be offset by the any |
| * of the extra painted objects. |
| */ |
| public static float calculateRadiusOffset( |
| float strokeSize, float diamondStrokeSize, float markerStrokeSize) { |
| return Math.max(strokeSize, Math.max(diamondStrokeSize, markerStrokeSize)); |
| } |
| |
| /** The pressed color used throughout the app. If this method is changed, it will not have |
| * any effect on the button press states, and those must be changed separately. |
| **/ |
| public static int getPressedColorId() { |
| return R.color.clock_red; |
| } |
| |
| /** The un-pressed color used throughout the app. If this method is changed, it will not have |
| * any effect on the button press states, and those must be changed separately. |
| **/ |
| public static int getGrayColorId() { |
| return R.color.clock_gray; |
| } |
| |
| /** |
| * Clears the persistent data of stopwatch (start time, state, laps, etc...). |
| */ |
| public static void clearSwSharedPref(SharedPreferences prefs) { |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.remove (Stopwatches.PREF_START_TIME); |
| editor.remove (Stopwatches.PREF_ACCUM_TIME); |
| editor.remove (Stopwatches.PREF_STATE); |
| int lapNum = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET); |
| for (int i = 0; i < lapNum; i++) { |
| String key = Stopwatches.PREF_LAP_TIME + Integer.toString(i); |
| editor.remove(key); |
| } |
| editor.remove(Stopwatches.PREF_LAP_NUM); |
| editor.apply(); |
| } |
| |
| /** |
| * Broadcast a message to show the in-use timers in the notifications |
| */ |
| public static void showInUseNotifications(Context context) { |
| Intent timerIntent = new Intent(); |
| timerIntent.setAction(Timers.NOTIF_IN_USE_SHOW); |
| context.sendBroadcast(timerIntent); |
| } |
| |
| /** Runnable for use with screensaver and dream, to move the clock every minute. |
| * registerViews() must be called prior to posting. |
| */ |
| public static class ScreensaverMoveSaverRunnable implements Runnable { |
| static final long MOVE_DELAY = 60000; // DeskClock.SCREEN_SAVER_MOVE_DELAY; |
| static final long SLIDE_TIME = 10000; |
| static final long FADE_TIME = 3000; |
| |
| static final boolean SLIDE = false; |
| |
| private View mContentView, mSaverView; |
| private final Handler mHandler; |
| |
| private static TimeInterpolator mSlowStartWithBrakes; |
| |
| |
| public ScreensaverMoveSaverRunnable(Handler handler) { |
| mHandler = handler; |
| mSlowStartWithBrakes = new TimeInterpolator() { |
| @Override |
| public float getInterpolation(float x) { |
| return (float)(Math.cos((Math.pow(x,3) + 1) * Math.PI) / 2.0f) + 0.5f; |
| } |
| }; |
| } |
| |
| public void registerViews(View contentView, View saverView) { |
| mContentView = contentView; |
| mSaverView = saverView; |
| } |
| |
| @Override |
| public void run() { |
| long delay = MOVE_DELAY; |
| if (mContentView == null || mSaverView == null) { |
| mHandler.removeCallbacks(this); |
| mHandler.postDelayed(this, delay); |
| return; |
| } |
| |
| final float xrange = mContentView.getWidth() - mSaverView.getWidth(); |
| final float yrange = mContentView.getHeight() - mSaverView.getHeight(); |
| Log.v("xrange: "+xrange+" yrange: "+yrange); |
| |
| if (xrange == 0 && yrange == 0) { |
| delay = 500; // back in a split second |
| } else { |
| final int nextx = (int) (Math.random() * xrange); |
| final int nexty = (int) (Math.random() * yrange); |
| |
| if (mSaverView.getAlpha() == 0f) { |
| // jump right there |
| mSaverView.setX(nextx); |
| mSaverView.setY(nexty); |
| ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f) |
| .setDuration(FADE_TIME) |
| .start(); |
| } else { |
| AnimatorSet s = new AnimatorSet(); |
| Animator xMove = ObjectAnimator.ofFloat(mSaverView, |
| "x", mSaverView.getX(), nextx); |
| Animator yMove = ObjectAnimator.ofFloat(mSaverView, |
| "y", mSaverView.getY(), nexty); |
| |
| Animator xShrink = ObjectAnimator.ofFloat(mSaverView, "scaleX", 1f, 0.85f); |
| Animator xGrow = ObjectAnimator.ofFloat(mSaverView, "scaleX", 0.85f, 1f); |
| |
| Animator yShrink = ObjectAnimator.ofFloat(mSaverView, "scaleY", 1f, 0.85f); |
| Animator yGrow = ObjectAnimator.ofFloat(mSaverView, "scaleY", 0.85f, 1f); |
| AnimatorSet shrink = new AnimatorSet(); shrink.play(xShrink).with(yShrink); |
| AnimatorSet grow = new AnimatorSet(); grow.play(xGrow).with(yGrow); |
| |
| Animator fadeout = ObjectAnimator.ofFloat(mSaverView, "alpha", 1f, 0f); |
| Animator fadein = ObjectAnimator.ofFloat(mSaverView, "alpha", 0f, 1f); |
| |
| |
| if (SLIDE) { |
| s.play(xMove).with(yMove); |
| s.setDuration(SLIDE_TIME); |
| |
| s.play(shrink.setDuration(SLIDE_TIME/2)); |
| s.play(grow.setDuration(SLIDE_TIME/2)).after(shrink); |
| s.setInterpolator(mSlowStartWithBrakes); |
| } else { |
| AccelerateInterpolator accel = new AccelerateInterpolator(); |
| DecelerateInterpolator decel = new DecelerateInterpolator(); |
| |
| shrink.setDuration(FADE_TIME).setInterpolator(accel); |
| fadeout.setDuration(FADE_TIME).setInterpolator(accel); |
| grow.setDuration(FADE_TIME).setInterpolator(decel); |
| fadein.setDuration(FADE_TIME).setInterpolator(decel); |
| s.play(shrink); |
| s.play(fadeout); |
| s.play(xMove.setDuration(0)).after(FADE_TIME); |
| s.play(yMove.setDuration(0)).after(FADE_TIME); |
| s.play(fadein).after(FADE_TIME); |
| s.play(grow).after(FADE_TIME); |
| } |
| s.start(); |
| } |
| |
| long now = System.currentTimeMillis(); |
| long adjust = (now % 60000); |
| delay = delay |
| + (MOVE_DELAY - adjust) // minute aligned |
| - (SLIDE ? 0 : FADE_TIME) // start moving before the fade |
| ; |
| } |
| |
| mHandler.removeCallbacks(this); |
| mHandler.postDelayed(this, delay); |
| } |
| } |
| |
| /** Setup to find out when the quarter-hour changes (e.g. Kathmandu is GMT+5:45) **/ |
| private static long getAlarmOnQuarterHour() { |
| Calendar nextQuarter = Calendar.getInstance(); |
| // Set 1 second to ensure quarter-hour threshold passed. |
| nextQuarter.set(Calendar.SECOND, 1); |
| int minute = nextQuarter.get(Calendar.MINUTE); |
| nextQuarter.add(Calendar.MINUTE, 15 - (minute % 15)); |
| long alarmOnQuarterHour = nextQuarter.getTimeInMillis(); |
| if (0 >= (alarmOnQuarterHour - System.currentTimeMillis()) |
| || (alarmOnQuarterHour - System.currentTimeMillis()) > 901000) { |
| Log.wtf("quarterly alarm calculation error"); |
| } |
| return alarmOnQuarterHour; |
| } |
| |
| /** Setup alarm refresh when the quarter-hour changes **/ |
| public static PendingIntent startAlarmOnQuarterHour(Context context) { |
| if (context != null) { |
| PendingIntent quarterlyIntent = PendingIntent.getBroadcast( |
| context, 0, new Intent(Utils.ACTION_ON_QUARTER_HOUR), 0); |
| ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setRepeating( |
| AlarmManager.RTC, getAlarmOnQuarterHour(), |
| AlarmManager.INTERVAL_FIFTEEN_MINUTES, quarterlyIntent); |
| return quarterlyIntent; |
| } else { |
| return null; |
| } |
| } |
| |
| public static void cancelAlarmOnQuarterHour(Context context, PendingIntent quarterlyIntent) { |
| if (quarterlyIntent != null && context != null) { |
| ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel( |
| quarterlyIntent); |
| } |
| } |
| |
| public static PendingIntent refreshAlarmOnQuarterHour( |
| Context context, PendingIntent quarterlyIntent) { |
| cancelAlarmOnQuarterHour(context, quarterlyIntent); |
| return startAlarmOnQuarterHour(context); |
| } |
| |
| /** |
| * For screensavers to set whether the digital or analog clock should be displayed. |
| * Returns the view to be displayed. |
| */ |
| public static View setClockStyle(Context context, View digitalClock, View analogClock, |
| String clockStyleKey) { |
| SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); |
| String defaultClockStyle = context.getResources().getString(R.string.default_clock_style); |
| String style = sharedPref.getString(clockStyleKey, defaultClockStyle); |
| View returnView; |
| if (style.equals(CLOCK_TYPE_ANALOG)) { |
| digitalClock.setVisibility(View.GONE); |
| analogClock.setVisibility(View.VISIBLE); |
| returnView = analogClock; |
| } else { |
| digitalClock.setVisibility(View.VISIBLE); |
| analogClock.setVisibility(View.GONE); |
| returnView = digitalClock; |
| } |
| |
| return returnView; |
| } |
| |
| /** |
| * For screensavers to dim the lights if necessary. |
| */ |
| public static void dimClockView(boolean dim, View clockView) { |
| Paint paint = new Paint(); |
| paint.setColor(Color.WHITE); |
| paint.setColorFilter(new PorterDuffColorFilter( |
| (dim ? 0x60FFFFFF : 0xC0FFFFFF), |
| PorterDuff.Mode.MULTIPLY)); |
| clockView.setLayerType(View.LAYER_TYPE_HARDWARE, paint); |
| } |
| |
| /** Clock views can call this to refresh their alarm to the next upcoming value. **/ |
| public static void refreshAlarm(Context context, View clock) { |
| String nextAlarm = Settings.System.getString(context.getContentResolver(), |
| Settings.System.NEXT_ALARM_FORMATTED); |
| TextView nextAlarmView; |
| nextAlarmView = (TextView) clock.findViewById(R.id.nextAlarm); |
| if (!TextUtils.isEmpty(nextAlarm) && nextAlarmView != null) { |
| nextAlarmView.setText( |
| context.getString(R.string.control_set_alarm_with_existing, nextAlarm)); |
| nextAlarmView.setContentDescription(context.getResources().getString( |
| R.string.next_alarm_description, nextAlarm)); |
| nextAlarmView.setVisibility(View.VISIBLE); |
| } else { |
| nextAlarmView.setVisibility(View.GONE); |
| } |
| } |
| |
| /** Clock views can call this to refresh their date. **/ |
| public static void updateDate( |
| String dateFormat, String dateFormatForAccessibility, View clock) { |
| |
| Date now = new Date(); |
| TextView dateDisplay; |
| dateDisplay = (TextView) clock.findViewById(R.id.date); |
| if (dateDisplay != null) { |
| final Locale l = Locale.getDefault(); |
| String fmt = DateFormat.getBestDateTimePattern(l, dateFormat); |
| SimpleDateFormat sdf = new SimpleDateFormat(fmt, l); |
| dateDisplay.setText(sdf.format(now)); |
| dateDisplay.setVisibility(View.VISIBLE); |
| fmt = DateFormat.getBestDateTimePattern(l, dateFormatForAccessibility); |
| sdf = new SimpleDateFormat(fmt, l); |
| dateDisplay.setContentDescription(sdf.format(now)); |
| } |
| } |
| |
| public static CityObj[] loadCitiesDataBase(Context c) { |
| final Collator collator = Collator.getInstance(); |
| Resources r = c.getResources(); |
| // Read strings array of name,timezone, id |
| // make sure the list are the same length |
| String[] cities = r.getStringArray(R.array.cities_names); |
| String[] timezones = r.getStringArray(R.array.cities_tz); |
| String[] ids = r.getStringArray(R.array.cities_id); |
| if (cities.length != timezones.length || ids.length != cities.length) { |
| Log.wtf("City lists sizes are not the same, cannot use the data"); |
| return null; |
| } |
| CityObj[] tempList = new CityObj[cities.length]; |
| for (int i = 0; i < cities.length; i++) { |
| tempList[i] = new CityObj(cities[i], timezones[i], ids[i]); |
| } |
| // Sort alphabetically |
| Arrays.sort(tempList, new Comparator<CityObj> () { |
| @Override |
| public int compare(CityObj c1, CityObj c2) { |
| Comparator<CityObj> mCollator; |
| return collator.compare(c1.mCityName, c2.mCityName); |
| } |
| }); |
| return tempList; |
| } |
| |
| public static String getCityName(CityObj city, CityObj dbCity) { |
| return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName; |
| } |
| } |