Home | History | Annotate | Download | only in widget
      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 
     69     public DateTimeView(Context context) {
     70         super(context);
     71     }
     72 
     73     public DateTimeView(Context context, AttributeSet attrs) {
     74         super(context, attrs);
     75     }
     76 
     77     @Override
     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
     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
     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 
    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