/* * 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.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.util.Arrays; import java.util.Calendar; import java.util.Comparator; 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) **/ public 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; } /** * 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) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); CharSequence newDate = DateFormat.format(dateFormat, cal); TextView dateDisplay; dateDisplay = (TextView) clock.findViewById(R.id.date); if (dateDisplay != null) { dateDisplay.setVisibility(View.VISIBLE); dateDisplay.setText(newDate); dateDisplay.setContentDescription(DateFormat.format(dateFormatForAccessibility, cal)); } } 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 () { @Override public int compare(CityObj c1, CityObj c2) { Comparator 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; } }