• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
prepareHelpMenuItem(Context context, MenuItem helpMenuItem)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      */
uriWithAddedParameters(Context context, Uri baseUri)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 
getTimeNow()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      */
calculateRadiusOffset( float strokeSize, float diamondStrokeSize, float markerStrokeSize)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     **/
getPressedColorId()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     **/
getGrayColorId()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      */
clearSwSharedPref(SharedPreferences prefs)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      */
showInUseNotifications(Context context)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 
ScreensaverMoveSaverRunnable(Handler handler)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 
registerViews(View contentView, View saverView)231         public void registerViews(View contentView, View saverView) {
232             mContentView = contentView;
233             mSaverView = saverView;
234         }
235 
236         @Override
run()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) **/
getAlarmOnQuarterHour()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 **/
startAlarmOnQuarterHour(Context context)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 
cancelAlarmOnQuarterHour(Context context, PendingIntent quarterlyIntent)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 
refreshAlarmOnQuarterHour( Context context, PendingIntent quarterlyIntent)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      */
setClockStyle(Context context, View digitalClock, View analogClock, String clockStyleKey)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      */
dimClockView(boolean dim, View clockView)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. **/
refreshAlarm(Context context, View clock)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. **/
updateDate( String dateFormat, String dateFormatForAccessibility, View clock)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 
loadCitiesDataBase(Context c)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 
getCityName(CityObj city, CityObj dbCity)459     public static String getCityName(CityObj city, CityObj dbCity) {
460         return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName;
461     }
462 }
463