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