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