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.Fragment;
     20 import android.app.FragmentManager;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.media.AudioManager;
     24 import android.os.Bundle;
     25 import android.support.annotation.VisibleForTesting;
     26 import android.support.design.widget.TabLayout;
     27 import android.support.design.widget.TabLayout.Tab;
     28 import android.support.design.widget.TabLayout.ViewPagerOnTabSelectedListener;
     29 import android.support.v13.app.FragmentPagerAdapter;
     30 import android.support.v4.view.ViewPager.OnPageChangeListener;
     31 import android.support.v7.app.AppCompatActivity;
     32 import android.support.v7.widget.Toolbar;
     33 import android.util.ArraySet;
     34 import android.view.Menu;
     35 import android.view.MenuItem;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.view.ViewGroup;
     39 import android.widget.ImageButton;
     40 import android.widget.ImageView;
     41 
     42 import com.android.deskclock.actionbarmenu.ActionBarMenuManager;
     43 import com.android.deskclock.actionbarmenu.MenuItemControllerFactory;
     44 import com.android.deskclock.actionbarmenu.NightModeMenuItemController;
     45 import com.android.deskclock.actionbarmenu.SettingMenuItemController;
     46 import com.android.deskclock.alarms.AlarmStateManager;
     47 import com.android.deskclock.data.DataModel;
     48 import com.android.deskclock.events.Events;
     49 import com.android.deskclock.provider.Alarm;
     50 import com.android.deskclock.stopwatch.StopwatchFragment;
     51 import com.android.deskclock.timer.TimerFragment;
     52 import com.android.deskclock.widget.RtlViewPager;
     53 
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 import java.util.Set;
     57 
     58 /**
     59  * DeskClock clock view for desk docks.
     60  */
     61 public class DeskClock extends BaseActivity
     62         implements LabelDialogFragment.AlarmLabelDialogHandler {
     63 
     64     private static final String TAG = "DeskClock";
     65 
     66     // Alarm action for midnight (so we can update the date display).
     67     private static final String KEY_SELECTED_TAB = "selected_tab";
     68     public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab";
     69 
     70     public static final int ALARM_TAB_INDEX = 0;
     71     public static final int CLOCK_TAB_INDEX = 1;
     72     public static final int TIMER_TAB_INDEX = 2;
     73     public static final int STOPWATCH_TAB_INDEX = 3;
     74 
     75     private final ActionBarMenuManager mActionBarMenuManager = new ActionBarMenuManager(this);
     76 
     77     private TabLayout mTabLayout;
     78     private RtlViewPager mViewPager;
     79     private ImageView mFab;
     80     private ImageButton mLeftButton;
     81     private ImageButton mRightButton;
     82 
     83     private TabsAdapter mTabsAdapter;
     84     private int mSelectedTab;
     85 
     86     /** {@code true} when a settings change necessitates recreating this activity. */
     87     private boolean mRecreateActivity;
     88 
     89     @Override
     90     public void onNewIntent(Intent newIntent) {
     91         super.onNewIntent(newIntent);
     92         LogUtils.d(TAG, "onNewIntent with intent: %s", newIntent);
     93 
     94         // update our intent so that we can consult it to determine whether or
     95         // not the most recent launch was via a dock event
     96         setIntent(newIntent);
     97 
     98         // Honor the tab requested by the intent, if any.
     99         int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
    100         if (tab != -1 && mTabLayout != null) {
    101             mTabLayout.getTabAt(tab).select();
    102             mViewPager.setCurrentItem(tab);
    103         }
    104     }
    105 
    106     @VisibleForTesting
    107     DeskClockFragment getSelectedFragment() {
    108         return (DeskClockFragment) mTabsAdapter.getItem(mSelectedTab);
    109     }
    110 
    111     private void createTabs() {
    112         final TabLayout.Tab alarmTab = mTabLayout.newTab();
    113         alarmTab.setIcon(R.drawable.ic_tab_alarm).setContentDescription(R.string.menu_alarm);
    114         mTabsAdapter.addTab(alarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX);
    115 
    116         final Tab clockTab = mTabLayout.newTab();
    117         clockTab.setIcon(R.drawable.ic_tab_clock).setContentDescription(R.string.menu_clock);
    118         mTabsAdapter.addTab(clockTab, ClockFragment.class, CLOCK_TAB_INDEX);
    119 
    120         final Tab timerTab = mTabLayout.newTab();
    121         timerTab.setIcon(R.drawable.ic_tab_timer).setContentDescription(R.string.menu_timer);
    122         mTabsAdapter.addTab(timerTab, TimerFragment.class, TIMER_TAB_INDEX);
    123 
    124         final Tab stopwatchTab = mTabLayout.newTab();
    125         stopwatchTab.setIcon(R.drawable.ic_tab_stopwatch)
    126                 .setContentDescription(R.string.menu_stopwatch);
    127         mTabsAdapter.addTab(stopwatchTab, StopwatchFragment.class, STOPWATCH_TAB_INDEX);
    128 
    129         mTabLayout.getTabAt(mSelectedTab).select();
    130         mViewPager.setCurrentItem(mSelectedTab);
    131         mTabsAdapter.notifySelectedPage(mSelectedTab);
    132     }
    133 
    134     @Override
    135     protected void onCreate(Bundle icicle) {
    136         super.onCreate(icicle);
    137         setVolumeControlStream(AudioManager.STREAM_ALARM);
    138 
    139         if (icicle != null) {
    140             mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX);
    141         } else {
    142             mSelectedTab = CLOCK_TAB_INDEX;
    143 
    144             // Set the background color to initially match the theme value so that we can
    145             // smoothly transition to the dynamic color.
    146             setBackgroundColor(getResources().getColor(R.color.default_background),
    147                     false /* animate */);
    148         }
    149 
    150         // Honor the tab requested by the intent, if any.
    151         final Intent intent = getIntent();
    152         if (intent != null) {
    153             int tab = intent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
    154             if (tab != -1) {
    155                 mSelectedTab = tab;
    156             }
    157         }
    158 
    159         setContentView(R.layout.desk_clock);
    160         final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    161         setSupportActionBar(toolbar);
    162         mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs);
    163         mFab = (ImageView) findViewById(R.id.fab);
    164         mLeftButton = (ImageButton) findViewById(R.id.left_button);
    165         mRightButton = (ImageButton) findViewById(R.id.right_button);
    166         if (mTabsAdapter == null) {
    167             mViewPager = (RtlViewPager) findViewById(R.id.desk_clock_pager);
    168             // Keep all four tabs to minimize jank.
    169             mViewPager.setOffscreenPageLimit(3);
    170             // Set Accessibility Delegate to null so ViewPager doesn't intercept movements and
    171             // prevent the fab from being selected.
    172             mViewPager.setAccessibilityDelegate(null);
    173             mTabsAdapter = new TabsAdapter(this, mViewPager);
    174             createTabs();
    175             mTabLayout.setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(mViewPager));
    176         }
    177 
    178         mFab.setOnClickListener(new OnClickListener() {
    179             @Override
    180             public void onClick(View view) {
    181                 getSelectedFragment().onFabClick(view);
    182             }
    183         });
    184         mLeftButton.setOnClickListener(new OnClickListener() {
    185             @Override
    186             public void onClick(View view) {
    187                 getSelectedFragment().onLeftButtonClick(view);
    188             }
    189         });
    190         mRightButton.setOnClickListener(new OnClickListener() {
    191             @Override
    192             public void onClick(View view) {
    193                 getSelectedFragment().onRightButtonClick(view);
    194             }
    195         });
    196 
    197         // Configure the menu item controllers.
    198         mActionBarMenuManager
    199                 .addMenuItemController(new SettingMenuItemController(this))
    200                 .addMenuItemController(new NightModeMenuItemController(this))
    201                 .addMenuItemController(MenuItemControllerFactory.getInstance()
    202                         .buildMenuItemControllers(this));
    203 
    204         // Inflate the menu during creation to avoid a double layout pass. Otherwise, the menu
    205         // inflation occurs *after* the initial draw and a second layout pass adds in the menu.
    206         onCreateOptionsMenu(toolbar.getMenu());
    207 
    208         // We need to update the system next alarm time on app startup because the
    209         // user might have clear our data.
    210         AlarmStateManager.updateNextAlarm(this);
    211     }
    212 
    213     @Override
    214     protected void onResume() {
    215         super.onResume();
    216         DataModel.getDataModel().setApplicationInForeground(true);
    217     }
    218 
    219     @Override
    220     protected void onPostResume() {
    221         super.onPostResume();
    222 
    223         if (mRecreateActivity) {
    224             mRecreateActivity = false;
    225 
    226             // A runnable must be posted here or the new DeskClock activity will be recreated in a
    227             // paused state, even though it is the foreground activity.
    228             mViewPager.post(new Runnable() {
    229                 @Override
    230                 public void run() {
    231                     recreate();
    232                 }
    233             });
    234         }
    235     }
    236 
    237     @Override
    238     public void onPause() {
    239         DataModel.getDataModel().setApplicationInForeground(false);
    240         super.onPause();
    241     }
    242 
    243     @Override
    244     protected void onSaveInstanceState(Bundle outState) {
    245         super.onSaveInstanceState(outState);
    246         outState.putInt(KEY_SELECTED_TAB, mTabLayout.getSelectedTabPosition());
    247     }
    248 
    249     @Override
    250     public boolean onCreateOptionsMenu(Menu menu) {
    251         mActionBarMenuManager.createOptionsMenu(menu, getMenuInflater());
    252         return true;
    253     }
    254 
    255     @Override
    256     public boolean onPrepareOptionsMenu(Menu menu) {
    257         super.onPrepareOptionsMenu(menu);
    258         mActionBarMenuManager.prepareShowMenu(menu);
    259         return true;
    260     }
    261 
    262     @Override
    263     public boolean onOptionsItemSelected(MenuItem item) {
    264         if (mActionBarMenuManager.handleMenuItemClick(item)) {
    265             return true;
    266         }
    267         return super.onOptionsItemSelected(item);
    268     }
    269 
    270     @Override
    271     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    272         // Recreate the activity if any settings have been changed
    273         if (requestCode == SettingMenuItemController.REQUEST_CHANGE_SETTINGS
    274                 && resultCode == RESULT_OK) {
    275             mRecreateActivity = true;
    276         }
    277     }
    278 
    279     public void registerPageChangedListener(DeskClockFragment frag) {
    280         if (mTabsAdapter != null) {
    281             mTabsAdapter.registerPageChangedListener(frag);
    282         }
    283     }
    284 
    285     public void unregisterPageChangedListener(DeskClockFragment frag) {
    286         if (mTabsAdapter != null) {
    287             mTabsAdapter.unregisterPageChangedListener(frag);
    288         }
    289     }
    290 
    291     /**
    292      * Adapter for wrapping together the ActionBar's tab with the ViewPager
    293      */
    294     private class TabsAdapter extends FragmentPagerAdapter implements OnPageChangeListener {
    295 
    296         private static final String KEY_TAB_POSITION = "tab_position";
    297 
    298         final class TabInfo {
    299             private final Class<?> clss;
    300             private final Bundle args;
    301 
    302             TabInfo(Class<?> _class, int position) {
    303                 clss = _class;
    304                 args = new Bundle();
    305                 args.putInt(KEY_TAB_POSITION, position);
    306             }
    307 
    308             public int getPosition() {
    309                 return args.getInt(KEY_TAB_POSITION, 0);
    310             }
    311         }
    312 
    313         private final List<TabInfo> mTabs = new ArrayList<>(4 /* number of fragments */);
    314         private final Context mContext;
    315         private final RtlViewPager mPager;
    316         // Used for doing callbacks to fragments.
    317         private final Set<String> mFragmentTags = new ArraySet<>(4 /* number of fragments */);
    318 
    319         public TabsAdapter(AppCompatActivity activity, RtlViewPager pager) {
    320             super(activity.getFragmentManager());
    321             mContext = activity;
    322             mPager = pager;
    323             mPager.setAdapter(this);
    324             mPager.setOnRTLPageChangeListener(this);
    325         }
    326 
    327         @Override
    328         public Object instantiateItem(ViewGroup container, int position) {
    329             return super.instantiateItem(container, mViewPager.getRtlAwareIndex(position));
    330         }
    331 
    332         @Override
    333         public Fragment getItem(int position) {
    334             // Because this public method is called outside many times,
    335             // check if it exits first before creating a new one.
    336             final String name = makeFragmentName(R.id.desk_clock_pager, position);
    337             Fragment fragment = getFragmentManager().findFragmentByTag(name);
    338             if (fragment == null) {
    339                 TabInfo info = mTabs.get(position);
    340                 fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
    341                 if (fragment instanceof TimerFragment) {
    342                     ((TimerFragment) fragment).setFabAppearance();
    343                     ((TimerFragment) fragment).setLeftRightButtonAppearance();
    344                 }
    345             }
    346             return fragment;
    347         }
    348 
    349         /**
    350          * Copied from:
    351          * android/frameworks/support/v13/java/android/support/v13/app/FragmentPagerAdapter.java#94
    352          * Create unique name for the fragment so fragment manager knows it exist.
    353          */
    354         private String makeFragmentName(int viewId, int index) {
    355             return "android:switcher:" + viewId + ":" + index;
    356         }
    357 
    358         @Override
    359         public int getCount() {
    360             return mTabs.size();
    361         }
    362 
    363         public void addTab(TabLayout.Tab tab, Class<?> clss, int position) {
    364             TabInfo info = new TabInfo(clss, position);
    365             mTabs.add(info);
    366             mTabLayout.addTab(tab);
    367             notifyDataSetChanged();
    368         }
    369 
    370         @Override
    371         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    372             // Do nothing
    373         }
    374 
    375         @Override
    376         public void onPageSelected(int position) {
    377             // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is.
    378             mTabLayout.getTabAt(position).select();
    379             notifyPageChanged(position);
    380 
    381             mSelectedTab = position;
    382 
    383             // Avoid sending events for the initial tab selection on launch and the reselecting a
    384             // tab after a configuration change.
    385             if (DataModel.getDataModel().isApplicationInForeground()) {
    386                 switch (mSelectedTab) {
    387                     case ALARM_TAB_INDEX:
    388                         Events.sendAlarmEvent(R.string.action_show, R.string.label_deskclock);
    389                         break;
    390                     case CLOCK_TAB_INDEX:
    391                         Events.sendClockEvent(R.string.action_show, R.string.label_deskclock);
    392                         break;
    393                     case TIMER_TAB_INDEX:
    394                         Events.sendTimerEvent(R.string.action_show, R.string.label_deskclock);
    395                         break;
    396                     case STOPWATCH_TAB_INDEX:
    397                         Events.sendStopwatchEvent(R.string.action_show, R.string.label_deskclock);
    398                         break;
    399                 }
    400             }
    401 
    402             final DeskClockFragment f = (DeskClockFragment) getItem(position);
    403             if (f != null) {
    404                 f.setFabAppearance();
    405                 f.setLeftRightButtonAppearance();
    406             }
    407         }
    408 
    409         @Override
    410         public void onPageScrollStateChanged(int state) {
    411             // Do nothing
    412         }
    413 
    414         public void notifySelectedPage(int page) {
    415             notifyPageChanged(page);
    416         }
    417 
    418         private void notifyPageChanged(int newPage) {
    419             for (String tag : mFragmentTags) {
    420                 final FragmentManager fm = getFragmentManager();
    421                 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag);
    422                 if (f != null) {
    423                     f.onPageChanged(newPage);
    424                 }
    425             }
    426         }
    427 
    428         public void registerPageChangedListener(DeskClockFragment frag) {
    429             String tag = frag.getTag();
    430             if (mFragmentTags.contains(tag)) {
    431                 LogUtils.wtf(TAG, "Trying to add an existing fragment " + tag);
    432             } else {
    433                 mFragmentTags.add(frag.getTag());
    434             }
    435             // Since registering a listener by the fragment is done sometimes after the page
    436             // was already changed, make sure the fragment gets the current page
    437             frag.onPageChanged(mTabLayout.getSelectedTabPosition());
    438         }
    439 
    440         public void unregisterPageChangedListener(DeskClockFragment frag) {
    441             mFragmentTags.remove(frag.getTag());
    442         }
    443     }
    444 
    445     /**
    446      * Called by the LabelDialogFormat class after the dialog is finished.
    447      */
    448     @Override
    449     public void onDialogLabelSet(Alarm alarm, String label, String tag) {
    450         Fragment frag = getFragmentManager().findFragmentByTag(tag);
    451         if (frag instanceof AlarmClockFragment) {
    452             ((AlarmClockFragment) frag).setLabel(alarm, label);
    453         }
    454     }
    455 
    456     public int getSelectedTab() {
    457         return mSelectedTab;
    458     }
    459 
    460     public ImageView getFab() {
    461         return mFab;
    462     }
    463 
    464     public ImageButton getLeftButton() {
    465         return mLeftButton;
    466     }
    467 
    468     public ImageButton getRightButton() {
    469         return mRightButton;
    470     }
    471 }
    472