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