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