Home | History | Annotate | Download | only in deskclock
      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.app.ActionBar;
     20 import android.app.ActionBar.Tab;
     21 import android.app.Activity;
     22 import android.app.Fragment;
     23 import android.app.FragmentManager;
     24 import android.app.FragmentTransaction;
     25 import android.content.ActivityNotFoundException;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.SharedPreferences;
     29 import android.content.res.Configuration;
     30 import android.os.Bundle;
     31 import android.preference.PreferenceManager;
     32 import android.support.v13.app.FragmentPagerAdapter;
     33 import android.support.v4.view.ViewPager;
     34 import android.text.TextUtils;
     35 import android.util.Log;
     36 import android.view.Menu;
     37 import android.view.MenuItem;
     38 import android.view.MotionEvent;
     39 import android.view.View;
     40 import android.view.View.OnTouchListener;
     41 import android.widget.PopupMenu;
     42 import android.widget.TextView;
     43 
     44 import com.android.deskclock.alarms.AlarmStateManager;
     45 import com.android.deskclock.provider.Alarm;
     46 import com.android.deskclock.stopwatch.StopwatchFragment;
     47 import com.android.deskclock.stopwatch.StopwatchService;
     48 import com.android.deskclock.stopwatch.Stopwatches;
     49 import com.android.deskclock.timer.TimerFragment;
     50 import com.android.deskclock.timer.TimerObj;
     51 import com.android.deskclock.timer.Timers;
     52 import com.android.deskclock.worldclock.CitiesActivity;
     53 
     54 import java.util.ArrayList;
     55 import java.util.HashSet;
     56 import java.util.Locale;
     57 import java.util.TimeZone;
     58 
     59 /**
     60  * DeskClock clock view for desk docks.
     61  */
     62 public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler,
     63             LabelDialogFragment.AlarmLabelDialogHandler{
     64     private static final boolean DEBUG = false;
     65 
     66     private static final String LOG_TAG = "DeskClock";
     67 
     68     // Alarm action for midnight (so we can update the date display).
     69     private static final String KEY_SELECTED_TAB = "selected_tab";
     70     private static final String KEY_CLOCK_STATE = "clock_state";
     71 
     72     public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab";
     73 
     74     private ActionBar mActionBar;
     75     private Tab mAlarmTab;
     76     private Tab mClockTab;
     77     private Tab mTimerTab;
     78     private Tab mStopwatchTab;
     79     private Menu mMenu;
     80 
     81     private ViewPager mViewPager;
     82     private TabsAdapter mTabsAdapter;
     83 
     84     public static final int ALARM_TAB_INDEX = 0;
     85     public static final int CLOCK_TAB_INDEX = 1;
     86     public static final int TIMER_TAB_INDEX = 2;
     87     public static final int STOPWATCH_TAB_INDEX = 3;
     88     // Tabs indices are switched for right-to-left since there is no
     89     // native support for RTL in the ViewPager.
     90     public static final int RTL_ALARM_TAB_INDEX = 3;
     91     public static final int RTL_CLOCK_TAB_INDEX = 2;
     92     public static final int RTL_TIMER_TAB_INDEX = 1;
     93     public static final int RTL_STOPWATCH_TAB_INDEX = 0;
     94 
     95     private int mSelectedTab;
     96 
     97     @Override
     98     public void onNewIntent(Intent newIntent) {
     99         super.onNewIntent(newIntent);
    100         if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent);
    101 
    102         // update our intent so that we can consult it to determine whether or
    103         // not the most recent launch was via a dock event
    104         setIntent(newIntent);
    105 
    106         // Timer receiver may ask to go to the timers fragment if a timer expired.
    107         int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
    108         if (tab != -1) {
    109             if (mActionBar != null) {
    110                 mActionBar.setSelectedNavigationItem(tab);
    111             }
    112         }
    113     }
    114 
    115     private void initViews() {
    116         if (mTabsAdapter == null) {
    117             mViewPager = new ViewPager(this);
    118             mViewPager.setId(R.id.desk_clock_pager);
    119             // Keep all four tabs to minimize jank.
    120             mViewPager.setOffscreenPageLimit(3);
    121             mTabsAdapter = new TabsAdapter(this, mViewPager);
    122             createTabs(mSelectedTab);
    123         }
    124         setContentView(mViewPager);
    125         mActionBar.setSelectedNavigationItem(mSelectedTab);
    126     }
    127 
    128     private void createTabs(int selectedIndex) {
    129         mActionBar = getActionBar();
    130 
    131         if (mActionBar != null) {
    132             mActionBar.setDisplayOptions(0);
    133             mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    134 
    135             mAlarmTab = mActionBar.newTab();
    136             mAlarmTab.setIcon(R.drawable.alarm_tab);
    137             mAlarmTab.setContentDescription(R.string.menu_alarm);
    138             mTabsAdapter.addTab(mAlarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX);
    139 
    140             mClockTab = mActionBar.newTab();
    141             mClockTab.setIcon(R.drawable.clock_tab);
    142             mClockTab.setContentDescription(R.string.menu_clock);
    143             mTabsAdapter.addTab(mClockTab, ClockFragment.class, CLOCK_TAB_INDEX);
    144 
    145             mTimerTab = mActionBar.newTab();
    146             mTimerTab.setIcon(R.drawable.timer_tab);
    147             mTimerTab.setContentDescription(R.string.menu_timer);
    148             mTabsAdapter.addTab(mTimerTab, TimerFragment.class, TIMER_TAB_INDEX);
    149 
    150             mStopwatchTab = mActionBar.newTab();
    151             mStopwatchTab.setIcon(R.drawable.stopwatch_tab);
    152             mStopwatchTab.setContentDescription(R.string.menu_stopwatch);
    153             mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class,STOPWATCH_TAB_INDEX);
    154 
    155             mActionBar.setSelectedNavigationItem(selectedIndex);
    156             mTabsAdapter.notifySelectedPage(selectedIndex);
    157         }
    158     }
    159 
    160     @Override
    161     protected void onCreate(Bundle icicle) {
    162         super.onCreate(icicle);
    163 
    164         mSelectedTab = CLOCK_TAB_INDEX;
    165         if (icicle != null) {
    166             mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX);
    167         }
    168 
    169         // Timer receiver may ask the app to go to the timer fragment if a timer expired
    170         Intent i = getIntent();
    171         if (i != null) {
    172             int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
    173             if (tab != -1) {
    174                 mSelectedTab = tab;
    175             }
    176         }
    177         initViews();
    178         setHomeTimeZone();
    179 
    180         // We need to update the system next alarm time on app startup because the
    181         // user might have clear our data.
    182         AlarmStateManager.updateNextAlarm(this);
    183     }
    184 
    185     @Override
    186     protected void onResume() {
    187         super.onResume();
    188 
    189         // We only want to show notifications for stopwatch/timer when the app is closed so
    190         // that we don't have to worry about keeping the notifications in perfect sync with
    191         // the app.
    192         Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class);
    193         stopwatchIntent.setAction(Stopwatches.KILL_NOTIF);
    194         startService(stopwatchIntent);
    195 
    196         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    197         SharedPreferences.Editor editor = prefs.edit();
    198         editor.putBoolean(Timers.NOTIF_APP_OPEN, true);
    199         editor.apply();
    200         Intent timerIntent = new Intent();
    201         timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL);
    202         sendBroadcast(timerIntent);
    203     }
    204 
    205     @Override
    206     public void onPause() {
    207         Intent intent = new Intent(getApplicationContext(), StopwatchService.class);
    208         intent.setAction(Stopwatches.SHOW_NOTIF);
    209         startService(intent);
    210 
    211         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    212         SharedPreferences.Editor editor = prefs.edit();
    213         editor.putBoolean(Timers.NOTIF_APP_OPEN, false);
    214         editor.apply();
    215         Utils.showInUseNotifications(this);
    216 
    217         super.onPause();
    218     }
    219 
    220     @Override
    221     protected void onSaveInstanceState(Bundle outState) {
    222         super.onSaveInstanceState(outState);
    223         outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex());
    224     }
    225 
    226     public void clockButtonsOnClick(View v) {
    227         if (v == null) {
    228             return;
    229         }
    230         switch (v.getId()) {
    231             case R.id.cities_button:
    232                 startActivity(new Intent(this, CitiesActivity.class));
    233                 break;
    234             default:
    235                 break;
    236         }
    237     }
    238 
    239     @Override
    240     public boolean onCreateOptionsMenu(Menu menu) {
    241         // We only want to show it as a menu in landscape, and only for clock/alarm fragment.
    242         mMenu = menu;
    243         if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
    244             if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX ||
    245                     mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) {
    246                 // Clear the menu so that it doesn't get duplicate items in case onCreateOptionsMenu
    247                 // was called multiple times.
    248                 menu.clear();
    249                 getMenuInflater().inflate(R.menu.desk_clock_menu, menu);
    250             }
    251             // Always return true for landscape, regardless of whether we've inflated the menu, so
    252             // that when we switch tabs this method will get called and we can inflate the menu.
    253             return true;
    254         }
    255         return false;
    256     }
    257 
    258     @Override
    259     public boolean onPrepareOptionsMenu(Menu menu) {
    260         updateMenu(menu);
    261         return true;
    262     }
    263 
    264     private void updateMenu(Menu menu) {
    265         // Hide "help" if we don't have a URI for it.
    266         MenuItem help = menu.findItem(R.id.menu_item_help);
    267         if (help != null) {
    268             Utils.prepareHelpMenuItem(this, help);
    269         }
    270 
    271         // Hide "lights out" for timer.
    272         MenuItem nightMode = menu.findItem(R.id.menu_item_night_mode);
    273         if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX) {
    274             nightMode.setVisible(false);
    275         } else if (mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) {
    276             nightMode.setVisible(true);
    277         }
    278     }
    279 
    280     @Override
    281     public boolean onOptionsItemSelected(MenuItem item) {
    282         if (processMenuClick(item)) {
    283             return true;
    284         }
    285 
    286         return super.onOptionsItemSelected(item);
    287     }
    288 
    289     private boolean processMenuClick(MenuItem item) {
    290         switch (item.getItemId()) {
    291             case R.id.menu_item_settings:
    292                 startActivity(new Intent(DeskClock.this, SettingsActivity.class));
    293                 return true;
    294             case R.id.menu_item_help:
    295                 Intent i = item.getIntent();
    296                 if (i != null) {
    297                     try {
    298                         startActivity(i);
    299                     } catch (ActivityNotFoundException e) {
    300                         // No activity found to match the intent - ignore
    301                     }
    302                 }
    303                 return true;
    304             case R.id.menu_item_night_mode:
    305                 startActivity(new Intent(DeskClock.this, ScreensaverActivity.class));
    306             default:
    307                 break;
    308         }
    309         return true;
    310     }
    311 
    312     /***
    313      * Insert the local time zone as the Home Time Zone if one is not set
    314      */
    315     private void setHomeTimeZone() {
    316         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    317         String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, "");
    318         if (!homeTimeZone.isEmpty()) {
    319             return;
    320         }
    321         homeTimeZone = TimeZone.getDefault().getID();
    322         SharedPreferences.Editor editor = prefs.edit();
    323         editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone);
    324         editor.apply();
    325         Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone);
    326     }
    327 
    328     public void registerPageChangedListener(DeskClockFragment frag) {
    329         if (mTabsAdapter != null) {
    330             mTabsAdapter.registerPageChangedListener(frag);
    331         }
    332     }
    333 
    334     public void unregisterPageChangedListener(DeskClockFragment frag) {
    335         if (mTabsAdapter != null) {
    336             mTabsAdapter.unregisterPageChangedListener(frag);
    337         }
    338     }
    339 
    340 
    341     /***
    342      * Adapter for wrapping together the ActionBar's tab with the ViewPager
    343      */
    344 
    345     private class TabsAdapter extends FragmentPagerAdapter
    346             implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
    347 
    348         private static final String KEY_TAB_POSITION = "tab_position";
    349 
    350         final class TabInfo {
    351             private final Class<?> clss;
    352             private final Bundle args;
    353 
    354             TabInfo(Class<?> _class, int position) {
    355                 clss = _class;
    356                 args = new Bundle();
    357                 args.putInt(KEY_TAB_POSITION, position);
    358             }
    359 
    360             public int getPosition() {
    361                 return args.getInt(KEY_TAB_POSITION, 0);
    362             }
    363         }
    364 
    365         private final ArrayList<TabInfo> mTabs = new ArrayList <TabInfo>();
    366         ActionBar mMainActionBar;
    367         Context mContext;
    368         ViewPager mPager;
    369         // Used for doing callbacks to fragments.
    370         HashSet<String> mFragmentTags = new HashSet<String>();
    371 
    372         public TabsAdapter(Activity activity, ViewPager pager) {
    373             super(activity.getFragmentManager());
    374             mContext = activity;
    375             mMainActionBar = activity.getActionBar();
    376             mPager = pager;
    377             mPager.setAdapter(this);
    378             mPager.setOnPageChangeListener(this);
    379         }
    380 
    381         @Override
    382         public Fragment getItem(int position) {
    383             TabInfo info = mTabs.get(getRtlPosition(position));
    384             DeskClockFragment f = (DeskClockFragment) Fragment.instantiate(
    385                     mContext, info.clss.getName(), info.args);
    386             return f;
    387         }
    388 
    389         @Override
    390         public int getCount() {
    391             return mTabs.size();
    392         }
    393 
    394         public void addTab(ActionBar.Tab tab, Class<?> clss, int position) {
    395             TabInfo info = new TabInfo(clss, position);
    396             tab.setTag(info);
    397             tab.setTabListener(this);
    398             mTabs.add(info);
    399             mMainActionBar.addTab(tab);
    400             notifyDataSetChanged();
    401         }
    402 
    403         @Override
    404         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    405             // Do nothing
    406         }
    407 
    408         @Override
    409         public void onPageSelected(int position) {
    410             // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is.
    411             mMainActionBar.setSelectedNavigationItem(getRtlPosition(position));
    412             notifyPageChanged(position);
    413 
    414             // Only show the overflow menu for alarm and world clock.
    415             if (mMenu != null) {
    416                 // Make sure the menu's been initialized.
    417                 if (position == ALARM_TAB_INDEX || position == CLOCK_TAB_INDEX) {
    418                     mMenu.setGroupVisible(R.id.menu_items, true);
    419                     onCreateOptionsMenu(mMenu);
    420                 } else {
    421                     mMenu.setGroupVisible(R.id.menu_items, false);
    422                 }
    423             }
    424         }
    425 
    426         @Override
    427         public void onPageScrollStateChanged(int state) {
    428             // Do nothing
    429         }
    430 
    431         @Override
    432         public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
    433             // Do nothing
    434         }
    435 
    436         @Override
    437         public void onTabSelected(Tab tab, FragmentTransaction ft) {
    438             TabInfo info = (TabInfo)tab.getTag();
    439             int position = info.getPosition();
    440             mPager.setCurrentItem(getRtlPosition(position));
    441         }
    442 
    443         @Override
    444         public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
    445             // Do nothing
    446         }
    447 
    448         public void notifySelectedPage(int page) {
    449             notifyPageChanged(page);
    450         }
    451 
    452         private void notifyPageChanged(int newPage) {
    453             for (String tag : mFragmentTags) {
    454                 final FragmentManager fm = getFragmentManager();
    455                 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag);
    456                 if (f != null) {
    457                     f.onPageChanged(newPage);
    458                 }
    459             }
    460         }
    461 
    462         public void registerPageChangedListener(DeskClockFragment frag) {
    463             String tag = frag.getTag();
    464             if (mFragmentTags.contains(tag)) {
    465                 Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag);
    466             } else {
    467                 mFragmentTags.add(frag.getTag());
    468             }
    469             // Since registering a listener by the fragment is done sometimes after the page
    470             // was already changed, make sure the fragment gets the current page
    471             frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex());
    472         }
    473 
    474         public void unregisterPageChangedListener(DeskClockFragment frag) {
    475             mFragmentTags.remove(frag.getTag());
    476         }
    477 
    478         private boolean isRtl() {
    479             return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
    480                     View.LAYOUT_DIRECTION_RTL;
    481         }
    482 
    483         private int getRtlPosition(int position) {
    484             if (isRtl()) {
    485                 switch (position) {
    486                     case TIMER_TAB_INDEX:
    487                         return RTL_TIMER_TAB_INDEX;
    488                     case CLOCK_TAB_INDEX:
    489                         return RTL_CLOCK_TAB_INDEX;
    490                     case STOPWATCH_TAB_INDEX:
    491                         return RTL_STOPWATCH_TAB_INDEX;
    492                     case ALARM_TAB_INDEX:
    493                         return RTL_ALARM_TAB_INDEX;
    494                     default:
    495                         break;
    496                 }
    497             }
    498             return position;
    499         }
    500     }
    501 
    502     public static abstract class OnTapListener implements OnTouchListener {
    503         private float mLastTouchX;
    504         private float mLastTouchY;
    505         private long mLastTouchTime;
    506         private final TextView mMakePressedTextView;
    507         private final int mPressedColor, mGrayColor;
    508         private final float MAX_MOVEMENT_ALLOWED = 20;
    509         private final long MAX_TIME_ALLOWED = 500;
    510 
    511         public OnTapListener(Activity activity, TextView makePressedView) {
    512             mMakePressedTextView = makePressedView;
    513             mPressedColor = activity.getResources().getColor(Utils.getPressedColorId());
    514             mGrayColor = activity.getResources().getColor(Utils.getGrayColorId());
    515         }
    516 
    517         @Override
    518         public boolean onTouch(View v, MotionEvent e) {
    519             switch (e.getAction()) {
    520                 case (MotionEvent.ACTION_DOWN):
    521                     mLastTouchTime = Utils.getTimeNow();
    522                     mLastTouchX = e.getX();
    523                     mLastTouchY = e.getY();
    524                     if (mMakePressedTextView != null) {
    525                         mMakePressedTextView.setTextColor(mPressedColor);
    526                     }
    527                     break;
    528                 case (MotionEvent.ACTION_UP):
    529                     float xDiff = Math.abs(e.getX()-mLastTouchX);
    530                     float yDiff = Math.abs(e.getY()-mLastTouchY);
    531                     long timeDiff = (Utils.getTimeNow() - mLastTouchTime);
    532                     if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED
    533                             && timeDiff < MAX_TIME_ALLOWED) {
    534                         if (mMakePressedTextView != null) {
    535                             v = mMakePressedTextView;
    536                         }
    537                         processClick(v);
    538                         resetValues();
    539                         return true;
    540                     }
    541                     resetValues();
    542                     break;
    543                 case (MotionEvent.ACTION_MOVE):
    544                     xDiff = Math.abs(e.getX()-mLastTouchX);
    545                     yDiff = Math.abs(e.getY()-mLastTouchY);
    546                     if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) {
    547                         resetValues();
    548                     }
    549                     break;
    550                 default:
    551                     resetValues();
    552             }
    553             return false;
    554         }
    555 
    556         private void resetValues() {
    557             mLastTouchX = -1*MAX_MOVEMENT_ALLOWED + 1;
    558             mLastTouchY = -1*MAX_MOVEMENT_ALLOWED + 1;
    559             mLastTouchTime = -1*MAX_TIME_ALLOWED + 1;
    560             if (mMakePressedTextView != null) {
    561                 mMakePressedTextView.setTextColor(mGrayColor);
    562             }
    563         }
    564 
    565         protected abstract void processClick(View v);
    566     }
    567 
    568     /** Called by the LabelDialogFormat class after the dialog is finished. **/
    569     @Override
    570     public void onDialogLabelSet(TimerObj timer, String label, String tag) {
    571         Fragment frag = getFragmentManager().findFragmentByTag(tag);
    572         if (frag instanceof TimerFragment) {
    573             ((TimerFragment) frag).setLabel(timer, label);
    574         }
    575     }
    576 
    577     /** Called by the LabelDialogFormat class after the dialog is finished. **/
    578     @Override
    579     public void onDialogLabelSet(Alarm alarm, String label, String tag) {
    580         Fragment frag = getFragmentManager().findFragmentByTag(tag);
    581         if (frag instanceof AlarmClockFragment) {
    582             ((AlarmClockFragment) frag).setLabel(alarm, label);
    583         }
    584     }
    585 }
    586