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