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