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.ActionBar;
20import android.app.ActionBar.Tab;
21import android.app.Activity;
22import android.app.Fragment;
23import android.app.FragmentManager;
24import android.app.FragmentTransaction;
25import android.content.ActivityNotFoundException;
26import android.content.Context;
27import android.content.Intent;
28import android.content.SharedPreferences;
29import android.os.Bundle;
30import android.preference.PreferenceManager;
31import android.support.v13.app.FragmentPagerAdapter;
32import android.support.v4.view.ViewPager;
33import android.util.Log;
34import android.view.Menu;
35import android.view.MenuItem;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.View.OnTouchListener;
39import android.widget.PopupMenu;
40import android.widget.TextView;
41
42import com.android.deskclock.stopwatch.StopwatchFragment;
43import com.android.deskclock.stopwatch.StopwatchService;
44import com.android.deskclock.stopwatch.Stopwatches;
45import com.android.deskclock.timer.TimerFragment;
46import com.android.deskclock.timer.TimerObj;
47import com.android.deskclock.timer.Timers;
48import com.android.deskclock.worldclock.CitiesActivity;
49
50import java.util.ArrayList;
51import java.util.HashSet;
52import java.util.TimeZone;
53
54/**
55 * DeskClock clock view for desk docks.
56 */
57public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler {
58    private static final boolean DEBUG = false;
59
60    private static final String LOG_TAG = "DeskClock";
61
62    // Alarm action for midnight (so we can update the date display).
63    private static final String KEY_SELECTED_TAB = "selected_tab";
64    private static final String KEY_CLOCK_STATE = "clock_state";
65
66    public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab";
67
68    private ActionBar mActionBar;
69    private Tab mTimerTab;
70    private Tab mClockTab;
71    private Tab mStopwatchTab;
72
73    private ViewPager mViewPager;
74    private TabsAdapter mTabsAdapter;
75
76    public static final int TIMER_TAB_INDEX = 0;
77    public static final int CLOCK_TAB_INDEX = 1;
78    public static final int STOPWATCH_TAB_INDEX = 2;
79
80    private int mSelectedTab;
81
82    @Override
83    public void onNewIntent(Intent newIntent) {
84        super.onNewIntent(newIntent);
85        if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent);
86
87        // update our intent so that we can consult it to determine whether or
88        // not the most recent launch was via a dock event
89        setIntent(newIntent);
90
91        // Timer receiver may ask to go to the timers fragment if a timer expired.
92        int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
93        if (tab != -1) {
94            if (mActionBar != null) {
95                mActionBar.setSelectedNavigationItem(tab);
96            }
97        }
98    }
99
100    private void initViews() {
101
102        if (mTabsAdapter == null) {
103            mViewPager = new ViewPager(this);
104            mViewPager.setId(R.id.desk_clock_pager);
105            mTabsAdapter = new TabsAdapter(this, mViewPager);
106            createTabs(mSelectedTab);
107        }
108        setContentView(mViewPager);
109        mActionBar.setSelectedNavigationItem(mSelectedTab);
110    }
111
112    private void createTabs(int selectedIndex) {
113        mActionBar = getActionBar();
114
115        mActionBar.setDisplayOptions(0);
116        if (mActionBar != null) {
117            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
118            mTimerTab = mActionBar.newTab();
119            mTimerTab.setIcon(R.drawable.timer_tab);
120            mTimerTab.setContentDescription(R.string.menu_timer);
121            mTabsAdapter.addTab(mTimerTab, TimerFragment.class,TIMER_TAB_INDEX);
122
123            mClockTab = mActionBar.newTab();
124            mClockTab.setIcon(R.drawable.clock_tab);
125            mClockTab.setContentDescription(R.string.menu_clock);
126            mTabsAdapter.addTab(mClockTab, ClockFragment.class,CLOCK_TAB_INDEX);
127            mStopwatchTab = mActionBar.newTab();
128            mStopwatchTab.setIcon(R.drawable.stopwatch_tab);
129            mStopwatchTab.setContentDescription(R.string.menu_stopwatch);
130            mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class,STOPWATCH_TAB_INDEX);
131            mActionBar.setSelectedNavigationItem(selectedIndex);
132            mTabsAdapter.notifySelectedPage(selectedIndex);
133        }
134    }
135
136    @Override
137    protected void onCreate(Bundle icicle) {
138        super.onCreate(icicle);
139
140        mSelectedTab = CLOCK_TAB_INDEX;
141        if (icicle != null) {
142            mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX);
143        }
144
145        // Timer receiver may ask the app to go to the timer fragment if a timer expired
146        Intent i = getIntent();
147        if (i != null) {
148            int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1);
149            if (tab != -1) {
150                mSelectedTab = tab;
151            }
152        }
153        initViews();
154        setHomeTimeZone();
155    }
156
157    @Override
158    protected void onResume() {
159        super.onResume();
160
161        Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class);
162        stopwatchIntent.setAction(Stopwatches.KILL_NOTIF);
163        startService(stopwatchIntent);
164
165        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
166        SharedPreferences.Editor editor = prefs.edit();
167        editor.putBoolean(Timers.NOTIF_APP_OPEN, true);
168        editor.apply();
169        Intent timerIntent = new Intent();
170        timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL);
171        sendBroadcast(timerIntent);
172    }
173
174    @Override
175    public void onPause() {
176
177        Intent intent = new Intent(getApplicationContext(), StopwatchService.class);
178        intent.setAction(Stopwatches.SHOW_NOTIF);
179        startService(intent);
180
181        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
182        SharedPreferences.Editor editor = prefs.edit();
183        editor.putBoolean(Timers.NOTIF_APP_OPEN, false);
184        editor.apply();
185        Utils.showInUseNotifications(this);
186
187        super.onPause();
188    }
189
190    @Override
191    protected void onSaveInstanceState(Bundle outState) {
192        super.onSaveInstanceState(outState);
193        outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex());
194    }
195
196    public void clockButtonsOnClick(View v) {
197        if (v == null)
198            return;
199        switch (v.getId()) {
200            case R.id.alarms_button:
201                startActivity(new Intent(this, AlarmClock.class));
202                break;
203            case R.id.cities_button:
204                startActivity(new Intent(this, CitiesActivity.class));
205                break;
206            case R.id.menu_button:
207                showMenu(v);
208                break;
209            default:
210                break;
211        }
212    }
213
214    private void showMenu(View v) {
215        PopupMenu popupMenu = new PopupMenu(this, v);
216        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener () {
217            @Override
218            public boolean onMenuItemClick(MenuItem item) {
219                switch (item.getItemId()) {
220                    case R.id.menu_item_settings:
221                        startActivity(new Intent(DeskClock.this, SettingsActivity.class));
222                        return true;
223                    case R.id.menu_item_help:
224                        Intent i = item.getIntent();
225                        if (i != null) {
226                            try {
227                                startActivity(i);
228                            } catch (ActivityNotFoundException e) {
229                                // No activity found to match the intent - ignore
230                            }
231                        }
232                        return true;
233                    case R.id.menu_item_night_mode:
234                        startActivity(new Intent(DeskClock.this, ScreensaverActivity.class));
235                    default:
236                        break;
237                }
238                return true;
239            }
240        });
241        popupMenu.inflate(R.menu.desk_clock_menu);
242
243        Menu menu = popupMenu.getMenu();
244        MenuItem help = menu.findItem(R.id.menu_item_help);
245        if (help != null) {
246            Utils.prepareHelpMenuItem(this, help);
247        }
248        popupMenu.show();
249    }
250
251    /***
252     * Insert the local time zone as the Home Time Zone if one is not set
253     */
254    private void setHomeTimeZone() {
255        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
256        String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, "");
257        if (!homeTimeZone.isEmpty()) {
258        return;
259        }
260        homeTimeZone = TimeZone.getDefault().getID();
261        SharedPreferences.Editor editor = prefs.edit();
262        editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone);
263        editor.apply();
264        Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone);
265    }
266
267    public void registerPageChangedListener(DeskClockFragment frag) {
268        if (mTabsAdapter != null) {
269            mTabsAdapter.registerPageChangedListener(frag);
270        }
271    }
272
273    public void unregisterPageChangedListener(DeskClockFragment frag) {
274        if (mTabsAdapter != null) {
275            mTabsAdapter.unregisterPageChangedListener(frag);
276        }
277    }
278
279
280    /***
281     * Adapter for wrapping together the ActionBar's tab with the ViewPager
282     */
283
284    private class TabsAdapter extends FragmentPagerAdapter
285            implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
286
287        private static final String KEY_TAB_POSITION = "tab_position";
288
289        final class TabInfo {
290            private final Class<?> clss;
291            private final Bundle args;
292
293            TabInfo(Class<?> _class, int position) {
294                clss = _class;
295                args = new Bundle();
296                args.putInt(KEY_TAB_POSITION, position);
297            }
298
299            public int getPosition() {
300                return args.getInt(KEY_TAB_POSITION, 0);
301            }
302        }
303
304        private final ArrayList<TabInfo> mTabs = new ArrayList <TabInfo>();
305        ActionBar mMainActionBar;
306        Context mContext;
307        ViewPager mPager;
308        // Used for doing callbacks to fragments.
309        HashSet<String> mFragmentTags = new HashSet<String>();
310
311        public TabsAdapter(Activity activity, ViewPager pager) {
312            super(activity.getFragmentManager());
313            mContext = activity;
314            mMainActionBar = activity.getActionBar();
315            mPager = pager;
316            mPager.setAdapter(this);
317            mPager.setOnPageChangeListener(this);
318        }
319
320        @Override
321        public Fragment getItem(int position) {
322            TabInfo info = mTabs.get(position);
323            DeskClockFragment f = (DeskClockFragment) Fragment.instantiate(
324                    mContext, info.clss.getName(), info.args);
325            return f;
326        }
327
328        @Override
329        public int getCount() {
330            return mTabs.size();
331        }
332
333        public void addTab(ActionBar.Tab tab, Class<?> clss, int position) {
334            TabInfo info = new TabInfo(clss, position);
335            tab.setTag(info);
336            tab.setTabListener(this);
337            mTabs.add(info);
338            mMainActionBar.addTab(tab);
339            notifyDataSetChanged();
340        }
341
342        @Override
343        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
344            // Do nothing
345        }
346
347        @Override
348        public void onPageSelected(int position) {
349            mMainActionBar.setSelectedNavigationItem(position);
350            notifyPageChanged(position);
351        }
352
353        @Override
354        public void onPageScrollStateChanged(int state) {
355            // Do nothing
356        }
357
358        @Override
359        public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
360            // Do nothing
361        }
362
363        @Override
364        public void onTabSelected(Tab tab, FragmentTransaction ft) {
365            TabInfo info = (TabInfo)tab.getTag();
366            mPager.setCurrentItem(info.getPosition());
367        }
368
369        @Override
370        public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
371            // Do nothing
372
373        }
374
375        public void notifySelectedPage(int page) {
376            notifyPageChanged(page);
377        }
378
379        private void notifyPageChanged(int newPage) {
380            for (String tag : mFragmentTags) {
381                final FragmentManager fm = getFragmentManager();
382                DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag);
383                if (f != null) {
384                    f.onPageChanged(newPage);
385                }
386            }
387        }
388
389        public void registerPageChangedListener(DeskClockFragment frag) {
390            String tag = frag.getTag();
391            if (mFragmentTags.contains(tag)) {
392                Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag);
393            } else {
394                mFragmentTags.add(frag.getTag());
395            }
396            // Since registering a listener by the fragment is done sometimes after the page
397            // was already changed, make sure the fragment gets the current page
398            frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex());
399        }
400
401        public void unregisterPageChangedListener(DeskClockFragment frag) {
402            mFragmentTags.remove(frag.getTag());
403        }
404    }
405
406    public static abstract class OnTapListener implements OnTouchListener {
407        private float mLastTouchX;
408        private float mLastTouchY;
409        private long mLastTouchTime;
410        private final TextView mMakePressedTextView;
411        private final int mPressedColor, mGrayColor;
412        private final float MAX_MOVEMENT_ALLOWED = 20;
413        private final long MAX_TIME_ALLOWED = 500;
414
415        public OnTapListener(Activity activity, TextView makePressedView) {
416            mMakePressedTextView = makePressedView;
417            mPressedColor = activity.getResources().getColor(Utils.getPressedColorId());
418            mGrayColor = activity.getResources().getColor(Utils.getGrayColorId());
419        }
420
421        @Override
422        public boolean onTouch(View v, MotionEvent e) {
423            switch (e.getAction()) {
424                case (MotionEvent.ACTION_DOWN):
425                    mLastTouchTime = Utils.getTimeNow();
426                    mLastTouchX = e.getX();
427                    mLastTouchY = e.getY();
428                    if (mMakePressedTextView != null) {
429                        mMakePressedTextView.setTextColor(mPressedColor);
430                    }
431                    break;
432                case (MotionEvent.ACTION_UP):
433                    float xDiff = Math.abs(e.getX()-mLastTouchX);
434                    float yDiff = Math.abs(e.getY()-mLastTouchY);
435                    long timeDiff = (Utils.getTimeNow() - mLastTouchTime);
436                    if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED
437                            && timeDiff < MAX_TIME_ALLOWED) {
438                        if (mMakePressedTextView != null) {
439                            v = mMakePressedTextView;
440                        }
441                        processClick(v);
442                        resetValues();
443                        return true;
444                    }
445                    resetValues();
446                    break;
447                case (MotionEvent.ACTION_MOVE):
448                    xDiff = Math.abs(e.getX()-mLastTouchX);
449                    yDiff = Math.abs(e.getY()-mLastTouchY);
450                    if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) {
451                        resetValues();
452                    }
453                    break;
454                default:
455                    resetValues();
456            }
457            return false;
458        }
459
460        private void resetValues() {
461            mLastTouchX = -1*MAX_MOVEMENT_ALLOWED + 1;
462            mLastTouchY = -1*MAX_MOVEMENT_ALLOWED + 1;
463            mLastTouchTime = -1*MAX_TIME_ALLOWED + 1;
464            if (mMakePressedTextView != null) {
465                mMakePressedTextView.setTextColor(mGrayColor);
466            }
467        }
468
469        protected abstract void processClick(View v);
470    }
471
472    /** Called by the LabelDialogFormat class after the dialog is finished. **/
473    @Override
474    public void onDialogLabelSet(TimerObj timer, String label, String tag) {
475        Fragment frag = getFragmentManager().findFragmentByTag(tag);
476        if (frag instanceof TimerFragment) {
477            ((TimerFragment) frag).setLabel(timer, label);
478        }
479    }
480}
481