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