Fix agenda list view location

Changed AgendaWindowAdapter to move to a specific time/event id instead
of moving to the day of the event.
Stop fling motion when the today button is pressed.

Bug 5424040 ICS: On opening, Agenda view should scroll to
            nearest upcoming or in progress event
    5547705 AgendaListView keeps scrolling after pressing
            the "today" button.

Change-Id: I8982b4d9254a077e83df48c857c418063b9ea691
diff --git a/src/com/android/calendar/StickyHeaderListView.java b/src/com/android/calendar/StickyHeaderListView.java
index 7a691ec..25955a0 100644
--- a/src/com/android/calendar/StickyHeaderListView.java
+++ b/src/com/android/calendar/StickyHeaderListView.java
@@ -56,6 +56,7 @@
     protected Context mContext = null;
     protected Adapter mAdapter = null;
     protected HeaderIndexer mIndexer = null;
+    protected HeaderHeightListener mHeaderHeightListener = null;
     protected View mStickyHeader = null;
     protected View mDummyHeader = null; // A invisible header used when a section has no header
     protected ListView mListView = null;
@@ -63,6 +64,7 @@
 
     private int mSeparatorWidth;
     private View mSeparatorView;
+    private int mLastStickyHeaderHeight = 0;
 
     // This code is needed only if dataset changes do not force a call to OnScroll
     // protected DataSetObserver mListDataObserver = null;
@@ -103,6 +105,21 @@
         int getHeaderItemsNumber(int headerPosition);
     }
 
+    /***
+    *
+    * Interface that is used to update the sticky header's height
+    *
+    */
+   public interface HeaderHeightListener {
+
+       /***
+        * Updated a change in the sticky header's size
+        *
+        * @param height - new height of sticky header
+        */
+       void OnHeaderHeightChanged(int height);
+   }
+
     /**
      * Sets the adapter to be used by the class to get views of headers
      *
@@ -158,6 +175,10 @@
         mListener = listener;
     }
 
+    public void setHeaderHeightListener(HeaderHeightListener listener) {
+        mHeaderHeightListener = listener;
+    }
+
     // This code is needed only if dataset changes do not force a call to OnScroll
     // protected void createDataListener() {
     //    mListDataObserver = new DataSetObserver() {
@@ -282,6 +303,14 @@
                 if (stickyHeaderHeight == 0) {
                     stickyHeaderHeight = mStickyHeader.getMeasuredHeight();
                 }
+
+                // Update new header height
+                if (mHeaderHeightListener != null &&
+                        mLastStickyHeaderHeight != stickyHeaderHeight) {
+                    mLastStickyHeaderHeight = stickyHeaderHeight;
+                    mHeaderHeightListener.OnHeaderHeightChanged(stickyHeaderHeight);
+                }
+
                 View SectionLastView = mListView.getChildAt(sectionLastItemPosition);
                 if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
                     int lastViewBottom = SectionLastView.getBottom();
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
index 5f8c42e..e3880ca 100644
--- a/src/com/android/calendar/Utils.java
+++ b/src/com/android/calendar/Utils.java
@@ -526,6 +526,26 @@
     }
 
     /**
+     * Finds and returns the next midnight after "theTime" in milliseconds UTC
+     *
+     * @param recycle - Time object to recycle, otherwise null.
+     * @param theTime - Time used for calculations (in UTC)
+     * @param tz The time zone to convert this time to.
+     */
+    public static long getNextMidnight(Time recycle, long theTime, String tz) {
+        if (recycle == null) {
+            recycle = new Time();
+        }
+        recycle.timezone = tz;
+        recycle.set(theTime);
+        recycle.monthDay ++;
+        recycle.hour = 0;
+        recycle.minute = 0;
+        recycle.second = 0;
+        return recycle.normalize(true);
+    }
+
+    /**
      * Scan through a cursor of calendars and check if names are duplicated.
      * This travels a cursor containing calendar display names and fills in the
      * provided map with whether or not each name is repeated.
diff --git a/src/com/android/calendar/agenda/AgendaByDayAdapter.java b/src/com/android/calendar/agenda/AgendaByDayAdapter.java
index a7aec99..ef71ae9 100644
--- a/src/com/android/calendar/agenda/AgendaByDayAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaByDayAdapter.java
@@ -80,6 +80,12 @@
         mTmpTime = new Time(mTimeZone);
     }
 
+    public long getInstanceId(int position) {
+        if (mRowInfo == null || position >= mRowInfo.size()) {
+            return -1;
+        }
+        return mRowInfo.get(position).mInstanceId;
+    }
 
     // Returns the position of a header of a specific item
     public int getHeaderPosition(int position) {
@@ -278,21 +284,31 @@
         Cursor cursor = dayAdapterInfo.cursor;
         ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
         int prevStartDay = -1;
-        Time time = new Time(mTimeZone);
+
+        Time tempTime = new Time(mTimeZone);
         long now = System.currentTimeMillis();
-        time.set(now);
-        mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
+        tempTime.set(now);
+        mTodayJulianDay = Time.getJulianDay(now, tempTime.gmtoff);
+
         LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>();
         for (int position = 0; cursor.moveToNext(); position++) {
             int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY);
-
+            long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID);
+            long startTime =  cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
+            long endTime =  cursor.getLong(AgendaWindowAdapter.INDEX_END);
+            long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID);
+            boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
+            if (allDay) {
+                startTime = Utils.convertAlldayUtcToLocal(tempTime, startTime, mTimeZone);
+                endTime = Utils.convertAlldayUtcToLocal(tempTime, endTime, mTimeZone);
+            }
             // Skip over the days outside of the adapter's range
             startDay = Math.max(startDay, dayAdapterInfo.start);
 
             if (startDay != prevStartDay) {
                 // Check if we skipped over any empty days
                 if (prevStartDay == -1) {
-                    rowInfo.add(new RowInfo(TYPE_DAY, startDay, 0));
+                    rowInfo.add(new RowInfo(TYPE_DAY, startDay));
                 } else {
                     // If there are any multiple-day events that span the empty
                     // range of days, then create day headers and events for
@@ -313,24 +329,34 @@
                             // If this is the first event for the day, then
                             // insert a day header.
                             if (!dayHeaderAdded) {
-                                rowInfo.add(new RowInfo(TYPE_DAY, currentDay, 0));
+                                rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
                                 dayHeaderAdded = true;
                             }
-                            rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition));
+                            long infoEndTime =
+                                    ((info.mEndDay == currentDay) ? info.mEventEndTimeMilli : Utils
+                                            .getNextMidnight(tempTime, info.mEventStartTimeMilli,
+                                                    mTimeZone));
+                            rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
+                                    info.mEventId, info.mEventStartTimeMilli,
+                                    infoEndTime, info.mInstanceId, info.mAllDay));
+
+                            info.mEventStartTimeMilli = Utils.getNextMidnight(tempTime,
+                                    info.mEventStartTimeMilli, mTimeZone);
                         }
                     }
 
                     // If the day header was not added for the start day, then
                     // add it now.
                     if (!dayHeaderAdded) {
-                        rowInfo.add(new RowInfo(TYPE_DAY, startDay, 0));
+                        rowInfo.add(new RowInfo(TYPE_DAY, startDay));
                     }
                 }
                 prevStartDay = startDay;
             }
 
             // Add in the event for this cursor position
-            rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position));
+            rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, endTime,
+                    instanceId, allDay));
 
             // If this event spans multiple days, then add it to the multipleDay
             // list.
@@ -339,7 +365,9 @@
             // Skip over the days outside of the adapter's range
             endDay = Math.min(endDay, dayAdapterInfo.end);
             if (endDay > startDay) {
-                multipleDayList.add(new MultipleDayInfo(position, endDay));
+                multipleDayList.add(new MultipleDayInfo(position, endDay, id,
+                        Utils.getNextMidnight(tempTime, startTime, mTimeZone),
+                        endTime, instanceId, allDay));
             }
         }
 
@@ -362,10 +390,18 @@
                     // If this is the first event for the day, then
                     // insert a day header.
                     if (!dayHeaderAdded) {
-                        rowInfo.add(new RowInfo(TYPE_DAY, currentDay, 0));
+                        rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
                         dayHeaderAdded = true;
                     }
-                    rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition));
+                    long nextMidnight = Utils.getNextMidnight(tempTime, info.mEventStartTimeMilli,
+                            mTimeZone);
+                    long infoEndTime =
+                            (info.mEndDay == currentDay) ? info.mEventEndTimeMilli : nextMidnight;
+                    rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
+                            info.mEventId, info.mEventStartTimeMilli, infoEndTime,
+                            info.mInstanceId, info.mAllDay));
+
+                    info.mEventStartTimeMilli = nextMidnight;
                 }
             }
         }
@@ -382,63 +418,146 @@
         // or later. This flag is used by the adapter to create a view with a visual separator
         // between the past and the present/future
         boolean mFirstDayAfterYesterday;
+        final long mEventId;
+        final long mEventStartTimeMilli;
+        final long mEventEndTimeMilli;
+        final long mInstanceId;
+        final boolean mAllDay;
 
-        RowInfo(int type, int julianDay, int position) {
+        RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime,
+                long instanceId, boolean allDay) {
             mType = type;
             mDay = julianDay;
             mPosition = position;
+            mEventId = id;
+            mEventStartTimeMilli = startTime;
+            mEventEndTimeMilli = endTime;
             mFirstDayAfterYesterday = false;
+            mInstanceId = instanceId;
+            mAllDay = allDay;
+        }
+
+        RowInfo(int type, int julianDay) {
+            mType = type;
+            mDay = julianDay;
+            mPosition = 0;
+            mEventId = 0;
+            mEventStartTimeMilli = 0;
+            mEventEndTimeMilli = 0;
+            mFirstDayAfterYesterday = false;
+            mInstanceId = -1;
+            mAllDay = false;
         }
     }
 
     private static class MultipleDayInfo {
         final int mPosition;
         final int mEndDay;
+        final long mEventId;
+        long mEventStartTimeMilli;
+        long mEventEndTimeMilli;
+        final long mInstanceId;
+        final boolean mAllDay;
 
-        MultipleDayInfo(int position, int endDay) {
+        MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime,
+                long instanceId, boolean allDay) {
             mPosition = position;
             mEndDay = endDay;
+            mEventId = id;
+            mEventStartTimeMilli = startTime;
+            mEventEndTimeMilli = endTime;
+            mInstanceId = instanceId;
+            mAllDay = allDay;
         }
     }
 
     /**
-     * Searches for the day that matches the given Time object and returns the
-     * list position of that day.  If there are no events for that day, then it
-     * finds the nearest day (before or after) that has events and returns the
-     * list position for that day.
-     *
-     * @param time the date to search for
-     * @return the cursor position of the first event for that date, or zero
-     * if no match was found
+     * Finds the position in the cursor of the event that best matches the time and Id.
+     * It will try to find the event that has the specified id and start time, if such event
+     * doesn't exist, it will return the event with a matching id that is closest to the start time.
+     * If the id doesn't exist, it will return the event with start time closest to the specified
+     * time.
+     * @param time - start of event in milliseconds (or any arbitrary time if event id is unknown)
+     * @param id - Event id (-1 if unknown).
+     * @return Position of event (if found) or position of nearest event according to the time.
+     *         Zero if no event found
      */
-    public int findDayPositionNearestTime(Time time) {
+    public int findEventPositionNearestTime(Time time, long id) {
         if (mRowInfo == null) {
             return 0;
         }
         long millis = time.toMillis(false /* use isDst */);
-        int julianDay = Time.getJulianDay(millis, time.gmtoff);
-        int minDistance = 1000;  // some big number
+        long minDistance =  Integer.MAX_VALUE;  // some big number
+        long IdFoundMinDistance =  Integer.MAX_VALUE;  // some big number
         int minIndex = 0;
+        int idFoundMinIndex = 0;
+        int eventInTimeIndex = -1;
+        int allDayEventInTimeIndex = -1;
+        int allDayEventDay = 0;
+        int minDay = 0;
+        boolean idFound = false;
         int len = mRowInfo.size();
+
+        // Loop through the events and find the best match
+        // 1. Event id and start time matches requested id and time
+        // 2. Event id matches and closest time
+        // 3. No event id match , time is between event start and end
+        // 4. No event id match , all day event
+        // 5. The closest event to the requested time
+
         for (int index = 0; index < len; index++) {
             RowInfo row = mRowInfo.get(index);
             if (row.mType == TYPE_DAY) {
-                int distance = Math.abs(julianDay - row.mDay);
-                if (distance == 0) {
+                continue;
+            }
+
+            // Found exact match - done
+            if (row.mEventId == id) {
+                if (row.mEventStartTimeMilli == millis) {
                     return index;
                 }
+
+                // Not an exact match, Save event index if it is the closest to time so far
+                long distance = Math.abs(millis - row.mEventStartTimeMilli);
                 if (distance < minDistance) {
-                    minDistance = distance;
-                    minIndex = index;
+                    IdFoundMinDistance = distance;
+                    idFoundMinIndex = index;
+                }
+                idFound = true;
+            }
+            if (!idFound) {
+                // Found an event that contains the requested time
+                if (millis >= row.mEventStartTimeMilli && millis <= row.mEventEndTimeMilli) {
+                    if (row.mAllDay) {
+                        allDayEventInTimeIndex = index;
+                        allDayEventDay = row.mDay;
+                    } else {
+                        eventInTimeIndex = index;
+                    }
+                } else {
+                    // Save event index if it is the closest to time so far
+                    long distance = Math.abs(millis - row.mEventStartTimeMilli);
+                    if (distance < minDistance) {
+                        minDistance = distance;
+                        minIndex = index;
+                        minDay = row.mDay;
+                    }
                 }
             }
         }
-
-        // We didn't find an exact match so take the nearest day that had
-        // events.
+        // We didn't find an exact match so take the best matching event
+        if (idFound) {
+            return idFoundMinIndex;
+        }
+        if (eventInTimeIndex != -1) {
+            return eventInTimeIndex;
+        } else if (allDayEventInTimeIndex != -1 && minDay != allDayEventDay) {
+            return allDayEventInTimeIndex;
+        }
         return minIndex;
     }
 
+
     /**
      * Returns a flag indicating if this position is the first day after "yesterday" that has
      * events in it.
@@ -497,6 +616,8 @@
      *
      * @param listPos the list position of an event
      * @return the corresponding cursor position of that event
+     *         if the position point to day header , it will give the position of the next event
+     *         negated.
      */
     public int getCursorPosition(int listPos) {
         if (mRowInfo != null && listPos >= 0) {
diff --git a/src/com/android/calendar/agenda/AgendaFragment.java b/src/com/android/calendar/agenda/AgendaFragment.java
index f8db0ff..1ffbfb2 100644
--- a/src/com/android/calendar/agenda/AgendaFragment.java
+++ b/src/com/android/calendar/agenda/AgendaFragment.java
@@ -41,6 +41,7 @@
 import com.android.calendar.GeneralPreferences;
 import com.android.calendar.R;
 import com.android.calendar.StickyHeaderListView;
+import com.android.calendar.StickyHeaderListView.HeaderHeightListener;
 import com.android.calendar.StickyHeaderListView.HeaderIndexer;
 import com.android.calendar.Utils;
 
@@ -67,6 +68,7 @@
     private boolean mIsTabletConfig;
     private EventInfo mOnAttachedInfo = null;
     private boolean mOnAttachAllDay = false;
+    private AgendaWindowAdapter mAdapter = null;
 
     // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action
     // bar.
@@ -157,9 +159,13 @@
             Adapter a = mAgendaListView.getAdapter();
             lv.setAdapter(a);
             if (a instanceof HeaderViewListAdapter) {
-                lv.setIndexer((HeaderIndexer) ((HeaderViewListAdapter)a).getWrappedAdapter());
+                mAdapter = (AgendaWindowAdapter) ((HeaderViewListAdapter)a).getWrappedAdapter();
+                lv.setIndexer(mAdapter);
+                lv.setHeaderHeightListener(mAdapter);
             } else if (a instanceof AgendaWindowAdapter) {
-                lv.setIndexer((HeaderIndexer) a);
+                mAdapter = (AgendaWindowAdapter)a;
+                lv.setIndexer(mAdapter);
+                lv.setHeaderHeightListener(mAdapter);
             } else {
                 Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView");
             }
@@ -185,7 +191,13 @@
                 GeneralPreferences.KEY_HIDE_DECLINED, false);
 
         mAgendaListView.setHideDeclinedEvents(hideDeclined);
-        mAgendaListView.goTo(mTime, -1, mQuery, true);
+        if (mLastHandledEventId != -1) {
+            mAgendaListView.goTo(mLastHandledEventTime, mLastHandledEventId, mQuery, true, false);
+            mLastHandledEventTime = null;
+            mLastHandledEventId = -1;
+        } else {
+            mAgendaListView.goTo(mTime, -1, mQuery, true, false);
+        }
         mAgendaListView.onResume();
 
 //        // Register for Intent broadcasts
@@ -265,7 +277,9 @@
                         .after(event.endTime))) {
             mTime.set(event.startTime);
         }
-        mAgendaListView.goTo(mTime, event.id, mQuery, false);
+        mAgendaListView.goTo(mTime, event.id, mQuery, false,
+                ((event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0  &&
+                        mShowEventDetailsWithAgenda) ? true : false);
         AgendaAdapter.ViewHolder vh = mAgendaListView.getSelectedViewHolder();
         showEventInfo(event, vh != null ? vh.allDay : false);
     }
@@ -279,7 +293,7 @@
             // The view hasn't been set yet. Just return.
             return;
         }
-        mAgendaListView.goTo(time, -1, mQuery, true);
+        mAgendaListView.goTo(time, -1, mQuery, true, false);
     }
 
     @Override
@@ -294,12 +308,16 @@
         return EventType.GO_TO | EventType.EVENTS_CHANGED | ((mUsedForSearch)?EventType.SEARCH:0);
     }
 
+    private long mLastHandledEventId = -1;
+    private Time mLastHandledEventTime = null;
     @Override
     public void handleEvent(EventInfo event) {
         if (event.eventType == EventType.GO_TO) {
             // TODO support a range of time
             // TODO support event_id
             // TODO figure out the animate bit
+            mLastHandledEventId = event.id;
+            mLastHandledEventTime = event.startTime;
             goTo(event, true);
         } else if (event.eventType == EventType.SEARCH) {
             search(event.query, event.startTime);
@@ -363,8 +381,13 @@
 
     // OnScrollListener implementation to update the date on the pull-down menu of the app
 
+    @Override
     public void onScrollStateChanged(AbsListView view, int scrollState) {
-        // Do nothing
+        // Save scroll state so that the adapter can stop the scroll when the
+        // agenda list is fling state and it needs to set the agenda list to a new position
+        if (mAdapter != null) {
+            mAdapter.setScrollState(scrollState);
+        }
     }
 
     // Gets the time of the first visible view. If it is a new time, send a message to update
diff --git a/src/com/android/calendar/agenda/AgendaListView.java b/src/com/android/calendar/agenda/AgendaListView.java
index 1f6ff4d..98761a2 100644
--- a/src/com/android/calendar/agenda/AgendaListView.java
+++ b/src/com/android/calendar/agenda/AgendaListView.java
@@ -228,7 +228,8 @@
         }
     }
 
-    public void goTo(Time time, long id, String searchQuery, boolean forced) {
+    public void goTo(Time time, long id, String searchQuery, boolean forced,
+            boolean refreshEventInfo) {
         if (time == null) {
             time = mTime;
             long goToTime = getFirstVisibleTime();
@@ -243,11 +244,11 @@
         if (DEBUG) {
             Log.d(TAG, "Goto with time " + mTime.toString());
         }
-        mWindowAdapter.refresh(mTime, id, searchQuery, forced);
+        mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo);
     }
 
     public void refresh(boolean forced) {
-        mWindowAdapter.refresh(mTime, -1, null, forced);
+        mWindowAdapter.refresh(mTime, -1, null, forced, false);
     }
 
     public void deleteSelectedEvent() {
@@ -258,24 +259,6 @@
         }
     }
 
-/*    @Override
-    public int getFirstVisiblePosition() {
-        // TODO File bug!
-        // getFirstVisiblePosition doesn't always return the first visible
-        // item. Sometimes, it is above the visible one.
-        // instead. I loop through the viewgroup children and find the first
-        // visible one. BTW, getFirstVisiblePosition() == getChildAt(0). I
-        // am not looping through the entire list.
-        View v = getFirstVisibleView();
-        if (v != null) {
-            if (DEBUG) {
-                Log.v(TAG, "getFirstVisiblePosition: " + AgendaWindowAdapter.getViewTitle(v));
-            }
-            return getPositionForView(v);
-        }
-        return -1;
-    }
-*/
     public View getFirstVisibleView() {
         Rect r = new Rect();
         int childCount = getChildCount();
diff --git a/src/com/android/calendar/agenda/AgendaWindowAdapter.java b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
index 3481067..14cb185 100644
--- a/src/com/android/calendar/agenda/AgendaWindowAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Handler;
 import android.provider.CalendarContract;
 import android.provider.CalendarContract.Attendees;
 import android.provider.CalendarContract.Calendars;
@@ -41,6 +42,8 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
@@ -69,7 +72,7 @@
  */
 
 public class AgendaWindowAdapter extends BaseAdapter
-    implements StickyHeaderListView.HeaderIndexer{
+    implements StickyHeaderListView.HeaderIndexer, StickyHeaderListView.HeaderHeightListener{
 
     static final boolean BASICLOG = false;
     static final boolean DEBUGLOG = false;
@@ -156,6 +159,9 @@
     private final boolean mIsTabletConfig;
     private final int mSkipDateHeader;
 
+    boolean mCleanQueryInitiated = false;
+    private int mStickyHeaderSize = 44; // Initial size big enough for it to work
+
     /**
      * When the user scrolled to the top, a query will be made for older events
      * and this will be incremented. Don't make more requests if
@@ -195,6 +201,9 @@
     private boolean mShuttingDown;
     private boolean mHideDeclined;
 
+    // Used to stop a fling motion if the ListView is set to a specific position
+    int mListViewScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
     /** The current search query, or null if none */
     private String mSearchQuery;
 
@@ -215,9 +224,11 @@
         int end;
         String searchQuery;
         int queryType;
+        long id;
 
         public QuerySpec(int queryType) {
             this.queryType = queryType;
+            id = -1;
         }
 
         @Override
@@ -235,6 +246,7 @@
                 long goToTimeMillis = goToTime.toMillis(false);
                 result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32));
             }
+            result = prime * result + (int)id;
             return result;
         }
 
@@ -246,7 +258,7 @@
             QuerySpec other = (QuerySpec) obj;
             if (end != other.end || queryStartMillis != other.queryStartMillis
                     || queryType != other.queryType || start != other.start
-                    || Utils.equals(searchQuery, other.searchQuery)) {
+                    || Utils.equals(searchQuery, other.searchQuery) || id != other.id) {
                 return false;
             }
 
@@ -319,6 +331,10 @@
 
         mShowEventOnStart = showEventOnStart;
 
+        // Implies there is no sticky header
+        if (!mShowEventOnStart) {
+            mStickyHeaderSize = 0;
+        }
         mSearchQuery = null;
 
         LayoutInflater inflater = (LayoutInflater) context
@@ -385,10 +401,23 @@
     }
 
     // Abstract Method in BaseAdapter
+    @Override
     public long getItemId(int position) {
         DayAdapterInfo info = getAdapterInfoByPosition(position);
         if (info != null) {
-            return ((position - info.offset) << 20) + info.start ;
+            int curPos = info.dayAdapter.getCursorPosition(position - info.offset);
+            if (curPos == Integer.MIN_VALUE) {
+                return -1;
+            }
+            // Regular event
+            if (curPos >= 0) {
+                info.cursor.moveToPosition(curPos);
+                return info.cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID) << 20 +
+                    info.cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
+            }
+            // Day Header
+            return info.dayAdapter.findJulianDayFromPosition(position);
+
         } else {
             return -1;
         }
@@ -469,12 +498,11 @@
 
     private AgendaAdapter.ViewHolder mSelectedVH = null;
 
-    private int findDayPositionNearestTime(Time time) {
-        if (DEBUGLOG) Log.e(TAG, "findDayPositionNearestTime " + time);
-
+    private int findEventPositionNearestTime(Time time, long id) {
+        if (DEBUGLOG) Log.e(TAG, "findEventPositionNearestTime " + time + " id " + id);
         DayAdapterInfo info = getAdapterInfoByTime(time);
         if (info != null) {
-            return info.offset + info.dayAdapter.findDayPositionNearestTime(time);
+            return info.offset + info.dayAdapter.findEventPositionNearestTime(time, id);
         } else {
             return -1;
         }
@@ -550,8 +578,7 @@
         }
 
         if (cursorPosition < info.cursor.getCount()) {
-            info.cursor.moveToPosition(cursorPosition);
-            EventInfo ei = buildEventInfoFromCursor(info.cursor, isDayHeader);
+            EventInfo ei = buildEventInfoFromCursor(info.cursor, cursorPosition, isDayHeader);
             if (!returnEventStartDay && !isDayHeader) {
                 ei.startDay = info.dayAdapter.findJulianDayFromPosition(cursorPosition);
             }
@@ -560,7 +587,13 @@
         return null;
     }
 
-    private EventInfo buildEventInfoFromCursor(final Cursor cursor, boolean isDayHeader) {
+    private EventInfo buildEventInfoFromCursor(final Cursor cursor, int cursorPosition,
+            boolean isDayHeader) {
+        if (cursorPosition == -1) {
+            cursor.moveToFirst();
+        } else {
+            cursor.moveToPosition(cursorPosition);
+        }
         EventInfo event = new EventInfo();
         event.begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
         event.end = cursor.getLong(AgendaWindowAdapter.INDEX_END);
@@ -594,25 +627,54 @@
         return event;
     }
 
-    public void refresh(Time goToTime, long id, String searchQuery, boolean forced) {
+    public void refresh(Time goToTime, long id, String searchQuery, boolean forced,
+            boolean refreshEventInfo) {
         if (searchQuery != null) {
             mSearchQuery = searchQuery;
         }
 
         if (DEBUGLOG) {
-            Log.e(TAG, this + ": refresh " + goToTime.toString()
-                    + (forced ? " forced" : " not forced"));
+            Log.e(TAG, this + ": refresh " + goToTime.toString() + " id " + id
+                    + ((searchQuery != null) ? searchQuery : "")
+                    + (forced ? " forced" : " not forced")
+                    + (refreshEventInfo ? " refresh event info" : ""));
         }
-
         int startDay = Time.getJulianDay(goToTime.toMillis(false), goToTime.gmtoff);
 
         if (!forced && isInRange(startDay, startDay)) {
             // No need to re-query
             if (!mAgendaListView.isEventVisible(goToTime, id)) {
-                int gotoPosition = findDayPositionNearestTime(goToTime);
+                int gotoPosition = findEventPositionNearestTime(goToTime, id);
                 if (gotoPosition > 0) {
-                    mAgendaListView.setSelection(gotoPosition + OFF_BY_ONE_BUG + mSkipDateHeader);
+                    mAgendaListView.setSelectionFromTop(gotoPosition +
+                            OFF_BY_ONE_BUG + mSkipDateHeader, mStickyHeaderSize);
+                    if (mListViewScrollState == OnScrollListener.SCROLL_STATE_FLING) {
+                        mAgendaListView.smoothScrollBy(0, 0);
+                    }
+                    if (refreshEventInfo) {
+                        long newInstanceId = findInstanceIdFromPosition(gotoPosition);
+                        if (newInstanceId != getSelectedInstanceId()) {
+                            setSelectedInstanceId(newInstanceId);
+                            new Handler().post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    notifyDataSetChanged();
+                                }
+                            });
+                            Cursor tempCursor = getCursorByPosition(gotoPosition);
+                            if (tempCursor != null) {
+                                int tempCursorPosition = getCursorPositionByPosition(gotoPosition);
+                                EventInfo event =
+                                        buildEventInfoFromCursor(tempCursor, tempCursorPosition,
+                                                false);
+                                CalendarController.getInstance(mContext).sendEventRelatedEvent(
+                                        this, EventType.VIEW_EVENT, event.id, event.begin,
+                                        event.end, 0, 0, -1);
+                            }
+                        }
+                    }
                 }
+
                 Time actualTime = new Time(mTimeZone);
                 if (goToTime != null) {
                     actualTime.set(goToTime);
@@ -625,19 +687,23 @@
             return;
         }
 
-        // Query for a total of MIN_QUERY_DURATION days
-        int endDay = startDay + MIN_QUERY_DURATION;
+        // If AllInOneActivity is sending a second GOTO event(in OnResume), ignore it.
+        if (!mCleanQueryInitiated || searchQuery != null) {
+            // Query for a total of MIN_QUERY_DURATION days
+            int endDay = startDay + MIN_QUERY_DURATION;
 
-        queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN);
+            mSelectedInstanceId = -1;
+            queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN, id);
+            mCleanQueryInitiated = true;
 
-        // Pre-fetch more data to overcome a race condition in AgendaListView.shiftSelection
-        // Queuing more data with the goToTime set to the selected time skips the call to
-        // shiftSelection on refresh.
-        mOlderRequests++;
-        queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_OLDER);
-        mNewerRequests++;
-        queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_NEWER);
-
+            // Pre-fetch more data to overcome a race condition in AgendaListView.shiftSelection
+            // Queuing more data with the goToTime set to the selected time skips the call to
+            // shiftSelection on refresh.
+            mOlderRequests++;
+            queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_OLDER, id);
+            mNewerRequests++;
+            queueQuery(0, 0, goToTime, searchQuery, QUERY_TYPE_NEWER, id);
+        }
     }
 
     public void close() {
@@ -742,12 +808,13 @@
     }
 
     private boolean queueQuery(int start, int end, Time goToTime,
-            String searchQuery, int queryType) {
+            String searchQuery, int queryType, long id) {
         QuerySpec queryData = new QuerySpec(queryType);
         queryData.goToTime = goToTime;
         queryData.start = start;
         queryData.end = end;
         queryData.searchQuery = searchQuery;
+        queryData.id = id;
         return queueQuery(queryData);
     }
 
@@ -856,6 +923,10 @@
                         + " Count: " + cursor.getCount());
             }
 
+            if (data.queryType == QUERY_TYPE_CLEAN) {
+                mCleanQueryInitiated = false;
+            }
+
             if (mShuttingDown) {
                 cursor.close();
                 return;
@@ -865,6 +936,7 @@
             int cursorSize = cursor.getCount();
             if (cursorSize > 0 || mAdapterInfos.isEmpty() || data.queryType == QUERY_TYPE_CLEAN) {
                 final int listPositionOffset = processNewCursor(data, cursor);
+                int newPosition = -1;
                 if (data.goToTime == null) { // Typical Scrolling type query
                     notifyDataSetChanged();
                     if (listPositionOffset != 0) {
@@ -873,10 +945,13 @@
                 } else { // refresh() called. Go to the designated position
                     final Time goToTime = data.goToTime;
                     notifyDataSetChanged();
-                    int newPosition = findDayPositionNearestTime(goToTime);
+                    newPosition = findEventPositionNearestTime(goToTime, data.id);
                     if (newPosition >= 0) {
-                        mAgendaListView.setSelection(newPosition + OFF_BY_ONE_BUG
-                                + mSkipDateHeader);
+                        if (mListViewScrollState == OnScrollListener.SCROLL_STATE_FLING) {
+                            mAgendaListView.smoothScrollBy(0, 0);
+                        }
+                        mAgendaListView.setSelectionFromTop(newPosition + OFF_BY_ONE_BUG
+                                + mSkipDateHeader, mStickyHeaderSize);
                         Time actualTime = new Time(mTimeZone);
                         actualTime.set(goToTime);
                         CalendarController.getInstance(mContext).sendEvent(this,
@@ -885,7 +960,16 @@
                     }
                     if (DEBUGLOG) {
                         Log.e(TAG, "Setting listview to " +
-                                "findDayPositionNearestTime: " + (newPosition + OFF_BY_ONE_BUG));
+                                "findEventPositionNearestTime: " + (newPosition + OFF_BY_ONE_BUG));
+                    }
+                }
+
+                // Make sure we change the selected instance Id only on a clean query and we
+                // do not have one set already
+                if (mSelectedInstanceId == -1 && newPosition != -1 &&
+                        data.queryType == QUERY_TYPE_CLEAN) {
+                    if (data.id != -1 || data.goToTime != null) {
+                        mSelectedInstanceId = findInstanceIdFromPosition(newPosition);
                     }
                 }
 
@@ -907,17 +991,32 @@
                     }
                 }
 
-                if (mSelectedInstanceId == -1 && cursor.moveToFirst()) {
-                    mSelectedInstanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID);
-                    // Set up a dummy view holder so we have the right all day
-                    // info when the view is created.
-                    // TODO determine the full set of what might be useful to
-                    // know about the selected view and fill it in.
-                    mSelectedVH = new AgendaAdapter.ViewHolder();
-                    mSelectedVH.allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
+                // Show the requested event
+                if (mShowEventOnStart && data.queryType == QUERY_TYPE_CLEAN) {
+                    Cursor tempCursor = null;
+                    int tempCursorPosition = -1;
 
-                    EventInfo event = buildEventInfoFromCursor(cursor, false);
-                    if (mShowEventOnStart) {
+                    // If no valid event is selected , just pick the first one
+                    if (mSelectedInstanceId == -1) {
+                        if (cursor.moveToFirst()) {
+                            mSelectedInstanceId = cursor
+                                    .getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID);
+                            // Set up a dummy view holder so we have the right all day
+                            // info when the view is created.
+                            // TODO determine the full set of what might be useful to
+                            // know about the selected view and fill it in.
+                            mSelectedVH = new AgendaAdapter.ViewHolder();
+                            mSelectedVH.allDay =
+                                cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
+                            tempCursor = cursor;
+                        }
+                    } else if (newPosition != -1) {
+                         tempCursor = getCursorByPosition(newPosition);
+                         tempCursorPosition = getCursorPositionByPosition(newPosition);
+                    }
+                    if (tempCursor != null) {
+                        EventInfo event = buildEventInfoFromCursor(tempCursor, tempCursorPosition,
+                                false);
                         CalendarController.getInstance(mContext).sendEventRelatedEvent(this,
                                 EventType.VIEW_EVENT, event.id, event.begin, event.end, 0, 0, -1);
                     }
@@ -1161,11 +1260,35 @@
         mSelectedVH = null;
     }
 
+    private long findInstanceIdFromPosition(int position) {
+        DayAdapterInfo info = getAdapterInfoByPosition(position);
+        if (info != null) {
+            return info.dayAdapter.getInstanceId(position - info.offset);
+        }
+        return -1;
+    }
+
+    private Cursor getCursorByPosition(int position) {
+        DayAdapterInfo info = getAdapterInfoByPosition(position);
+        if (info != null) {
+            return info.cursor;
+        }
+        return null;
+    }
+
+    private int getCursorPositionByPosition(int position) {
+        DayAdapterInfo info = getAdapterInfoByPosition(position);
+        if (info != null) {
+            return info.dayAdapter.getCursorPosition(position - info.offset);
+        }
+        return -1;
+    }
 
     // Implementation of HeaderIndexer interface for StickyHeeaderListView
 
     // Returns the location of the day header of a specific event specified in the position
     // in the adapter
+    @Override
     public int getHeaderPositionFromItemPosition(int position) {
 
         // For phone configuration, return -1 so there will be no sticky header
@@ -1182,6 +1305,7 @@
     }
 
     // Returns the number of events for a specific day header
+    @Override
     public int getHeaderItemsNumber(int headerPosition) {
         if (headerPosition < 0 || !mIsTabletConfig) {
             return -1;
@@ -1192,4 +1316,13 @@
         }
         return -1;
     }
+
+    @Override
+    public void OnHeaderHeightChanged(int height) {
+        mStickyHeaderSize = height;
+    }
+
+    public void setScrollState(int state) {
+        mListViewScrollState = state;
+    }
 }