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