1 /*
2  * Copyright (C) 2009 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.ArgbEvaluator;
20 import android.animation.ObjectAnimator;
21 import android.app.ActionBar;
22 import android.app.ActionBar.Tab;
23 import android.app.Activity;
24 import android.app.Fragment;
25 import android.app.FragmentManager;
26 import android.app.FragmentTransaction;
27 import android.content.ActivityNotFoundException;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.SharedPreferences;
31 import android.content.res.Configuration;
32 import android.graphics.Outline;
33 import android.media.AudioManager;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.preference.PreferenceManager;
37 import android.support.v13.app.FragmentPagerAdapter;
38 import android.support.v4.view.ViewPager;
39 import android.text.TextUtils;
40 import android.text.format.DateUtils;
41 import android.util.Log;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.View.OnClickListener;
47 import android.view.View.OnTouchListener;
48 import android.view.ViewOutlineProvider;
49 import android.widget.ImageButton;
50 import android.widget.TextView;
51 
52 import com.android.deskclock.alarms.AlarmStateManager;
53 import com.android.deskclock.provider.Alarm;
54 import com.android.deskclock.stopwatch.StopwatchFragment;
55 import com.android.deskclock.stopwatch.StopwatchService;
56 import com.android.deskclock.stopwatch.Stopwatches;
57 import com.android.deskclock.timer.TimerFragment;
58 import com.android.deskclock.timer.TimerObj;
59 import com.android.deskclock.timer.Timers;
60 
61 import java.util.ArrayList;
62 import java.util.HashSet;
63 import java.util.Locale;
64 import java.util.TimeZone;
65 
66 /**
67  * DeskClock clock view for desk docks.
68  */
69 public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler,
70         LabelDialogFragment.AlarmLabelDialogHandler {
71     private static final boolean DEBUG = false;
72     private static final String LOG_TAG = "DeskClock";
73     // Alarm action for midnight (so we can update the date display).
74     private static final String KEY_SELECTED_TAB = "selected_tab";
75     private static final String KEY_LAST_HOUR_COLOR = "last_hour_color";
76     // Check whether to change background every minute
77     private static final long BACKGROUND_COLOR_CHECK_DELAY_MILLIS = DateUtils.MINUTE_IN_MILLIS;
78     private static final int BACKGROUND_COLOR_INITIAL_ANIMATION_DURATION_MILLIS = 3000;
79     private static final int UNKNOWN_COLOR_ID = 0;
80 
81     private boolean mIsFirstLaunch = true;
82     private ActionBar mActionBar;
83     private Tab mAlarmTab;
84     private Tab mClockTab;
85     private Tab mTimerTab;
86     private Tab mStopwatchTab;
87     private Menu mMenu;
88     private ViewPager mViewPager;
89     private TabsAdapter mTabsAdapter;
90     private Handler mHander;
91     private ImageButton mFab;
92     private ImageButton mLeftButton;
93     private ImageButton mRightButton;
94     private int mSelectedTab;
95     private int mLastHourColor = UNKNOWN_COLOR_ID;
96     private final Runnable mBackgroundColorChanger = new Runnable() {
97         @Override
98         public void run() {
99             setBackgroundColor();
100             mHander.postDelayed(this, BACKGROUND_COLOR_CHECK_DELAY_MILLIS);
101         }
102     };
103 
104     public static final int ALARM_TAB_INDEX = 0;
105     public static final int CLOCK_TAB_INDEX = 1;
106     public static final int TIMER_TAB_INDEX = 2;
107     public static final int STOPWATCH_TAB_INDEX = 3;
108     // Tabs indices are switched for right-to-left since there is no
109     // native support for RTL in the ViewPager.
110     public static final int RTL_ALARM_TAB_INDEX = 3;
111     public static final int RTL_CLOCK_TAB_INDEX = 2;
112     public static final int RTL_TIMER_TAB_INDEX = 1;
113     public static final int RTL_STOPWATCH_TAB_INDEX = 0;
114     public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab";
115 
116     // TODO(rachelzhang): adding a broadcast receiver to adjust color when the timezone/time
117     // changes in the background.
118 
119     @Override
onStart()120     protected void onStart() {
121         super.onStart();
122         if (mHander == null) {
123             mHander = new Handler();
124         }
125         mHander.postDelayed(mBackgroundColorChanger, BACKGROUND_COLOR_CHECK_DELAY_MILLIS);
126     }
127 
128     @Override
onStop()129     protected void onStop() {
130         super.onStop();
131         mHander.removeCallbacks(mBackgroundColorChanger);
132     }
133 
134     @Override
onNewIntent(Intent newIntent)135     public void onNewIntent(Intent newIntent) {
136         super.onNewIntent(newIntent);
137         if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent);
138 
139         // update our intent so that we can consult it to determine whether or
140         // not the most recent launch was via a dock event
141         setIntent(newIntent);
142 
143         // Timer receiver may ask to go to the timers fragment if a timer expired.
144         int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
145         if (tab != -1) {
146             if (mActionBar != null) {
147                 mActionBar.setSelectedNavigationItem(tab);
148             }
149         }
150     }
151 
152     private static final ViewOutlineProvider OVAL_OUTLINE_PROVIDER = new ViewOutlineProvider() {
153         @Override
154         public void getOutline(View view, Outline outline) {
155             outline.setOval(0, 0, view.getWidth(), view.getHeight());
156         }
157     };
158 
initViews()159     private void initViews() {
160         setContentView(R.layout.desk_clock);
161         mFab = (ImageButton) findViewById(R.id.fab);
162         mFab.setOutlineProvider(OVAL_OUTLINE_PROVIDER);
163         mLeftButton = (ImageButton) findViewById(R.id.left_button);
164         mRightButton = (ImageButton) findViewById(R.id.right_button);
165         if (mTabsAdapter == null) {
166             mViewPager = (ViewPager) findViewById(R.id.desk_clock_pager);
167             // Keep all four tabs to minimize jank.
168             mViewPager.setOffscreenPageLimit(3);
169             mTabsAdapter = new TabsAdapter(this, mViewPager);
170             createTabs(mSelectedTab);
171         }
172 
173         mFab.setOnClickListener(new OnClickListener() {
174             @Override
175             public void onClick(View view) {
176                 getSelectedFragment().onFabClick(view);
177             }
178         });
179         mLeftButton.setOnClickListener(new OnClickListener() {
180             @Override
181             public void onClick(View view) {
182                 getSelectedFragment().onLeftButtonClick(view);
183             }
184         });
185         mRightButton.setOnClickListener(new OnClickListener() {
186             @Override
187             public void onClick(View view) {
188                 getSelectedFragment().onRightButtonClick(view);
189             }
190         });
191 
192         mActionBar.setSelectedNavigationItem(mSelectedTab);
193     }
194 
getSelectedFragment()195     private DeskClockFragment getSelectedFragment() {
196         return (DeskClockFragment) mTabsAdapter.getItem(getRtlPosition(mSelectedTab));
197     }
198 
createTabs(int selectedIndex)199     private void createTabs(int selectedIndex) {
200         mActionBar = getActionBar();
201 
202         if (mActionBar != null) {
203             mActionBar.setDisplayOptions(0);
204             mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
205 
206             mAlarmTab = mActionBar.newTab();
207             mAlarmTab.setIcon(R.drawable.ic_alarm_animation);
208             mAlarmTab.setContentDescription(R.string.menu_alarm);
209             mTabsAdapter.addTab(mAlarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX);
210 
211             mClockTab = mActionBar.newTab();
212             mClockTab.setIcon(R.drawable.ic_clock_animation);
213             mClockTab.setContentDescription(R.string.menu_clock);
214             mTabsAdapter.addTab(mClockTab, ClockFragment.class, CLOCK_TAB_INDEX);
215 
216             mTimerTab = mActionBar.newTab();
217             mTimerTab.setIcon(R.drawable.ic_timer_animation);
218             mTimerTab.setContentDescription(R.string.menu_timer);
219             mTabsAdapter.addTab(mTimerTab, TimerFragment.class, TIMER_TAB_INDEX);
220 
221             mStopwatchTab = mActionBar.newTab();
222             mStopwatchTab.setIcon(R.drawable.ic_stopwatch_animation);
223             mStopwatchTab.setContentDescription(R.string.menu_stopwatch);
224             mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class, STOPWATCH_TAB_INDEX);
225 
226             mActionBar.setSelectedNavigationItem(selectedIndex);
227             mTabsAdapter.notifySelectedPage(selectedIndex);
228         }
229     }
230 
231     @Override
onCreate(Bundle icicle)232     protected void onCreate(Bundle icicle) {
233         super.onCreate(icicle);
234         setVolumeControlStream(AudioManager.STREAM_ALARM);
235 
236         mIsFirstLaunch = (icicle == null);
237         getWindow().setBackgroundDrawable(null);
238 
239         mIsFirstLaunch = true;
240         mSelectedTab = CLOCK_TAB_INDEX;
241         if (icicle != null) {
242             mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX);
243             mLastHourColor = icicle.getInt(KEY_LAST_HOUR_COLOR, UNKNOWN_COLOR_ID);
244             if (mLastHourColor != UNKNOWN_COLOR_ID) {
245                 getWindow().getDecorView().setBackgroundColor(mLastHourColor);
246             }
247         }
248 
249         // Timer receiver may ask the app to go to the timer fragment if a timer expired
250         Intent i = getIntent();
251         if (i != null) {
252             int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
253             if (tab != -1) {
254                 mSelectedTab = tab;
255             }
256         }
257         initViews();
258         setHomeTimeZone();
259 
260         // We need to update the system next alarm time on app startup because the
261         // user might have clear our data.
262         AlarmStateManager.updateNextAlarm(this);
263         ExtensionsFactory.init(getAssets());
264     }
265 
266     @Override
onResume()267     protected void onResume() {
268         super.onResume();
269 
270         setBackgroundColor();
271 
272         // We only want to show notifications for stopwatch/timer when the app is closed so
273         // that we don't have to worry about keeping the notifications in perfect sync with
274         // the app.
275         Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class);
276         stopwatchIntent.setAction(Stopwatches.KILL_NOTIF);
277         startService(stopwatchIntent);
278 
279         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
280         SharedPreferences.Editor editor = prefs.edit();
281         editor.putBoolean(Timers.NOTIF_APP_OPEN, true);
282         editor.apply();
283         Intent timerIntent = new Intent();
284         timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL);
285         sendBroadcast(timerIntent);
286     }
287 
288     @Override
onPause()289     public void onPause() {
290         Intent intent = new Intent(getApplicationContext(), StopwatchService.class);
291         intent.setAction(Stopwatches.SHOW_NOTIF);
292         startService(intent);
293 
294         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
295         SharedPreferences.Editor editor = prefs.edit();
296         editor.putBoolean(Timers.NOTIF_APP_OPEN, false);
297         editor.apply();
298         Utils.showInUseNotifications(this);
299 
300         super.onPause();
301     }
302 
303     @Override
onSaveInstanceState(Bundle outState)304     protected void onSaveInstanceState(Bundle outState) {
305         super.onSaveInstanceState(outState);
306         outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex());
307         outState.putInt(KEY_LAST_HOUR_COLOR, mLastHourColor);
308     }
309 
310     @Override
onCreateOptionsMenu(Menu menu)311     public boolean onCreateOptionsMenu(Menu menu) {
312         // We only want to show it as a menu in landscape, and only for clock/alarm fragment.
313         mMenu = menu;
314         if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
315             if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX ||
316                     mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) {
317                 // Clear the menu so that it doesn't get duplicate items in case onCreateOptionsMenu
318                 // was called multiple times.
319                 menu.clear();
320                 getMenuInflater().inflate(R.menu.desk_clock_menu, menu);
321             }
322             // Always return true for landscape, regardless of whether we've inflated the menu, so
323             // that when we switch tabs this method will get called and we can inflate the menu.
324             return true;
325         }
326         return false;
327     }
328 
329     @Override
onPrepareOptionsMenu(Menu menu)330     public boolean onPrepareOptionsMenu(Menu menu) {
331         updateMenu(menu);
332         return true;
333     }
334 
updateMenu(Menu menu)335     private void updateMenu(Menu menu) {
336         // Hide "help" if we don't have a URI for it.
337         MenuItem help = menu.findItem(R.id.menu_item_help);
338         if (help != null) {
339             Utils.prepareHelpMenuItem(this, help);
340         }
341 
342         // Hide "lights out" for timer.
343         MenuItem nightMode = menu.findItem(R.id.menu_item_night_mode);
344         if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX) {
345             nightMode.setVisible(false);
346         } else if (mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) {
347             nightMode.setVisible(true);
348         }
349     }
350 
351     @Override
onOptionsItemSelected(MenuItem item)352     public boolean onOptionsItemSelected(MenuItem item) {
353         if (processMenuClick(item)) {
354             return true;
355         }
356 
357         return super.onOptionsItemSelected(item);
358     }
359 
processMenuClick(MenuItem item)360     private boolean processMenuClick(MenuItem item) {
361         switch (item.getItemId()) {
362             case R.id.menu_item_settings:
363                 startActivity(new Intent(DeskClock.this, SettingsActivity.class));
364                 return true;
365             case R.id.menu_item_help:
366                 Intent i = item.getIntent();
367                 if (i != null) {
368                     try {
369                         startActivity(i);
370                     } catch (ActivityNotFoundException e) {
371                         // No activity found to match the intent - ignore
372                     }
373                 }
374                 return true;
375             case R.id.menu_item_night_mode:
376                 startActivity(new Intent(DeskClock.this, ScreensaverActivity.class));
377             default:
378                 break;
379         }
380         return true;
381     }
382 
383     /**
384      * Insert the local time zone as the Home Time Zone if one is not set
385      */
setHomeTimeZone()386     private void setHomeTimeZone() {
387         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
388         String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, "");
389         if (!homeTimeZone.isEmpty()) {
390             return;
391         }
392         homeTimeZone = TimeZone.getDefault().getID();
393         SharedPreferences.Editor editor = prefs.edit();
394         editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone);
395         editor.apply();
396         Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone);
397     }
398 
registerPageChangedListener(DeskClockFragment frag)399     public void registerPageChangedListener(DeskClockFragment frag) {
400         if (mTabsAdapter != null) {
401             mTabsAdapter.registerPageChangedListener(frag);
402         }
403     }
404 
unregisterPageChangedListener(DeskClockFragment frag)405     public void unregisterPageChangedListener(DeskClockFragment frag) {
406         if (mTabsAdapter != null) {
407             mTabsAdapter.unregisterPageChangedListener(frag);
408         }
409     }
410 
setBackgroundColor()411     private void setBackgroundColor() {
412         final int duration;
413         if (mLastHourColor == UNKNOWN_COLOR_ID) {
414             mLastHourColor = getResources().getColor(R.color.default_background);
415             duration = BACKGROUND_COLOR_INITIAL_ANIMATION_DURATION_MILLIS;
416         } else {
417             duration = getResources().getInteger(android.R.integer.config_longAnimTime);
418         }
419         final int currHourColor = Utils.getCurrentHourColor();
420         if (mLastHourColor != currHourColor) {
421             final ObjectAnimator animator = ObjectAnimator.ofInt(getWindow().getDecorView(),
422                     "backgroundColor", mLastHourColor, currHourColor);
423             animator.setDuration(duration);
424             animator.setEvaluator(new ArgbEvaluator());
425             animator.start();
426             mLastHourColor = currHourColor;
427         }
428     }
429 
430     /**
431      * Adapter for wrapping together the ActionBar's tab with the ViewPager
432      */
433     private class TabsAdapter extends FragmentPagerAdapter
434             implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
435 
436         private static final String KEY_TAB_POSITION = "tab_position";
437 
438         final class TabInfo {
439             private final Class<?> clss;
440             private final Bundle args;
441 
TabInfo(Class<?> _class, int position)442             TabInfo(Class<?> _class, int position) {
443                 clss = _class;
444                 args = new Bundle();
445                 args.putInt(KEY_TAB_POSITION, position);
446             }
447 
getPosition()448             public int getPosition() {
449                 return args.getInt(KEY_TAB_POSITION, 0);
450             }
451         }
452 
453         private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
454         ActionBar mMainActionBar;
455         Context mContext;
456         ViewPager mPager;
457         // Used for doing callbacks to fragments.
458         HashSet<String> mFragmentTags = new HashSet<String>();
459 
TabsAdapter(Activity activity, ViewPager pager)460         public TabsAdapter(Activity activity, ViewPager pager) {
461             super(activity.getFragmentManager());
462             mContext = activity;
463             mMainActionBar = activity.getActionBar();
464             mPager = pager;
465             mPager.setAdapter(this);
466             mPager.setOnPageChangeListener(this);
467         }
468 
469         @Override
getItem(int position)470         public Fragment getItem(int position) {
471             // Because this public method is called outside many times,
472             // check if it exits first before creating a new one.
473             final String name = makeFragmentName(R.id.desk_clock_pager, position);
474             Fragment fragment = getFragmentManager().findFragmentByTag(name);
475             if (fragment == null) {
476                 TabInfo info = mTabs.get(getRtlPosition(position));
477                 fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
478                 if (fragment instanceof TimerFragment) {
479                     ((TimerFragment) fragment).setFabAppearance();
480                     ((TimerFragment) fragment).setLeftRightButtonAppearance();
481                 }
482             }
483             return fragment;
484         }
485 
486         /**
487          * Copied from:
488          * android/frameworks/support/v13/java/android/support/v13/app/FragmentPagerAdapter.java#94
489          * Create unique name for the fragment so fragment manager knows it exist.
490          */
makeFragmentName(int viewId, int index)491         private String makeFragmentName(int viewId, int index) {
492             return "android:switcher:" + viewId + ":" + index;
493         }
494 
495         @Override
getCount()496         public int getCount() {
497             return mTabs.size();
498         }
499 
addTab(ActionBar.Tab tab, Class<?> clss, int position)500         public void addTab(ActionBar.Tab tab, Class<?> clss, int position) {
501             TabInfo info = new TabInfo(clss, position);
502             tab.setTag(info);
503             tab.setTabListener(this);
504             mTabs.add(info);
505             mMainActionBar.addTab(tab);
506             notifyDataSetChanged();
507         }
508 
509         @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)510         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
511             // Do nothing
512         }
513 
514         @Override
onPageSelected(int position)515         public void onPageSelected(int position) {
516             // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is.
517             mMainActionBar.setSelectedNavigationItem(getRtlPosition(position));
518             notifyPageChanged(position);
519 
520             // Only show the overflow menu for alarm and world clock.
521             if (mMenu != null) {
522                 // Make sure the menu's been initialized.
523                 if (position == ALARM_TAB_INDEX || position == CLOCK_TAB_INDEX) {
524                     mMenu.setGroupVisible(R.id.menu_items, true);
525                     onCreateOptionsMenu(mMenu);
526                 } else {
527                     mMenu.setGroupVisible(R.id.menu_items, false);
528                 }
529             }
530         }
531 
532         @Override
onPageScrollStateChanged(int state)533         public void onPageScrollStateChanged(int state) {
534             // Do nothing
535         }
536 
537         @Override
onTabReselected(Tab tab, FragmentTransaction arg1)538         public void onTabReselected(Tab tab, FragmentTransaction arg1) {
539             // Do nothing
540         }
541 
542         @Override
onTabSelected(Tab tab, FragmentTransaction ft)543         public void onTabSelected(Tab tab, FragmentTransaction ft) {
544             final TabInfo info = (TabInfo) tab.getTag();
545             final int position = info.getPosition();
546             final int rtlSafePosition = getRtlPosition(position);
547             mSelectedTab = position;
548 
549             if (mIsFirstLaunch && isClockTab(rtlSafePosition)) {
550                 mLeftButton.setVisibility(View.INVISIBLE);
551                 mRightButton.setVisibility(View.INVISIBLE);
552                 mFab.setVisibility(View.VISIBLE);
553                 mFab.setImageResource(R.drawable.ic_globe);
554                 mFab.setContentDescription(getString(R.string.button_cities));
555                 mIsFirstLaunch = false;
556             } else {
557                 DeskClockFragment f = (DeskClockFragment) getItem(rtlSafePosition);
558                 f.setFabAppearance();
559                 f.setLeftRightButtonAppearance();
560             }
561             mPager.setCurrentItem(rtlSafePosition);
562         }
563 
564         @Override
onTabUnselected(Tab arg0, FragmentTransaction arg1)565         public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
566             // Do nothing
567         }
568 
isClockTab(int rtlSafePosition)569         private boolean isClockTab(int rtlSafePosition) {
570             final int clockTabIndex = isRtl() ? RTL_CLOCK_TAB_INDEX : CLOCK_TAB_INDEX;
571             return rtlSafePosition == clockTabIndex;
572         }
573 
notifySelectedPage(int page)574         public void notifySelectedPage(int page) {
575             notifyPageChanged(page);
576         }
577 
notifyPageChanged(int newPage)578         private void notifyPageChanged(int newPage) {
579             for (String tag : mFragmentTags) {
580                 final FragmentManager fm = getFragmentManager();
581                 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag);
582                 if (f != null) {
583                     f.onPageChanged(newPage);
584                 }
585             }
586         }
587 
registerPageChangedListener(DeskClockFragment frag)588         public void registerPageChangedListener(DeskClockFragment frag) {
589             String tag = frag.getTag();
590             if (mFragmentTags.contains(tag)) {
591                 Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag);
592             } else {
593                 mFragmentTags.add(frag.getTag());
594             }
595             // Since registering a listener by the fragment is done sometimes after the page
596             // was already changed, make sure the fragment gets the current page
597             frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex());
598         }
599 
unregisterPageChangedListener(DeskClockFragment frag)600         public void unregisterPageChangedListener(DeskClockFragment frag) {
601             mFragmentTags.remove(frag.getTag());
602         }
603 
604     }
605 
606     public static abstract class OnTapListener implements OnTouchListener {
607         private float mLastTouchX;
608         private float mLastTouchY;
609         private long mLastTouchTime;
610         private final TextView mMakePressedTextView;
611         private final int mPressedColor, mGrayColor;
612         private final float MAX_MOVEMENT_ALLOWED = 20;
613         private final long MAX_TIME_ALLOWED = 500;
614 
OnTapListener(Activity activity, TextView makePressedView)615         public OnTapListener(Activity activity, TextView makePressedView) {
616             mMakePressedTextView = makePressedView;
617             mPressedColor = activity.getResources().getColor(Utils.getPressedColorId());
618             mGrayColor = activity.getResources().getColor(Utils.getGrayColorId());
619         }
620 
621         @Override
onTouch(View v, MotionEvent e)622         public boolean onTouch(View v, MotionEvent e) {
623             switch (e.getAction()) {
624                 case (MotionEvent.ACTION_DOWN):
625                     mLastTouchTime = Utils.getTimeNow();
626                     mLastTouchX = e.getX();
627                     mLastTouchY = e.getY();
628                     if (mMakePressedTextView != null) {
629                         mMakePressedTextView.setTextColor(mPressedColor);
630                     }
631                     break;
632                 case (MotionEvent.ACTION_UP):
633                     float xDiff = Math.abs(e.getX() - mLastTouchX);
634                     float yDiff = Math.abs(e.getY() - mLastTouchY);
635                     long timeDiff = (Utils.getTimeNow() - mLastTouchTime);
636                     if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED
637                             && timeDiff < MAX_TIME_ALLOWED) {
638                         if (mMakePressedTextView != null) {
639                             v = mMakePressedTextView;
640                         }
641                         processClick(v);
642                         resetValues();
643                         return true;
644                     }
645                     resetValues();
646                     break;
647                 case (MotionEvent.ACTION_MOVE):
648                     xDiff = Math.abs(e.getX() - mLastTouchX);
649                     yDiff = Math.abs(e.getY() - mLastTouchY);
650                     if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) {
651                         resetValues();
652                     }
653                     break;
654                 default:
655                     resetValues();
656             }
657             return false;
658         }
659 
resetValues()660         private void resetValues() {
661             mLastTouchX = -1 * MAX_MOVEMENT_ALLOWED + 1;
662             mLastTouchY = -1 * MAX_MOVEMENT_ALLOWED + 1;
663             mLastTouchTime = -1 * MAX_TIME_ALLOWED + 1;
664             if (mMakePressedTextView != null) {
665                 mMakePressedTextView.setTextColor(mGrayColor);
666             }
667         }
668 
processClick(View v)669         protected abstract void processClick(View v);
670     }
671 
672     /**
673      * Called by the LabelDialogFormat class after the dialog is finished. *
674      */
675     @Override
onDialogLabelSet(TimerObj timer, String label, String tag)676     public void onDialogLabelSet(TimerObj timer, String label, String tag) {
677         Fragment frag = getFragmentManager().findFragmentByTag(tag);
678         if (frag instanceof TimerFragment) {
679             ((TimerFragment) frag).setLabel(timer, label);
680         }
681     }
682 
683     /**
684      * Called by the LabelDialogFormat class after the dialog is finished. *
685      */
686     @Override
onDialogLabelSet(Alarm alarm, String label, String tag)687     public void onDialogLabelSet(Alarm alarm, String label, String tag) {
688         Fragment frag = getFragmentManager().findFragmentByTag(tag);
689         if (frag instanceof AlarmClockFragment) {
690             ((AlarmClockFragment) frag).setLabel(alarm, label);
691         }
692     }
693 
getSelectedTab()694     public int getSelectedTab() {
695         return mSelectedTab;
696     }
697 
isRtl()698     private boolean isRtl() {
699         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
700                 View.LAYOUT_DIRECTION_RTL;
701     }
702 
getRtlPosition(int position)703     private int getRtlPosition(int position) {
704         if (isRtl()) {
705             switch (position) {
706                 case TIMER_TAB_INDEX:
707                     return RTL_TIMER_TAB_INDEX;
708                 case CLOCK_TAB_INDEX:
709                     return RTL_CLOCK_TAB_INDEX;
710                 case STOPWATCH_TAB_INDEX:
711                     return RTL_STOPWATCH_TAB_INDEX;
712                 case ALARM_TAB_INDEX:
713                     return RTL_ALARM_TAB_INDEX;
714                 default:
715                     break;
716             }
717         }
718         return position;
719     }
720 
getFab()721     public ImageButton getFab() {
722         return mFab;
723     }
724 
getLeftButton()725     public ImageButton getLeftButton() {
726         return mLeftButton;
727     }
728 
getRightButton()729     public ImageButton getRightButton() {
730         return mRightButton;
731     }
732 }
733