• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.widget;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.BroadcastReceiver;
23 import android.database.ContentObserver;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.text.format.Time;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.provider.Settings;
30 import android.widget.TextView;
31 import android.widget.RemoteViews.RemoteView;
32 
33 import java.text.DateFormat;
34 import java.text.SimpleDateFormat;
35 import java.util.ArrayList;
36 import java.util.Date;
37 
38 //
39 // TODO
40 // - listen for the next threshold time to update the view.
41 // - listen for date format pref changed
42 // - put the AM/PM in a smaller font
43 //
44 
45 /**
46  * Displays a given time in a convenient human-readable foramt.
47  *
48  * @hide
49  */
50 @RemoteView
51 public class DateTimeView extends TextView {
52     private static final String TAG = "DateTimeView";
53 
54     private static final long TWELVE_HOURS_IN_MINUTES = 12 * 60;
55     private static final long TWENTY_FOUR_HOURS_IN_MILLIS = 24 * 60 * 60 * 1000;
56 
57     private static final int SHOW_TIME = 0;
58     private static final int SHOW_MONTH_DAY_YEAR = 1;
59 
60     Date mTime;
61     long mTimeMillis;
62 
63     int mLastDisplay = -1;
64     DateFormat mLastFormat;
65 
66     private long mUpdateTimeMillis;
67     private static final ThreadLocal<ReceiverInfo> sReceiverInfo = new ThreadLocal<ReceiverInfo>();
68 
DateTimeView(Context context)69     public DateTimeView(Context context) {
70         super(context);
71     }
72 
DateTimeView(Context context, AttributeSet attrs)73     public DateTimeView(Context context, AttributeSet attrs) {
74         super(context, attrs);
75     }
76 
77     @Override
onAttachedToWindow()78     protected void onAttachedToWindow() {
79         super.onAttachedToWindow();
80         ReceiverInfo ri = sReceiverInfo.get();
81         if (ri == null) {
82             ri = new ReceiverInfo();
83             sReceiverInfo.set(ri);
84         }
85         ri.addView(this);
86     }
87 
88     @Override
onDetachedFromWindow()89     protected void onDetachedFromWindow() {
90         super.onDetachedFromWindow();
91         final ReceiverInfo ri = sReceiverInfo.get();
92         if (ri != null) {
93             ri.removeView(this);
94         }
95     }
96 
97     @android.view.RemotableViewMethod
setTime(long time)98     public void setTime(long time) {
99         Time t = new Time();
100         t.set(time);
101         t.second = 0;
102         mTimeMillis = t.toMillis(false);
103         mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
104         update();
105     }
106 
update()107     void update() {
108         if (mTime == null) {
109             return;
110         }
111 
112         long start = System.nanoTime();
113 
114         int display;
115         Date time = mTime;
116 
117         Time t = new Time();
118         t.set(mTimeMillis);
119         t.second = 0;
120 
121         t.hour -= 12;
122         long twelveHoursBefore = t.toMillis(false);
123         t.hour += 12;
124         long twelveHoursAfter = t.toMillis(false);
125         t.hour = 0;
126         t.minute = 0;
127         long midnightBefore = t.toMillis(false);
128         t.monthDay++;
129         long midnightAfter = t.toMillis(false);
130 
131         long nowMillis = System.currentTimeMillis();
132         t.set(nowMillis);
133         t.second = 0;
134         nowMillis = t.normalize(false);
135 
136         // Choose the display mode
137         choose_display: {
138             if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
139                     || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
140                 display = SHOW_TIME;
141                 break choose_display;
142             }
143             // Else, show month day and year.
144             display = SHOW_MONTH_DAY_YEAR;
145             break choose_display;
146         }
147 
148         // Choose the format
149         DateFormat format;
150         if (display == mLastDisplay && mLastFormat != null) {
151             // use cached format
152             format = mLastFormat;
153         } else {
154             switch (display) {
155                 case SHOW_TIME:
156                     format = getTimeFormat();
157                     break;
158                 case SHOW_MONTH_DAY_YEAR:
159                     format = getDateFormat();
160                     break;
161                 default:
162                     throw new RuntimeException("unknown display value: " + display);
163             }
164             mLastFormat = format;
165         }
166 
167         // Set the text
168         String text = format.format(mTime);
169         setText(text);
170 
171         // Schedule the next update
172         if (display == SHOW_TIME) {
173             // Currently showing the time, update at the later of twelve hours after or midnight.
174             mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
175         } else {
176             // Currently showing the date
177             if (mTimeMillis < nowMillis) {
178                 // If the time is in the past, don't schedule an update
179                 mUpdateTimeMillis = 0;
180             } else {
181                 // If hte time is in the future, schedule one at the earlier of twelve hours
182                 // before or midnight before.
183                 mUpdateTimeMillis = twelveHoursBefore < midnightBefore
184                         ? twelveHoursBefore : midnightBefore;
185             }
186         }
187         if (false) {
188             Log.d(TAG, "update needed for '" + time + "' at '" + new Date(mUpdateTimeMillis)
189                     + "' - text=" + text);
190         }
191 
192         long finish = System.nanoTime();
193     }
194 
195     private DateFormat getTimeFormat() {
196         return android.text.format.DateFormat.getTimeFormat(getContext());
197     }
198 
199     private DateFormat getDateFormat() {
200         String format = Settings.System.getString(getContext().getContentResolver(),
201                 Settings.System.DATE_FORMAT);
202         if (format == null || "".equals(format)) {
203             return DateFormat.getDateInstance(DateFormat.SHORT);
204         } else {
205             try {
206                 return new SimpleDateFormat(format);
207             } catch (IllegalArgumentException e) {
208                 // If we tried to use a bad format string, fall back to a default.
209                 return DateFormat.getDateInstance(DateFormat.SHORT);
210             }
211         }
212     }
213 
214     void clearFormatAndUpdate() {
215         mLastFormat = null;
216         update();
217     }
218 
219     private static class ReceiverInfo {
220         private final ArrayList<DateTimeView> mAttachedViews = new ArrayList<DateTimeView>();
221         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
222             @Override
223             public void onReceive(Context context, Intent intent) {
224                 String action = intent.getAction();
225                 if (Intent.ACTION_TIME_TICK.equals(action)) {
226                     if (System.currentTimeMillis() < getSoonestUpdateTime()) {
227                         // The update() function takes a few milliseconds to run because of
228                         // all of the time conversions it needs to do, so we can't do that
229                         // every minute.
230                         return;
231                     }
232                 }
233                 // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
234                 updateAll();
235             }
236         };
237 
238         private final ContentObserver mObserver = new ContentObserver(new Handler()) {
239             @Override
240             public void onChange(boolean selfChange) {
241                 updateAll();
242             }
243         };
244 
245         public void addView(DateTimeView v) {
246             final boolean register = mAttachedViews.isEmpty();
247             mAttachedViews.add(v);
248             if (register) {
249                 register(v.getContext().getApplicationContext());
250             }
251         }
252 
253         public void removeView(DateTimeView v) {
254             mAttachedViews.remove(v);
255             if (mAttachedViews.isEmpty()) {
256                 unregister(v.getContext().getApplicationContext());
257             }
258         }
259 
260         void updateAll() {
261             final int count = mAttachedViews.size();
262             for (int i = 0; i < count; i++) {
263                 mAttachedViews.get(i).clearFormatAndUpdate();
264             }
265         }
266 
267         long getSoonestUpdateTime() {
268             long result = Long.MAX_VALUE;
269             final int count = mAttachedViews.size();
270             for (int i = 0; i < count; i++) {
271                 final long time = mAttachedViews.get(i).mUpdateTimeMillis;
272                 if (time < result) {
273                     result = time;
274                 }
275             }
276             return result;
277         }
278 
279         void register(Context context) {
280             final IntentFilter filter = new IntentFilter();
281             filter.addAction(Intent.ACTION_TIME_TICK);
282             filter.addAction(Intent.ACTION_TIME_CHANGED);
283             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
284             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
285             context.registerReceiver(mReceiver, filter);
286 
287             final Uri uri = Settings.System.getUriFor(Settings.System.DATE_FORMAT);
288             context.getContentResolver().registerContentObserver(uri, true, mObserver);
289         }
290 
291         void unregister(Context context) {
292             context.unregisterReceiver(mReceiver);
293             context.getContentResolver().unregisterContentObserver(mObserver);
294         }
295     }
296 }
297