Fix bug #2589483 and implemented animated pagination in the widget

 - Fixed a number of refresh issues in the widget
 - Implemented animated pagination for overflowing events
 - Updated corresponding unit tests for new behavior

Change-Id: I8883611be476d61d655c4427b8b9ba86092b0cbb
diff --git a/res/anim/slide_in.xml b/res/anim/slide_in.xml
new file mode 100644
index 0000000..72fc56b
--- /dev/null
+++ b/res/anim/slide_in.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromYDelta="100%p" android:toYDelta="0"
+        android:duration="@integer/slide_transition_duration"/>
+    <alpha
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@integer/slide_transition_duration" />
+</set>
\ No newline at end of file
diff --git a/res/anim/slide_out.xml b/res/anim/slide_out.xml
new file mode 100644
index 0000000..52681a7
--- /dev/null
+++ b/res/anim/slide_out.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromYDelta="0" android:toYDelta="-100%p"
+        android:duration="@integer/slide_transition_duration"/>
+    <alpha
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromAlpha="1.0" android:toAlpha="0"
+        android:duration="@integer/slide_transition_duration" />
+</set>
\ No newline at end of file
diff --git a/res/layout/agenda_appwidget.xml b/res/layout/agenda_appwidget.xml
index 8f4c922..826eac9 100644
--- a/res/layout/agenda_appwidget.xml
+++ b/res/layout/agenda_appwidget.xml
@@ -59,6 +59,25 @@
             android:singleLine="true" />
     </LinearLayout>
 
+    <!-- Container to show only a single page -->
+    <FrameLayout
+        android:id="@+id/single_page"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    </FrameLayout>
+
+    <!-- Flipper for event pages -->
+    <ViewFlipper
+        android:id="@+id/page_flipper"
+        android:autoStart="true"
+        android:flipInterval="@integer/flip_interval"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:inAnimation="@anim/slide_in"
+        android:outAnimation="@anim/slide_out"
+        android:animateFirstView="false">
+    </ViewFlipper>
+
     <!-- No Event -->
     <TextView
         android:id="@+id/no_events"
@@ -72,43 +91,6 @@
         android:textColor="@color/appwidget_no_events"
         android:text="@string/gadget_no_events" />
 
-    <!-- Event #1 -->
-    <TextView
-        android:id="@+id/when1"
-        style="@style/TextAppearance.WidgetWhen" />
 
-    <TextView
-        android:id="@+id/where1"
-        style="@style/TextAppearance.WidgetWhere" />
 
-    <TextView
-        android:id="@+id/title1"
-        android:layout_marginBottom="6dip"
-        style="@style/TextAppearance.WidgetTitle" />
-
-    <!-- Event #2 -->
-    <TextView
-        android:id="@+id/when2"
-        style="@style/TextAppearance.WidgetWhen" />
-
-    <TextView
-        android:id="@+id/where2"
-        style="@style/TextAppearance.WidgetWhere" />
-
-    <TextView
-        android:id="@+id/title2"
-        android:layout_marginBottom="6dip"
-        style="@style/TextAppearance.WidgetTitle" />
-
-    <!-- Conflict banner -->
-    <TextView
-        android:id="@+id/conflict_portrait"
-        android:textStyle="bold"
-        style="@style/TextAppearance.WidgetConflict" />
-
-    <TextView
-        android:id="@+id/conflict_landscape"
-        android:visibility="gone"
-        android:layout_width="0dp"
-        android:layout_height="0dp" />
 </LinearLayout>
diff --git a/res/layout/page_appwidget.xml b/res/layout/page_appwidget.xml
new file mode 100644
index 0000000..eabd43a
--- /dev/null
+++ b/res/layout/page_appwidget.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/page_appwidget"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:addStatesFromChildren="true">
+
+     <!-- Event #1 -->
+    <TextView
+        android:id="@+id/when1"
+        style="@style/TextAppearance.WidgetWhen" />
+
+    <TextView
+        android:id="@+id/where1"
+        style="@style/TextAppearance.WidgetWhere" />
+
+    <TextView
+        android:id="@+id/title1"
+        android:layout_marginBottom="6dip"
+        style="@style/TextAppearance.WidgetTitle" />
+
+    <!-- Event #2 -->
+    <TextView
+        android:id="@+id/when2"
+        style="@style/TextAppearance.WidgetWhen" />
+
+    <TextView
+        android:id="@+id/where2"
+        style="@style/TextAppearance.WidgetWhere" />
+
+    <TextView
+        android:id="@+id/title2"
+        android:layout_marginBottom="6dip"
+        style="@style/TextAppearance.WidgetTitle" />
+
+    <!-- Page count -->
+    <TextView
+        android:id="@+id/page_count"
+        android:gravity="right|bottom"
+        android:layout_gravity="bottom"
+        android:layout_weight="1"
+        style="@style/TextAppearance.WidgetPageCount">
+    </TextView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ea98617..ac4c3f1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,5 +23,5 @@
     <color name="appwidget_conflict">#ff000000</color>
 
     <color name="appwidget_no_events">#bb000000</color>
-</resources>
-
+    <color name="appwidget_page_count">#ff666666</color>
+</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..ff94dd5
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <integer name="flip_interval">5000</integer>
+    <integer name="slide_transition_duration">600</integer>
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9435360..4765299 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -39,9 +39,15 @@
     <!-- Caption to show on gadget when there are no upcoming calendar events -->
     <string name="gadget_no_events">No upcoming calendar events</string>
 
-    <!-- Text to show on gadget when an events starts on the next day -->
+    <!-- Text to show on gadget when an event starts on the next day -->
     <string name="tomorrow">Tomorrow</string>
 
+    <!-- Text to show on gadget when an event is currently in progress -->
+    <string name="in_progress">in progress</string>
+
+    <!-- Text to show on gadget when an all-day event is in progress -->
+    <string name="today">Today</string>
+
     <!-- Caption on secret calendar info -->
     <string name="calendar_info">Calendar info</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9799b12..c2a1b3a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -72,4 +72,18 @@
         <item name="android:singleLine">true</item>
         <item name="android:textColor">@color/appwidget_conflict</item>
     </style>
+
+    <style name="TextAppearance.WidgetPageCount">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">5dip</item>
+        <item name="android:layout_marginRight">5dip</item>
+
+        <item name="android:layout_marginTop">3dip</item>
+        <item name="android:layout_marginBottom">7dip</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/appwidget_when</item>
+        <item name="android:singleLine">true</item>
+    </style>
 </resources>
diff --git a/src/com/android/providers/calendar/CalendarAppWidgetService.java b/src/com/android/providers/calendar/CalendarAppWidgetService.java
index f5e426b..29644f6 100644
--- a/src/com/android/providers/calendar/CalendarAppWidgetService.java
+++ b/src/com/android/providers/calendar/CalendarAppWidgetService.java
@@ -18,6 +18,8 @@
 
 import com.google.common.annotations.VisibleForTesting;
 
+import com.android.providers.calendar.CalendarAppWidgetService.CalendarAppWidgetModel.EventInfo;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -40,7 +42,9 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 import java.util.TimeZone;
 
@@ -49,6 +53,10 @@
     private static final String TAG = "CalendarAppWidgetService";
     private static final boolean LOGD = false;
 
+    /* TODO query doesn't handle all-day events properly, we should fix this in
+     * the provider in a manner similar to how it is handled in Event.loadEvents
+     * in the Calendar application.
+     */
     private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
             + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
             + Instances.END_MINUTE + " ASC LIMIT 10";
@@ -92,19 +100,17 @@
 
         EventInfo[] eventInfos;
 
-        int visibConflictPortrait; // Visibility value for conflictPortrait textview
-        String conflictPortrait;
-        int visibConflictLandscape; // Visibility value for conflictLandscape textview
-        String conflictLandscape;
-
         public CalendarAppWidgetModel() {
-            eventInfos = new EventInfo[2];
-            eventInfos[0] = new EventInfo();
-            eventInfos[1] = new EventInfo();
+            this(2);
+        }
 
+        public CalendarAppWidgetModel(int size) {
+            // we round up to the nearest even integer
+            eventInfos = new EventInfo[2 * ((size + 1) / 2)];
+            for (int i = 0; i < eventInfos.length; i++) {
+                eventInfos[i] = new EventInfo();
+            }
             visibNoEvents = View.GONE;
-            visibConflictPortrait = View.GONE;
-            visibConflictLandscape = View.GONE;
         }
 
         class EventInfo {
@@ -209,14 +215,6 @@
             StringBuilder builder = new StringBuilder();
             builder.append("\nCalendarAppWidgetModel [eventInfos=");
             builder.append(Arrays.toString(eventInfos));
-            builder.append(", visibConflictLandscape=");
-            builder.append(visibConflictLandscape);
-            builder.append(", conflictLandscape=");
-            builder.append(conflictLandscape);
-            builder.append(", visibConflictPortrait=");
-            builder.append(visibConflictPortrait);
-            builder.append(", conflictPortrait=");
-            builder.append(conflictPortrait);
             builder.append(", visibNoEvents=");
             builder.append(visibNoEvents);
             builder.append(", dayOfMonth=");
@@ -307,7 +305,7 @@
                     shouldUpdate = events.watchFound;
                 }
 
-                if (events.primaryCount == 0) {
+                if (events.markedIds.isEmpty()) {
                     views = getAppWidgetNoEvents(context);
                 } else if (shouldUpdate) {
                     views = getAppWidgetUpdate(context, cursor, events);
@@ -402,8 +400,8 @@
      */
     private long calculateUpdateTime(Cursor cursor, MarkedEvents events) {
         long result = -1;
-        if (events.primaryRow != -1) {
-            cursor.moveToPosition(events.primaryRow);
+        if (!events.markedIds.isEmpty()) {
+            cursor.moveToPosition(events.markedIds.get(0));
             long start = cursor.getLong(INDEX_BEGIN);
             long end = cursor.getLong(INDEX_END);
             boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
@@ -437,15 +435,14 @@
 
     /**
      * Calculate flipping point for the given event; when we should hide this
-     * event and show the next one. This is 15 minutes into the event or half
-     * way into the event whichever is earlier.
+     * event and show the next one. This is defined as the end time of the
+     * event.
      *
      * @param start Event start time in local timezone.
      * @param end Event end time in local timezone.
      */
     static private long getEventFlip(Cursor cursor, long start, long end, boolean allDay) {
-        long duration = end - start;
-        return start + Math.min(DateUtils.MINUTE_IN_MILLIS * 15, duration / 2);
+        return end;
     }
 
     /**
@@ -457,15 +454,8 @@
      */
     private void setNoEventsVisible(RemoteViews views, boolean noEvents) {
         views.setViewVisibility(R.id.no_events, noEvents ? View.VISIBLE : View.GONE);
-
-        views.setViewVisibility(R.id.when1, View.GONE);
-        views.setViewVisibility(R.id.where1, View.GONE);
-        views.setViewVisibility(R.id.title1, View.GONE);
-        views.setViewVisibility(R.id.when2, View.GONE);
-        views.setViewVisibility(R.id.where2, View.GONE);
-        views.setViewVisibility(R.id.title2, View.GONE);
-        views.setViewVisibility(R.id.conflict_landscape, View.GONE);
-        views.setViewVisibility(R.id.conflict_portrait, View.GONE);
+        views.setViewVisibility(R.id.page_flipper, View.GONE);
+        views.setViewVisibility(R.id.single_page, View.GONE);
     }
 
     /**
@@ -482,15 +472,10 @@
         long currentTime = System.currentTimeMillis();
         CalendarAppWidgetModel model = buildAppWidgetModel(context, cursor, events, currentTime);
 
-        applyModelToView(model, views);
+        applyModelToView(context, model, views);
 
         // Clicking on the widget launches Calendar
-        long startTime;
-        if (events.primaryAllDay) {
-            startTime = currentTime;
-        } else {
-            startTime = events.primaryTime;
-        }
+        long startTime = Math.max(currentTime, events.firstTime);
 
         PendingIntent pendingIntent = getLaunchPendingIntent(context, startTime);
         views.setOnClickPendingIntent(R.id.agenda_appwidget, pendingIntent);
@@ -498,29 +483,62 @@
         return views;
     }
 
-    private void applyModelToView(CalendarAppWidgetModel model, RemoteViews views) {
+    private void applyModelToView(Context context, CalendarAppWidgetModel model, RemoteViews views) {
         views.setTextViewText(R.id.day_of_week, model.dayOfWeek);
         views.setTextViewText(R.id.day_of_month, model.dayOfMonth);
         views.setViewVisibility(R.id.no_events, model.visibNoEvents);
-        if (model.visibNoEvents == View.GONE) {
-            updateTextView(views, R.id.when1, model.eventInfos[0].visibWhen,
-                    model.eventInfos[0].when);
-            updateTextView(views, R.id.where1, model.eventInfos[0].visibWhere,
-                    model.eventInfos[0].where);
-            updateTextView(views, R.id.title1, model.eventInfos[0].visibTitle,
-                    model.eventInfos[0].title);
-            updateTextView(views, R.id.when2, model.eventInfos[1].visibWhen,
-                    model.eventInfos[1].when);
-            updateTextView(views, R.id.where2, model.eventInfos[1].visibWhere,
-                    model.eventInfos[1].where);
-            updateTextView(views, R.id.title2, model.eventInfos[1].visibTitle,
-                    model.eventInfos[1].title);
 
-            updateTextView(views, R.id.conflict_portrait, model.visibConflictPortrait,
-                    model.conflictPortrait);
-            updateTextView(views, R.id.conflict_landscape, model.visibConflictLandscape,
-                    model.conflictLandscape);
+        // Make sure we have a clean slate first
+        views.removeAllViews(R.id.page_flipper);
+        views.removeAllViews(R.id.single_page);
+
+        // If we don't have any events, just hide the relevant views and return
+        if (model.visibNoEvents != View.GONE) {
+            views.setViewVisibility(R.id.page_flipper, View.GONE);
+            views.setViewVisibility(R.id.single_page, View.GONE);
+            return;
         }
+
+        // Luckily, length of this array is guaranteed to be even
+        int pages = model.eventInfos.length / 2;
+
+        // We use a separate container for the case of only one page to prevent
+        // a ViewFlipper from repeatedly animating one view
+        if (pages > 1) {
+            views.setViewVisibility(R.id.page_flipper, View.VISIBLE);
+            views.setViewVisibility(R.id.single_page, View.GONE);
+        } else {
+            views.setViewVisibility(R.id.single_page, View.VISIBLE);
+            views.setViewVisibility(R.id.page_flipper, View.GONE);
+        }
+
+        // Iterate two at a time through the events and populate the views
+        for (int i = 0; i < model.eventInfos.length; i += 2) {
+            RemoteViews pageViews = new RemoteViews(context.getPackageName(),
+                    R.layout.page_appwidget);
+            EventInfo e1 = model.eventInfos[i];
+            EventInfo e2 = model.eventInfos[i + 1];
+
+            updateTextView(pageViews, R.id.when1, e1.visibWhen, e1.when);
+            updateTextView(pageViews, R.id.where1, e1.visibWhere, e1.where);
+            updateTextView(pageViews, R.id.title1, e1.visibTitle, e1.title);
+            updateTextView(pageViews, R.id.when2, e2.visibWhen, e2.when);
+            updateTextView(pageViews, R.id.where2, e2.visibWhere, e2.where);
+            updateTextView(pageViews, R.id.title2, e2.visibTitle, e2.title);
+
+            if (pages > 1) {
+                views.addView(R.id.page_flipper, pageViews);
+                updateTextView(pageViews, R.id.page_count, View.VISIBLE,
+                        makePageCount((i / 2) + 1, pages));
+            } else {
+                views.addView(R.id.single_page, pageViews);
+            }
+        }
+
+    }
+
+    static String makePageCount(int current, int total) {
+        return Integer.toString(current) + " / " + Integer.toString(total);
     }
 
     static void updateTextView(RemoteViews views, int id, int visibility, String string) {
@@ -532,7 +550,8 @@
 
     static CalendarAppWidgetModel buildAppWidgetModel(Context context, Cursor cursor,
             MarkedEvents events, long currentTime) {
-        CalendarAppWidgetModel model = new CalendarAppWidgetModel();
+        int eventCount = events.markedIds.size();
+        CalendarAppWidgetModel model = new CalendarAppWidgetModel(eventCount);
         Time time = new Time();
         time.set(currentTime);
         time.monthDay++;
@@ -550,99 +569,81 @@
         model.dayOfWeek = dayOfWeek;
         model.dayOfMonth = Integer.toString(time.monthDay);
 
-        // Fill primary event details
-        cursor.moveToPosition(events.primaryRow);
-        boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
-        populateEvent(context, cursor, model, time, 0, true, startOfNextDay);
-
-        // Conflicts
-        int conflictCountLandscape = events.primaryCount - 1;
-        if (conflictCountLandscape > 0) {
-            model.conflictLandscape = context.getResources().getQuantityString(
-                    R.plurals.gadget_more_events, conflictCountLandscape, conflictCountLandscape);
-            model.visibConflictLandscape = View.VISIBLE;
-        } else {
-            model.visibConflictLandscape = View.GONE;
+        int i = 0;
+        for (Integer id : events.markedIds) {
+            populateEvent(context, cursor, id, model, time, i, true, startOfNextDay, currentTime);
+            i++;
         }
 
-        int conflictCountPortrait = 0;
-        if (events.primaryCount > 2) {
-            conflictCountPortrait = events.primaryCount - 1;
-        } else if (events.primaryCount == 1 && events.secondaryCount > 1
-                && cursor.moveToPosition(events.secondaryRow)) {
-            // Show conflict string for the secondary time slot if there is only one event
-            // in the primary time slot.
-            populateEvent(context, cursor, model, time, 1, false, startOfNextDay);
-            conflictCountPortrait = events.secondaryCount;
-        }
-
-        if (conflictCountPortrait != 0) {
-            model.conflictPortrait = context.getResources().getQuantityString(
-                    R.plurals.gadget_more_events, conflictCountPortrait, conflictCountPortrait);
-            model.visibConflictPortrait = View.VISIBLE;
-        } else {
-            model.visibConflictPortrait = View.GONE;
-
-            // Fill secondary event details
-            int secondaryRow = -1;
-            if (events.primaryCount == 2) {
-                secondaryRow = events.primaryConflictRow;
-            } else if (events.primaryCount == 1) {
-                secondaryRow = events.secondaryRow;
-            }
-
-            if (secondaryRow != -1 && cursor.moveToPosition(secondaryRow)) {
-                populateEvent(context, cursor, model, time, 1, true, startOfNextDay);
-            }
-        }
         return model;
     }
 
-    static private void populateEvent(Context context, Cursor cursor, CalendarAppWidgetModel model,
-            Time recycle, int eventIndex, boolean showTitleLocation, long startOfNextDay) {
+    /**
+     * Pulls the information for a single event from the cursor and populates
+     * the corresponding model object with the data.
+     *
+     * @param context a Context to use for accessing resources
+     * @param cursor the cursor to retrieve the data from
+     * @param rowId the ID of the row to retrieve
+     * @param model the model object to populate
+     * @param recycle a Time instance to recycle
+     * @param eventIndex which event index in the model to populate
+     * @param showTitleLocation whether or not to show the title and location
+     * @param startOfNextDay the beginning of the next day
+     * @param currentTime the current time
+     */
+    static private void populateEvent(Context context, Cursor cursor, int rowId,
+            CalendarAppWidgetModel model, Time recycle, int eventIndex,
+            boolean showTitleLocation, long startOfNextDay, long currentTime) {
+        cursor.moveToPosition(rowId);
 
         // When
         boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
         long start = cursor.getLong(INDEX_BEGIN);
+        long end = cursor.getLong(INDEX_END);
         if (allDay) {
             start = convertUtcToLocal(recycle, start);
+            end = convertUtcToLocal(recycle, end);
         }
 
+        boolean eventIsInProgress = start <= currentTime && end > currentTime;
         boolean eventIsToday = start < startOfNextDay;
-        boolean eventIsTomorrow = !eventIsToday
+        boolean eventIsTomorrow = !eventIsToday && !eventIsInProgress
                 && (start < (startOfNextDay + DateUtils.DAY_IN_MILLIS));
 
-        String whenString = "";
-
-        if (!(allDay && eventIsTomorrow)) {
+        // Compute a human-readable string for the start time of the event
+        String whenString;
+        if (eventIsInProgress && allDay) {
+            // All day events for the current day display as just "Today"
+            whenString = context.getString(R.string.today);
+        } else if (eventIsTomorrow && allDay) {
+            // All day events for the next day display as just "Tomorrow"
+            whenString = context.getString(R.string.tomorrow);
+        } else {
             int flags = DateUtils.FORMAT_ABBREV_ALL;
-
             if (allDay) {
                 flags |= DateUtils.FORMAT_UTC;
-                if (!eventIsTomorrow) {
-                    flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
-                }
             } else {
                 flags |= DateUtils.FORMAT_SHOW_TIME;
-
                 if (DateFormat.is24HourFormat(context)) {
                     flags |= DateUtils.FORMAT_24HOUR;
                 }
-
-                // Show day or week if different from today
-                if (!eventIsTomorrow && !eventIsToday) {
-                    flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
-                }
+            }
+            // Show day of the week if not today or tomorrow
+            if (!eventIsTomorrow && !eventIsToday) {
+                flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
             }
             whenString = DateUtils.formatDateRange(context, start, start, flags);
+            if (eventIsTomorrow) {
+                whenString += (", ");
+                whenString += context.getString(R.string.tomorrow);
+            } else if (eventIsInProgress) {
+                whenString += " (";
+                whenString += context.getString(R.string.in_progress);
+                whenString += ")";
+            }
         }
 
-        if (eventIsTomorrow) {
-            if (!allDay) {
-                whenString += (", ");
-            }
-            whenString += context.getString(R.string.tomorrow);
-        }
         model.eventInfos[eventIndex].when = whenString;
         model.eventInfos[eventIndex].visibWhen = View.VISIBLE;
 
@@ -713,14 +714,38 @@
     }
 
     static class MarkedEvents {
+
+        /**
+         * The row IDs of all events marked for display
+         */
+        List<Integer> markedIds = new ArrayList<Integer>(10);
+
+        /**
+         * The start time of the first marked event
+         */
+        long firstTime = -1;
+
+        /** The number of events currently in progress */
+        int inProgressCount = 0; // Number of events with same start time as the primary evt.
+
+        /** The start time of the next upcoming event */
         long primaryTime = -1;
-        int primaryRow = -1;
-        int primaryConflictRow = -1;
-        int primaryCount = 0; // Number of events with same start time as the primary evt.
-        boolean primaryAllDay = false;
-        long secondaryTime = -1;
-        int secondaryRow = -1;
-        int secondaryCount = 0; // Number of events with same start time as the secondary evt.
+
+        /**
+         * The number of events that share the same start time as the next
+         * upcoming event
+         */
+        int primaryCount = 0; // Number of events with same start time as the secondary evt.
+
+        /** The start time of the next next upcoming event */
+        long secondaryTime = 1;
+
+        /**
+         * The number of events that share the same start time as the next next
+         * upcoming event.
+         */
+        int secondaryCount = 0;
+
         boolean watchFound = false;
     }
 
@@ -746,6 +771,7 @@
             long eventId = cursor.getLong(INDEX_EVENT_ID);
             long start = cursor.getLong(INDEX_BEGIN);
             long end = cursor.getLong(INDEX_END);
+
             boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
 
             if (LOGD) {
@@ -759,6 +785,8 @@
                 end = convertUtcToLocal(recycle, end);
             }
 
+            boolean inProgress = now < end && now > start;
+
             // Skip events that have already passed their flip times
             long eventFlip = getEventFlip(cursor, start, end, allDay);
             if (LOGD) Log.d(TAG, "Calculated flip time " + formatDebugTime(eventFlip, now));
@@ -771,29 +799,52 @@
                 events.watchFound = true;
             }
 
-            if (events.primaryRow == -1) {
-                // Found first event
-                events.primaryRow = row;
-                events.primaryTime = start;
-                events.primaryAllDay = allDay;
-                events.primaryCount = 1;
-            } else if (events.primaryTime == start) {
-                // Found conflicting primary event
-                if (events.primaryConflictRow == -1) {
-                    events.primaryConflictRow = row;
+            /* Scan through the events with the following logic:
+             *   Rule #1 Show A) all the events that are in progress including
+             *     all day events and B) the next upcoming event and any events
+             *     with the same start time.
+             *
+             *   Rule #2 If there are no events in progress, show A) the next
+             *     upcoming event and B) any events with the same start time.
+             *
+             *   Rule #3 If no events start at the same time at A in rule 2,
+             *     show A) the next upcoming event and B) the following upcoming
+             *     event + any events with the same start time.
+             */
+            if (inProgress) {
+                // events for part A of Rule #1
+                events.markedIds.add(row);
+                events.inProgressCount++;
+                if (events.firstTime == -1) {
+                    events.firstTime = start;
                 }
-                events.primaryCount += 1;
-            } else if (events.secondaryRow == -1) {
-                // Found second event
-                events.secondaryRow = row;
-                events.secondaryTime = start;
-                events.secondaryCount = 1;
-            } else if (events.secondaryTime == start) {
-                // Found conflicting secondary event
-                events.secondaryCount += 1;
             } else {
-                // Nothing interesting about this event, so bail out
-                break;
+                if (events.primaryCount == 0) {
+                    // first upcoming event
+                    events.markedIds.add(row);
+                    events.primaryTime = start;
+                    events.primaryCount++;
+                    if (events.firstTime == -1) {
+                        events.firstTime = start;
+                    }
+                } else if (events.primaryTime == start) {
+                    // any events with same start time as first upcoming event
+                    events.markedIds.add(row);
+                    events.primaryCount++;
+                } else if (events.markedIds.size() == 1) {
+                    // only one upcoming event, so we take the next upcoming
+                    events.markedIds.add(row);
+                    events.secondaryTime = start;
+                    events.secondaryCount++;
+                } else if (events.secondaryCount > 0
+                        && events.secondaryTime == start) {
+                    // any events with same start time as next upcoming
+                    events.markedIds.add(row);
+                    events.secondaryCount++;
+                } else {
+                    // looks like we're done
+                    break;
+                }
             }
         }
         return events;
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index d65d437..2d5e0cc 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -1607,6 +1607,7 @@
                     mDbHelper.scheduleSync(account, false /* two-way sync */, calendarUrl);
                 }
                 id = mDbHelper.calendarsInsert(values);
+                triggerAppWidgetUpdate(id);
                 break;
             case ATTENDEES:
                 if (!values.containsKey(Attendees.EVENT_ID)) {
@@ -2604,6 +2605,11 @@
                 int result = mDb.update("Calendars", values, "_id=?",
                         new String[] {String.valueOf(id)});
 
+                if (result > 0) {
+                    // update the widget
+                    triggerAppWidgetUpdate(-1 /* changedEventId */);
+                }
+
                 return result;
             }
             case EVENTS:
@@ -2682,9 +2688,11 @@
                             Log.d(TAG, "updateInternal() changing event");
                         }
                         scheduleNextAlarm(false /* do not remove alarms */);
-                        triggerAppWidgetUpdate(id);
                     }
+
+                    triggerAppWidgetUpdate(id);
                 }
+
                 return result;
             }
             case ATTENDEES_ID: {
@@ -3431,6 +3439,9 @@
         } finally {
             mDb.endTransaction();
         }
+
+        // make sure the widget reflects the account changes
+        triggerAppWidgetUpdate(-1 /* changedEventId */);
     }
 
     /* package */ static boolean readBooleanQueryParameter(Uri uri, String name,
diff --git a/tests/src/com/android/providers/calendar/CalendarAppWidgetServiceTest.java b/tests/src/com/android/providers/calendar/CalendarAppWidgetServiceTest.java
index bef87a5..d473cca 100644
--- a/tests/src/com/android/providers/calendar/CalendarAppWidgetServiceTest.java
+++ b/tests/src/com/android/providers/calendar/CalendarAppWidgetServiceTest.java
@@ -33,8 +33,11 @@
     private static final String TAG = "CalendarAppWidgetService";
 
     final long now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST)
-    final long ONE_HOUR = 3600000;
-    final long TWO_HOURS = 7200000;
+    final long ONE_MINUTE = 60000;
+    final long ONE_HOUR = 60 * ONE_MINUTE;
+    final long HALF_HOUR = ONE_HOUR / 2;
+    final long TWO_HOURS = ONE_HOUR * 2;
+
     final String title = "Title";
     final String location = "Location";
 
@@ -60,8 +63,9 @@
 
     @SmallTest
     public void testGetAppWidgetModel_1Event() throws Exception {
-        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
         CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
 
         // Input
         // allDay, begin, end, title, location, eventId
@@ -81,15 +85,15 @@
         // Test
         MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
         CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
-                getTestContext(), cursor, events, now);
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
     }
 
     @SmallTest
     public void testGetAppWidgetModel_2StaggeredEvents() throws Exception {
-        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
         CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
 
         int i = 0;
         long tomorrow = now + DateUtils.DAY_IN_MILLIS;
@@ -124,7 +128,7 @@
         // Test
         MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
         CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
-                getTestContext(), cursor, events, now);
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
 
@@ -135,15 +139,15 @@
 
         // Test again
         events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
-        actual = CalendarAppWidgetService.buildAppWidgetModel(getTestContext(), cursor, events, now);
+        actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
     }
 
     @SmallTest
     public void testGetAppWidgetModel_2SameStartTimeEvents() throws Exception {
-        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
         CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
 
         int i = 0;
         // Expected Output
@@ -164,9 +168,6 @@
         expected.eventInfos[1].where = location + i;
         expected.eventInfos[1].title = title + i;
 
-        expected.conflictPortrait = null;
-        expected.conflictLandscape = "1 more event";
-        expected.visibConflictLandscape = View.VISIBLE;
 
         // Input
         // allDay, begin, end, title, location, eventId
@@ -179,7 +180,7 @@
         // Test
         MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
         CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
-                getTestContext(), cursor, events, now);
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
 
@@ -190,15 +191,15 @@
 
         // Test again
         events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
-        actual = CalendarAppWidgetService.buildAppWidgetModel(getTestContext(), cursor, events, now);
+        actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
     }
 
     @SmallTest
     public void testGetAppWidgetModel_1EventThen2SameStartTimeEvents() throws Exception {
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(3);
         MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
-        CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
 
         // Input
         int i = 0;
@@ -214,24 +215,31 @@
         expected.dayOfWeek = "FRI";
         i = 0;
         expected.visibNoEvents = View.GONE;
-        expected.eventInfos[0].visibWhen = View.VISIBLE;
-        expected.eventInfos[0].visibWhere = View.VISIBLE;
-        expected.eventInfos[0].visibTitle = View.VISIBLE;
-        expected.eventInfos[0].when = "2am";
-        expected.eventInfos[0].where = location + i;
-        expected.eventInfos[0].title = title + i;
-
-        expected.eventInfos[1].visibWhen = View.VISIBLE;
-        expected.eventInfos[1].when = "3am";
-
-        expected.visibConflictPortrait = View.VISIBLE;
-        expected.conflictPortrait = "2 more events";
-        expected.conflictLandscape = null;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "2am (in progress)";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
 
         // Test
         MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
         CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
-                getTestContext(), cursor, events, now);
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
     }
@@ -239,8 +247,8 @@
     @SmallTest
     public void testGetAppWidgetModel_3SameStartTimeEvents() throws Exception {
         final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(3);
         MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
-        CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
 
         int i = 0;
 
@@ -248,17 +256,29 @@
         expected.dayOfMonth = "1";
         expected.dayOfWeek = "FRI";
         expected.visibNoEvents = View.GONE;
-        expected.eventInfos[0].visibWhen = View.VISIBLE;
-        expected.eventInfos[0].visibWhere = View.VISIBLE;
-        expected.eventInfos[0].visibTitle = View.VISIBLE;
-        expected.eventInfos[0].when = "3am";
-        expected.eventInfos[0].where = location + i;
-        expected.eventInfos[0].title = title + i;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
 
-        expected.visibConflictPortrait = View.VISIBLE;
-        expected.visibConflictLandscape = View.VISIBLE;
-        expected.conflictPortrait = "2 more events";
-        expected.conflictLandscape = "2 more events";
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
 
         // Input
         // allDay, begin, end, title, location, eventId
@@ -273,18 +293,201 @@
         // Test
         MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
         CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
-                getTestContext(), cursor, events, now);
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
 
-        // Secondary test - Add two more afterwards
-        cursor.addRow(getRow(0, now + TWO_HOURS, now + TWO_HOURS + 1, title + i, location + i, 0));
-        ++i;
+        // Secondary test - Add one more afterwards
         cursor.addRow(getRow(0, now + TWO_HOURS, now + TWO_HOURS + 1, title + i, location + i, 0));
 
-        // Test again
+        // Test again, nothing should have changed, same expected result
         events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
-        actual = CalendarAppWidgetService.buildAppWidgetModel(getTestContext(), cursor, events, now);
+        actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_2InProgress2After() throws Exception {
+        final long now = 1262340000000L + HALF_HOUR; // Fri Jan 01 2010 01:30:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(4);
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.dayOfMonth = "1";
+        expected.dayOfWeek = "FRI";
+        expected.visibNoEvents = View.GONE;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "2am (in progress)";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "2am (in progress)";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "4:30am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "4:30am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+
+        // Input
+        // allDay, begin, end, title, location, eventId
+        i = 0;
+        cursor.addRow(getRow(0, now - HALF_HOUR, now + HALF_HOUR, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, now - HALF_HOUR, now + HALF_HOUR, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, now + TWO_HOURS, now + 3 * ONE_HOUR, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, now + TWO_HOURS, now + 4 * ONE_HOUR, title + i, location + i, 0));
+
+        // Test
+        MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
+        CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
+                getContext(), cursor, events, now);
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventToday() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.dayOfMonth = "1";
+        expected.dayOfWeek = "FRI";
+        expected.visibNoEvents = View.GONE;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "Today";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i = 0;
+        cursor.addRow(getRow(1, 1262304000000L, 1262390400000L, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+
+        // Test
+        MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
+        CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
+                getContext(), cursor, events, now);
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventTomorrow() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.dayOfMonth = "1";
+        expected.dayOfWeek = "FRI";
+        expected.visibNoEvents = View.GONE;
+
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "Tomorrow";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i = 0;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(1, 1262390400000L, 1262476800000L, title + i, location + i, 0));
+
+        // Test
+        MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
+        CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
+                getContext(), cursor, events, now);
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventLater() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.dayOfMonth = "1";
+        expected.dayOfWeek = "FRI";
+        expected.visibNoEvents = View.GONE;
+
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "3am";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i++;
+        expected.eventInfos[i].visibWhen = View.VISIBLE;
+        expected.eventInfos[i].visibWhere = View.VISIBLE;
+        expected.eventInfos[i].visibTitle = View.VISIBLE;
+        expected.eventInfos[i].when = "Sun";
+        expected.eventInfos[i].where = location + i;
+        expected.eventInfos[i].title = title + i;
+
+        i = 0;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(1, 1262476800000L, 1262563200000L, title + i, location + i, 0));
+
+        // Test
+        MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
+        CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
+                getContext(), cursor, events, now);
 
         assertEquals(expected.toString(), actual.toString());
     }