1/*
2 * Copyright (C) 2012 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.Activity;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.SharedPreferences;
25import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
26import android.database.ContentObserver;
27import android.os.Bundle;
28import android.os.Handler;
29import android.preference.PreferenceManager;
30import android.provider.Settings;
31import android.view.LayoutInflater;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.View.OnTouchListener;
35import android.view.ViewConfiguration;
36import android.view.ViewGroup;
37import android.widget.ListView;
38import android.widget.TextClock;
39
40import com.android.deskclock.alarms.AlarmNotifications;
41import com.android.deskclock.worldclock.WorldClockAdapter;
42
43/**
44 * Fragment that shows  the clock (analog or digital), the next alarm info and the world clock.
45 */
46public class ClockFragment extends DeskClockFragment implements OnSharedPreferenceChangeListener {
47
48    private static final String BUTTONS_HIDDEN_KEY = "buttons_hidden";
49    private final static String TAG = "ClockFragment";
50
51    private boolean mButtonsHidden = false;
52    private View mDigitalClock, mAnalogClock, mClockFrame;
53    private WorldClockAdapter mAdapter;
54    private ListView mList;
55    private SharedPreferences mPrefs;
56    private String mDateFormat;
57    private String mDateFormatForAccessibility;
58    private String mDefaultClockStyle;
59    private String mClockStyle;
60
61    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
62            @Override
63        public void onReceive(Context context, Intent intent) {
64            String action = intent.getAction();
65            boolean changed = action.equals(Intent.ACTION_TIME_CHANGED)
66                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)
67                    || action.equals(Intent.ACTION_LOCALE_CHANGED);
68            if (changed) {
69                Utils.updateDate(mDateFormat, mDateFormatForAccessibility,mClockFrame);
70                if (mAdapter != null) {
71                    // *CHANGED may modify the need for showing the Home City
72                    if (mAdapter.hasHomeCity() != mAdapter.needHomeCity()) {
73                        mAdapter.reloadData(context);
74                    } else {
75                        mAdapter.notifyDataSetChanged();
76                    }
77                    // Locale change: update digital clock format and
78                    // reload the cities list with new localized names
79                    if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
80                        if (mDigitalClock != null) {
81                            Utils.setTimeFormat(
82                                   (TextClock)(mDigitalClock.findViewById(R.id.digital_clock)),
83                                   (int)context.getResources().
84                                           getDimension(R.dimen.bottom_text_size));
85                        }
86                        mAdapter.loadCitiesDb(context);
87                        mAdapter.notifyDataSetChanged();
88                    }
89                }
90                Utils.setQuarterHourUpdater(mHandler, mQuarterHourUpdater);
91            }
92            if (changed || action.equals(AlarmNotifications.SYSTEM_ALARM_CHANGE_ACTION)) {
93                Utils.refreshAlarm(getActivity(), mClockFrame);
94            }
95        }
96    };
97
98    private final Handler mHandler = new Handler();
99
100    private final ContentObserver mAlarmObserver = new ContentObserver(mHandler) {
101        @Override
102        public void onChange(boolean selfChange) {
103            Utils.refreshAlarm(ClockFragment.this.getActivity(), mClockFrame);
104        }
105    };
106
107    // Thread that runs on every quarter-hour and refreshes the date.
108    private final Runnable mQuarterHourUpdater = new Runnable() {
109        @Override
110        public void run() {
111            // Update the main and world clock dates
112            Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mClockFrame);
113            if (mAdapter != null) {
114                mAdapter.notifyDataSetChanged();
115            }
116            Utils.setQuarterHourUpdater(mHandler, mQuarterHourUpdater);
117        }
118    };
119
120    public ClockFragment() {
121    }
122
123    @Override
124    public View onCreateView(LayoutInflater inflater, ViewGroup container,
125                             Bundle icicle) {
126        // Inflate the layout for this fragment
127        View v = inflater.inflate(R.layout.clock_fragment, container, false);
128        if (icicle != null) {
129            mButtonsHidden = icicle.getBoolean(BUTTONS_HIDDEN_KEY, false);
130        }
131        mList = (ListView)v.findViewById(R.id.cities);
132        mList.setDivider(null);
133
134        OnTouchListener longPressNightMode = new OnTouchListener() {
135            private float mMaxMovementAllowed = -1;
136            private int mLongPressTimeout = -1;
137            private float mLastTouchX, mLastTouchY;
138
139            @Override
140            public boolean onTouch(View v, MotionEvent event) {
141                if (mMaxMovementAllowed == -1) {
142                    mMaxMovementAllowed = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
143                    mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
144                }
145
146                switch (event.getAction()) {
147                    case (MotionEvent.ACTION_DOWN):
148                        long time = Utils.getTimeNow();
149                        mHandler.postDelayed(new Runnable() {
150                            @Override
151                            public void run() {
152                                startActivity(new Intent(getActivity(), ScreensaverActivity.class));
153                            }
154                        }, mLongPressTimeout);
155                        mLastTouchX = event.getX();
156                        mLastTouchY = event.getY();
157                        return true;
158                    case (MotionEvent.ACTION_MOVE):
159                        float xDiff = Math.abs(event.getX()-mLastTouchX);
160                        float yDiff = Math.abs(event.getY()-mLastTouchY);
161                        if (xDiff >= mMaxMovementAllowed || yDiff >= mMaxMovementAllowed) {
162                            mHandler.removeCallbacksAndMessages(null);
163                        }
164                        break;
165                    default:
166                        mHandler.removeCallbacksAndMessages(null);
167                }
168                return false;
169            }
170        };
171
172        // On tablet landscape, the clock frame will be a distinct view. Otherwise, it'll be added
173        // on as a header to the main listview.
174        mClockFrame = v.findViewById(R.id.main_clock_left_pane);
175        if (mClockFrame == null) {
176            mClockFrame = inflater.inflate(R.layout.main_clock_frame, mList, false);
177            mList.addHeaderView(mClockFrame, null, false);
178        } else {
179            // The main clock frame needs its own touch listener for night mode now.
180            v.setOnTouchListener(longPressNightMode);
181        }
182        mList.setOnTouchListener(longPressNightMode);
183
184        // If the current layout has a fake overflow menu button, let the parent
185        // activity set up its click and touch listeners.
186        View menuButton = v.findViewById(R.id.menu_button);
187        if (menuButton != null) {
188            setupFakeOverflowMenuButton(menuButton);
189        }
190
191        mDigitalClock = mClockFrame.findViewById(R.id.digital_clock);
192        mAnalogClock = mClockFrame.findViewById(R.id.analog_clock);
193        Utils.setTimeFormat((TextClock)(mDigitalClock.findViewById(R.id.digital_clock)),
194                (int)getResources().getDimension(R.dimen.bottom_text_size));
195        View footerView = inflater.inflate(R.layout.blank_footer_view, mList, false);
196        footerView.setBackgroundResource(R.color.blackish);
197        mList.addFooterView(footerView);
198        mAdapter = new WorldClockAdapter(getActivity());
199        mList.setAdapter(mAdapter);
200
201        mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
202        mDefaultClockStyle = getActivity().getResources().getString(R.string.default_clock_style);
203        return v;
204    }
205
206    @Override
207    public void onResume () {
208        super.onResume();
209        mPrefs.registerOnSharedPreferenceChangeListener(this);
210        mDateFormat = getString(R.string.abbrev_wday_month_day_no_year);
211        mDateFormatForAccessibility = getString(R.string.full_wday_month_day_no_year);
212
213        Activity activity = getActivity();
214        Utils.setQuarterHourUpdater(mHandler, mQuarterHourUpdater);
215        // Besides monitoring when quarter-hour changes, monitor other actions that
216        // effect clock time
217        IntentFilter filter = new IntentFilter();
218        filter.addAction(AlarmNotifications.SYSTEM_ALARM_CHANGE_ACTION);
219        filter.addAction(Intent.ACTION_TIME_CHANGED);
220        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
221        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
222        activity.registerReceiver(mIntentReceiver, filter);
223
224        // Resume can invoked after changing the cities list or a change in locale
225        if (mAdapter != null) {
226            mAdapter.loadCitiesDb(activity);
227            mAdapter.reloadData(activity);
228        }
229        // Resume can invoked after changing the clock style.
230        View clockView = Utils.setClockStyle(activity, mDigitalClock, mAnalogClock,
231                SettingsActivity.KEY_CLOCK_STYLE);
232        mClockStyle = (clockView == mDigitalClock ?
233                Utils.CLOCK_TYPE_DIGITAL : Utils.CLOCK_TYPE_ANALOG);
234
235        // Center the main clock frame if cities are empty.
236        if (getView().findViewById(R.id.main_clock_left_pane) != null && mAdapter.getCount() == 0) {
237            mList.setVisibility(View.GONE);
238        } else {
239            mList.setVisibility(View.VISIBLE);
240        }
241        mAdapter.notifyDataSetChanged();
242
243        Utils.updateDate(mDateFormat, mDateFormatForAccessibility,mClockFrame);
244        Utils.refreshAlarm(activity, mClockFrame);
245        activity.getContentResolver().registerContentObserver(
246                Settings.System.getUriFor(Settings.System.NEXT_ALARM_FORMATTED),
247                false,
248                mAlarmObserver);
249    }
250
251    @Override
252    public void onPause() {
253        super.onPause();
254        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
255        Utils.cancelQuarterHourUpdater(mHandler, mQuarterHourUpdater);
256        Activity activity = getActivity();
257        activity.unregisterReceiver(mIntentReceiver);
258        activity.getContentResolver().unregisterContentObserver(mAlarmObserver);
259    }
260
261    @Override
262    public void onSaveInstanceState (Bundle outState) {
263        outState.putBoolean(BUTTONS_HIDDEN_KEY, mButtonsHidden);
264        super.onSaveInstanceState(outState);
265    }
266
267    @Override
268    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
269        if (key == SettingsActivity.KEY_CLOCK_STYLE) {
270            mClockStyle = prefs.getString(SettingsActivity.KEY_CLOCK_STYLE, mDefaultClockStyle);
271            mAdapter.notifyDataSetChanged();
272        }
273    }
274 }
275