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.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 
     66     public DateTimeView(Context context) {
     67         super(context);
     68     }
     69 
     70     public DateTimeView(Context context, AttributeSet attrs) {
     71         super(context, attrs);
     72     }
     73 
     74     @Override
     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
     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
     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 
    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