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