Add DateTimeView, a widget that shows a time or the date depending on the
current time.  Use that for notifications instead of a TextView that
doesn't ever update.

BUG 1563917
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index be5a7d3..4d72f73 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -443,11 +443,7 @@
             contentView.setTextViewText(com.android.internal.R.id.text, contentText);
         }
         if (this.when != 0) {
-            Date date = new Date(when);
-            CharSequence str = 
-                DateUtils.isToday(when) ? DateFormat.getTimeFormat(context).format(date)
-                    : DateFormat.getDateFormat(context).format(date);
-            contentView.setTextViewText(com.android.internal.R.id.time, str);
+            contentView.setLong(com.android.internal.R.id.time, "setTime", when);
         }
 
         this.contentView = contentView;
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
new file mode 100644
index 0000000..9067e26
--- /dev/null
+++ b/core/java/android/widget/DateTimeView.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.widget.TextView;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+//
+// TODO
+// - listen for the next threshold time to update the view.
+// - listen for date format pref changed
+// - put the AM/PM in a smaller font
+//
+
+/**
+ * Displays a given time in a convenient human-readable foramt.
+ *
+ * @hide
+ */
+@RemoteView
+public class DateTimeView extends TextView {
+    private static final String TAG = "DateTimeView";
+
+    private static final long TWELVE_HOURS_IN_MINUTES = 12 * 60;
+    private static final long TWENTY_FOUR_HOURS_IN_MILLIS = 24 * 60 * 60 * 1000;
+
+    private static final int SHOW_TIME = 0;
+    private static final int SHOW_MONTH_DAY_YEAR = 1;
+
+    Date mTime;
+    long mTimeMillis;
+
+    int mLastDisplay = -1;
+    DateFormat mLastFormat;
+
+    private boolean mAttachedToWindow;
+    private long mUpdateTimeMillis;
+
+    public DateTimeView(Context context) {
+        super(context);
+    }
+
+    public DateTimeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onDetachedFromWindow();
+        registerReceivers();
+        mAttachedToWindow = true;
+    }
+        
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        unregisterReceivers();
+        mAttachedToWindow = false;
+    }
+
+    @android.view.RemotableViewMethod
+    public void setTime(long time) {
+        Time t = new Time();
+        t.set(time);
+        t.second = 0;
+        mTimeMillis = t.toMillis(false);
+        mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
+        update();
+    }
+
+    void update() {
+        if (mTime == null) {
+            return;
+        }
+
+        long start = System.nanoTime();
+
+        int display;
+        Date time = mTime;
+
+        Time t = new Time();
+        t.set(mTimeMillis);
+        t.second = 0;
+
+        t.hour -= 12;
+        long twelveHoursBefore = t.toMillis(false);
+        t.hour += 12;
+        long twelveHoursAfter = t.toMillis(false);
+        t.hour = 0;
+        t.minute = 0;
+        long midnightBefore = t.toMillis(false);
+        t.monthDay++;
+        long midnightAfter = t.toMillis(false);
+
+        long nowMillis = System.currentTimeMillis();
+        t.set(nowMillis);
+        t.second = 0;
+        nowMillis = t.normalize(false);
+
+        // Choose the display mode
+        choose_display: {
+            if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
+                    || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
+                display = SHOW_TIME;
+                break choose_display;
+            }
+            // Else, show month day and year.
+            display = SHOW_MONTH_DAY_YEAR;
+            break choose_display;
+        }
+
+        // Choose the format
+        DateFormat format;
+        if (display == mLastDisplay && mLastFormat != null) {
+            // use cached format
+            format = mLastFormat;
+        } else {
+            switch (display) {
+                case SHOW_TIME:
+                    format = getTimeFormat();
+                    break;
+                case SHOW_MONTH_DAY_YEAR:
+                    format = getDateFormat();
+                    break;
+                default:
+                    throw new RuntimeException("unknown display value: " + display);
+            }
+            mLastFormat = format;
+        }
+
+        // Set the text
+        String text = format.format(mTime);
+        setText(text);
+
+        // Schedule the next update
+        if (display == SHOW_TIME) {
+            // Currently showing the time, update at the later of twelve hours after or midnight.
+            mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
+        } else {
+            // Currently showing the date
+            if (mTimeMillis < nowMillis) {
+                // If the time is in the past, don't schedule an update
+                mUpdateTimeMillis = 0;
+            } else {
+                // If hte time is in the future, schedule one at the earlier of twelve hours
+                // before or midnight before.
+                mUpdateTimeMillis = twelveHoursBefore < midnightBefore
+                        ? twelveHoursBefore : midnightBefore;
+            }
+        }
+        if (false) {
+            Log.d(TAG, "update needed for '" + time + "' at '" + new Date(mUpdateTimeMillis)
+                    + "' - text=" + text);
+        }
+
+        long finish = System.nanoTime();
+    }
+
+    private DateFormat getTimeFormat() {
+        int res;
+        Context context = getContext();
+        if (android.text.format.DateFormat.is24HourFormat(context)) {
+            res = R.string.twenty_four_hour_time_format;
+        } else {
+            res = R.string.twelve_hour_time_format;
+        }
+        String format = context.getString(res);
+        return new SimpleDateFormat(format);
+    }
+
+    private DateFormat getDateFormat() {
+        String format = Settings.System.getString(getContext().getContentResolver(),
+                Settings.System.DATE_FORMAT);
+        if ("".equals(format)) {
+            return DateFormat.getDateInstance(DateFormat.SHORT);
+        } else {
+            return new SimpleDateFormat(format);
+        }
+    }
+
+    private void registerReceivers() {
+        Context context = getContext();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_TIME_TICK);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        context.registerReceiver(mBroadcastReceiver, filter);
+
+        Uri uri = Settings.System.getUriFor(Settings.System.DATE_FORMAT);
+        context.getContentResolver().registerContentObserver(uri, true, mContentObserver);
+    }
+
+    private void unregisterReceivers() {
+        Context context = getContext();
+        context.unregisterReceiver(mBroadcastReceiver);
+        context.getContentResolver().unregisterContentObserver(mContentObserver);
+    }
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_TIME_TICK.equals(action)) {
+                if (System.currentTimeMillis() < mUpdateTimeMillis) {
+                    // The update() function takes a few milliseconds to run because of
+                    // all of the time conversions it needs to do, so we can't do that
+                    // every minute.
+                    return;
+                }
+            }
+            // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
+            mLastFormat = null;
+            update();
+        }
+    };
+
+    private ContentObserver mContentObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            mLastFormat = null;
+            update();
+        }
+    };
+}
diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml
index 2f7036f..c3aa041 100644
--- a/core/res/res/layout/status_bar_latest_event_content.xml
+++ b/core/res/res/layout/status_bar_latest_event_content.xml
@@ -45,7 +45,7 @@
             android:textSize="14sp"
             android:paddingLeft="4dp"
             />
-        <TextView android:id="@+id/time"
+        <android.widget.DateTimeView android:id="@+id/time"
             android:layout_marginLeft="4dp"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index f2ddd0f..ffc2cbc 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -121,6 +121,20 @@
             }
         },
 
+        new Test("Times") {
+            public void run()
+            {
+                long now = System.currentTimeMillis();
+
+                timeNotification(7, "24 hours from now", now+(1000*60*60*24));
+                timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000));
+                timeNotification(5, "12 hours from now", now+(1000*60*60*12));
+                timeNotification(4, "now", now);
+                timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000)));
+                timeNotification(2, "12 hours ago", now-(1000*60*60*12));
+                timeNotification(1, "24 hours ago", now-(1000*60*60*24));
+            }
+        },
         new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] {
                 new Runnable() {
                     public void run() {
@@ -590,5 +604,12 @@
             mHandler.postDelayed(mRunnable, mPause);
         }
     }
+
+    void timeNotification(int n, String label, long time) {
+        mNM.notify(n, new Notification(NotificationTestList.this,
+                    R.drawable.ic_statusbar_missedcall, null,
+                    time, label, "" + new java.util.Date(time), null));
+
+    }
 }