From 00b8c1a39d75c1b4626dc987bd1a51cfaf7c9be1 Mon Sep 17 00:00:00 2001 From: Mason Tang Date: Mon, 23 Aug 2010 12:02:00 -0700 Subject: Moved agenda-related classes into an agenda package Change-Id: If834a652490938b4a31bfd5f6fa4e1aea2a6ffa7 --- res/layout/agenda_item.xml | 4 +- src/com/android/calendar/AgendaAdapter.java | 184 ---- src/com/android/calendar/AgendaByDayAdapter.java | 403 --------- src/com/android/calendar/AgendaFragment.java | 204 ----- src/com/android/calendar/AgendaItemView.java | 57 -- src/com/android/calendar/AgendaListView.java | 213 ----- src/com/android/calendar/AgendaWindowAdapter.java | 921 -------------------- src/com/android/calendar/AllInOneActivity.java | 1 + src/com/android/calendar/CalendarController.java | 18 +- src/com/android/calendar/SearchActivity.java | 1 + src/com/android/calendar/agenda/AgendaAdapter.java | 186 +++++ .../calendar/agenda/AgendaByDayAdapter.java | 404 +++++++++ .../android/calendar/agenda/AgendaFragment.java | 201 +++++ .../android/calendar/agenda/AgendaItemView.java | 57 ++ .../android/calendar/agenda/AgendaListView.java | 215 +++++ .../calendar/agenda/AgendaWindowAdapter.java | 924 +++++++++++++++++++++ 16 files changed, 2000 insertions(+), 1993 deletions(-) delete mode 100644 src/com/android/calendar/AgendaAdapter.java delete mode 100644 src/com/android/calendar/AgendaByDayAdapter.java delete mode 100644 src/com/android/calendar/AgendaFragment.java delete mode 100644 src/com/android/calendar/AgendaItemView.java delete mode 100644 src/com/android/calendar/AgendaListView.java delete mode 100644 src/com/android/calendar/AgendaWindowAdapter.java create mode 100644 src/com/android/calendar/agenda/AgendaAdapter.java create mode 100644 src/com/android/calendar/agenda/AgendaByDayAdapter.java create mode 100644 src/com/android/calendar/agenda/AgendaFragment.java create mode 100644 src/com/android/calendar/agenda/AgendaItemView.java create mode 100644 src/com/android/calendar/agenda/AgendaListView.java create mode 100644 src/com/android/calendar/agenda/AgendaWindowAdapter.java diff --git a/res/layout/agenda_item.xml b/res/layout/agenda_item.xml index d650f1e0..b8ade61f 100644 --- a/res/layout/agenda_item.xml +++ b/res/layout/agenda_item.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - + diff --git a/src/com/android/calendar/AgendaAdapter.java b/src/com/android/calendar/AgendaAdapter.java deleted file mode 100644 index 9df12012..00000000 --- a/src/com/android/calendar/AgendaAdapter.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.provider.Calendar.Attendees; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.ResourceCursorAdapter; -import android.widget.TextView; - -import java.util.Formatter; -import java.util.Locale; - -public class AgendaAdapter extends ResourceCursorAdapter { - private String mNoTitleLabel; - private Resources mResources; - private int mDeclinedColor; - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private Formatter mFormatter; - private StringBuilder mStringBuilder; - - static class ViewHolder { - int overLayColor; // Used by AgendaItemView to gray out the entire item if so desired - - /* Event */ - TextView title; - TextView when; - TextView where; - int calendarColor; // Used by AgendaItemView to color the vertical stripe - } - - public AgendaAdapter(Context context, int resource) { - super(context, resource, null); - mResources = context.getResources(); - mNoTitleLabel = mResources.getString(R.string.no_title_label); - mDeclinedColor = mResources.getColor(R.drawable.agenda_item_declined); - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ViewHolder holder = null; - - // Listview may get confused and pass in a different type of view since - // we keep shifting data around. Not a big problem. - Object tag = view.getTag(); - if (tag instanceof ViewHolder) { - holder = (ViewHolder) view.getTag(); - } - - if (holder == null) { - holder = new ViewHolder(); - view.setTag(holder); - holder.title = (TextView) view.findViewById(R.id.title); - holder.when = (TextView) view.findViewById(R.id.when); - holder.where = (TextView) view.findViewById(R.id.where); - } - - // Fade text if event was declined. - int selfAttendeeStatus = cursor.getInt(AgendaWindowAdapter.INDEX_SELF_ATTENDEE_STATUS); - if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) { - holder.overLayColor = mDeclinedColor; - } else { - holder.overLayColor = 0; - } - - TextView title = holder.title; - TextView when = holder.when; - TextView where = holder.where; - - /* Calendar Color */ - int color = cursor.getInt(AgendaWindowAdapter.INDEX_COLOR); - holder.calendarColor = color; - - // What - String titleString = cursor.getString(AgendaWindowAdapter.INDEX_TITLE); - if (titleString == null || titleString.length() == 0) { - titleString = mNoTitleLabel; - } - title.setText(titleString); - title.setTextColor(color); - - // When - long begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - long end = cursor.getLong(AgendaWindowAdapter.INDEX_END); - boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - int flags; - String whenString; - if (allDay) { - flags = DateUtils.FORMAT_UTC; - } else { - flags = DateUtils.FORMAT_SHOW_TIME; - } - if (DateFormat.is24HourFormat(context)) { - flags |= DateUtils.FORMAT_24HOUR; - } - mStringBuilder.setLength(0); - whenString = DateUtils.formatDateRange(context, mFormatter, begin, end, flags).toString(); - when.setText(whenString); - - String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE); - if (rrule != null) { - when.setCompoundDrawablesWithIntrinsicBounds(null, null, - context.getResources().getDrawable(R.drawable.ic_repeat_dark), null); - when.setCompoundDrawablePadding(5); - } else { - when.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - /* - // Repeating info - View repeatContainer = view.findViewById(R.id.repeat_icon); - String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE); - if (rrule != null) { - repeatContainer.setVisibility(View.VISIBLE); - } else { - repeatContainer.setVisibility(View.GONE); - } - */ - - /* - // Reminder - boolean hasAlarm = cursor.getInt(AgendaWindowAdapter.INDEX_HAS_ALARM) != 0; - if (hasAlarm) { - updateReminder(view, context, begin, cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID)); - } - */ - - // Where - String whereString = cursor.getString(AgendaWindowAdapter.INDEX_EVENT_LOCATION); - if (whereString != null && whereString.length() > 0) { - where.setVisibility(View.VISIBLE); - where.setText(whereString); - } else { - where.setVisibility(View.GONE); - } - } - - /* - public static void updateReminder(View view, Context context, long begin, long eventId) { - ContentResolver cr = context.getContentResolver(); - Uri uri = Reminders.CONTENT_URI; - String where = String.format(REMINDERS_WHERE, eventId); - - Cursor remindersCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null); - if (remindersCursor != null) { - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - LinearLayout parent = (LinearLayout) view.findViewById(R.id.reminders_container); - parent.removeAllViews(); - while (remindersCursor.moveToNext()) { - int alarm = remindersCursor.getInt(REMINDERS_INDEX_MINUTES); - String before = EditEvent.constructReminderLabel(context, alarm, true); - LinearLayout reminderItem = (LinearLayout) - inflater.inflate(R.layout.agenda_reminder_item, null); - TextView reminderItemText = (TextView) reminderItem.findViewById(R.id.reminder); - reminderItemText.setText(before); - parent.addView(reminderItem); - } - } - remindersCursor.close(); - } - */ -} - diff --git a/src/com/android/calendar/AgendaByDayAdapter.java b/src/com/android/calendar/AgendaByDayAdapter.java deleted file mode 100644 index f7b86de5..00000000 --- a/src/com/android/calendar/AgendaByDayAdapter.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.content.Context; -import android.database.Cursor; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.android.calendar.AgendaWindowAdapter.DayAdapterInfo; - -import java.util.ArrayList; -import java.util.Formatter; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; - -public class AgendaByDayAdapter extends BaseAdapter { - private static final int TYPE_DAY = 0; - private static final int TYPE_MEETING = 1; - static final int TYPE_LAST = 2; - - private final Context mContext; - private final AgendaAdapter mAgendaAdapter; - private final LayoutInflater mInflater; - private ArrayList mRowInfo; - private int mTodayJulianDay; - private Time mTmpTime = new Time(); - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private Formatter mFormatter; - private StringBuilder mStringBuilder; - - static class ViewHolder { - TextView dateView; - } - - public AgendaByDayAdapter(Context context) { - mContext = context; - mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - } - - public int getCount() { - if (mRowInfo != null) { - return mRowInfo.size(); - } - return mAgendaAdapter.getCount(); - } - - public Object getItem(int position) { - if (mRowInfo != null) { - RowInfo row = mRowInfo.get(position); - if (row.mType == TYPE_DAY) { - return row; - } else { - return mAgendaAdapter.getItem(row.mData); - } - } - return mAgendaAdapter.getItem(position); - } - - public long getItemId(int position) { - if (mRowInfo != null) { - RowInfo row = mRowInfo.get(position); - if (row.mType == TYPE_DAY) { - return -position; - } else { - return mAgendaAdapter.getItemId(row.mData); - } - } - return mAgendaAdapter.getItemId(position); - } - - @Override - public int getViewTypeCount() { - return TYPE_LAST; - } - - @Override - public int getItemViewType(int position) { - return mRowInfo != null && mRowInfo.size() > position ? - mRowInfo.get(position).mType : TYPE_DAY; - } - - public View getView(int position, View convertView, ViewGroup parent) { - if ((mRowInfo == null) || (position > mRowInfo.size())) { - // If we have no row info, mAgendaAdapter returns the view. - return mAgendaAdapter.getView(position, convertView, parent); - } - - RowInfo row = mRowInfo.get(position); - if (row.mType == TYPE_DAY) { - ViewHolder holder = null; - View agendaDayView = null; - if ((convertView != null) && (convertView.getTag() != null)) { - // Listview may get confused and pass in a different type of - // view since we keep shifting data around. Not a big problem. - Object tag = convertView.getTag(); - if (tag instanceof ViewHolder) { - agendaDayView = convertView; - holder = (ViewHolder) tag; - } - } - - if (holder == null) { - // Create a new AgendaView with a ViewHolder for fast access to - // views w/o calling findViewById() - holder = new ViewHolder(); - agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false); - holder.dateView = (TextView) agendaDayView.findViewById(R.id.date); - agendaDayView.setTag(holder); - } - - // Re-use the member variable "mTime" which is set to the local timezone. - Time date = mTmpTime; - long millis = date.setJulianDay(row.mData); - int flags = DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_SHOW_DATE; - - mStringBuilder.setLength(0); - String dateViewText; - if (row.mData == mTodayJulianDay) { - dateViewText = mContext.getString(R.string.agenda_today, DateUtils.formatDateRange( - mContext, mFormatter, millis, millis, flags).toString()); - } else { - flags |= DateUtils.FORMAT_SHOW_WEEKDAY; - dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis, - flags).toString(); - } - - if (AgendaWindowAdapter.BASICLOG) { - dateViewText += " P:" + position; - } - holder.dateView.setText(dateViewText); - - return agendaDayView; - } else if (row.mType == TYPE_MEETING) { - View x = mAgendaAdapter.getView(row.mData, convertView, parent); - TextView y = ((AgendaAdapter.ViewHolder) x.getTag()).title; - if (AgendaWindowAdapter.BASICLOG) { - y.setText(y.getText() + " P:" + position); - } else { - y.setText(y.getText()); - } - return x; - } else { - // Error - throw new IllegalStateException("Unknown event type:" + row.mType); - } - } - - public void clearDayHeaderInfo() { - mRowInfo = null; - } - - public void changeCursor(DayAdapterInfo info) { - calculateDays(info); - mAgendaAdapter.changeCursor(info.cursor); - } - - public void calculateDays(DayAdapterInfo dayAdapterInfo) { - Cursor cursor = dayAdapterInfo.cursor; - ArrayList rowInfo = new ArrayList(); - int prevStartDay = -1; - Time time = new Time(); - long now = System.currentTimeMillis(); - time.set(now); - mTodayJulianDay = Time.getJulianDay(now, time.gmtoff); - LinkedList multipleDayList = new LinkedList(); - for (int position = 0; cursor.moveToNext(); position++) { - int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); - - // 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)); - } else { - // If there are any multiple-day events that span the empty - // range of days, then create day headers and events for - // those multiple-day events. - boolean dayHeaderAdded = false; - for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) { - dayHeaderAdded = false; - Iterator iter = multipleDayList.iterator(); - while (iter.hasNext()) { - MultipleDayInfo info = iter.next(); - // If this event has ended then remove it from the - // list. - if (info.mEndDay < currentDay) { - iter.remove(); - continue; - } - - // If this is the first event for the day, then - // insert a day header. - if (!dayHeaderAdded) { - rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); - dayHeaderAdded = true; - } - rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition)); - } - } - - // If the day header was not added for the start day, then - // add it now. - if (!dayHeaderAdded) { - rowInfo.add(new RowInfo(TYPE_DAY, startDay)); - } - } - prevStartDay = startDay; - } - - // Add in the event for this cursor position - rowInfo.add(new RowInfo(TYPE_MEETING, position)); - - // If this event spans multiple days, then add it to the multipleDay - // list. - int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); - - // 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)); - } - } - - // There are no more cursor events but we might still have multiple-day - // events left. So create day headers and events for those. - if (prevStartDay > 0) { - for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end; - currentDay++) { - boolean dayHeaderAdded = false; - Iterator iter = multipleDayList.iterator(); - while (iter.hasNext()) { - MultipleDayInfo info = iter.next(); - // If this event has ended then remove it from the - // list. - if (info.mEndDay < currentDay) { - iter.remove(); - continue; - } - - // If this is the first event for the day, then - // insert a day header. - if (!dayHeaderAdded) { - rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); - dayHeaderAdded = true; - } - rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition)); - } - } - } - mRowInfo = rowInfo; - } - - private static class RowInfo { - // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING) - final int mType; - - // If mType is TYPE_DAY, then mData is the Julian day. Otherwise - // mType is TYPE_MEETING and mData is the cursor position. - final int mData; - - RowInfo(int type, int data) { - mType = type; - mData = data; - } - } - - private static class MultipleDayInfo { - final int mPosition; - final int mEndDay; - - MultipleDayInfo(int position, int endDay) { - mPosition = position; - mEndDay = endDay; - } - } - - /** - * 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 - */ - public int findDayPositionNearestTime(Time time) { - 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 - int minIndex = 0; - int len = mRowInfo.size(); - for (int index = 0; index < len; index++) { - RowInfo row = mRowInfo.get(index); - if (row.mType == TYPE_DAY) { - int distance = Math.abs(julianDay - row.mData); - if (distance == 0) { - return index; - } - if (distance < minDistance) { - minDistance = distance; - minIndex = index; - } - } - } - - // We didn't find an exact match so take the nearest day that had - // events. - return minIndex; - } - - /** - * Finds the Julian day containing the event at the given position. - * - * @param position the list position of an event - * @return the Julian day containing that event - */ - public int findJulianDayFromPosition(int position) { - if (mRowInfo == null || position < 0) { - return 0; - } - - int len = mRowInfo.size(); - if (position >= len) return 0; // no row info at this position - - for (int index = position; index >= 0; index--) { - RowInfo row = mRowInfo.get(index); - if (row.mType == TYPE_DAY) { - return row.mData; - } - } - return 0; - } - - /** - * Converts a list position to a cursor position. The list contains - * day headers as well as events. The cursor contains only events. - * - * @param listPos the list position of an event - * @return the corresponding cursor position of that event - */ - public int getCursorPosition(int listPos) { - if (mRowInfo != null && listPos >= 0) { - RowInfo row = mRowInfo.get(listPos); - if (row.mType == TYPE_MEETING) { - return row.mData; - } else { - int nextPos = listPos + 1; - if (nextPos < mRowInfo.size()) { - nextPos = getCursorPosition(nextPos); - if (nextPos >= 0) { - return -nextPos; - } - } - } - } - return Integer.MIN_VALUE; - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - if (mRowInfo != null && position < mRowInfo.size()) { - RowInfo row = mRowInfo.get(position); - return row.mType == TYPE_MEETING; - } - return true; - } -} diff --git a/src/com/android/calendar/AgendaFragment.java b/src/com/android/calendar/AgendaFragment.java deleted file mode 100644 index 40d8a285..00000000 --- a/src/com/android/calendar/AgendaFragment.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.CalendarController.EventInfo; -import com.android.calendar.CalendarController.EventType; - -import dalvik.system.VMRuntime; - -import android.app.Fragment; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.os.Bundle; -import android.os.Handler; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -public class AgendaFragment extends Fragment implements CalendarController.EventHandler { - - private static final String TAG = AgendaFragment.class.getSimpleName(); - private static boolean DEBUG = false; - - protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; - private static final long INITIAL_HEAP_SIZE = 4*1024*1024; - - private AgendaListView mAgendaListView; - private Time mTime; - - private String mQuery; - - public AgendaFragment() { - this(0); - } - - public AgendaFragment(long timeMillis) { - mTime = new Time(); - if (timeMillis == 0) { - mTime.setToNow(); - } else { - mTime.set(timeMillis); - } - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - // Eliminate extra GCs during startup by setting the initial heap size to 4MB. - // TODO: We should restore the old heap size once the activity reaches the idle state - VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Context context = getActivity(); - mAgendaListView = new AgendaListView(context); - mAgendaListView.goTo(mTime, mQuery, false); - return mAgendaListView; - } - - @Override - public void onResume() { - super.onResume(); - if (DEBUG) { - Log.v(TAG, "OnResume to " + mTime.toString()); - } - - SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences( - getActivity()); - boolean hideDeclined = prefs.getBoolean( - CalendarPreferenceActivity.KEY_HIDE_DECLINED, false); - - mAgendaListView.setHideDeclinedEvents(hideDeclined); - mAgendaListView.goTo(mTime, mQuery, true); - mAgendaListView.onResume(); - -// // Register for Intent broadcasts -// IntentFilter filter = new IntentFilter(); -// filter.addAction(Intent.ACTION_TIME_CHANGED); -// filter.addAction(Intent.ACTION_DATE_CHANGED); -// filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); -// registerReceiver(mIntentReceiver, filter); -// -// mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - long firstVisibleTime = mAgendaListView.getFirstVisibleTime(); - if (firstVisibleTime > 0) { - mTime.set(firstVisibleTime); - outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); - if (DEBUG) { - Log.v(TAG, "onSaveInstanceState " + mTime.toString()); - } - } - } - - @Override - public void onPause() { - super.onPause(); - - mAgendaListView.onPause(); -// mContentResolver.unregisterContentObserver(mObserver); -// unregisterReceiver(mIntentReceiver); - - // Record Agenda View as the (new) default detailed view. -// Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); - } - - /* Navigator interface methods */ - @Override - public void goToToday() { - if (mAgendaListView == null) { - // The view hasn't been set yet. Just save the time and use it later. - mTime.setToNow(); - return; - } - Time now = new Time(); - now.setToNow(); - mAgendaListView.goTo(now, mQuery, true); // Force refresh - } - - @Override - public void goTo(Time time, boolean animate) { - if (mAgendaListView == null) { - // The view hasn't been set yet. Just save the time and use it - // later. - mTime.set(time); - return; - } - mAgendaListView.goTo(time, mQuery, false); - } - - private void search(String query, Time time) { - mQuery = query; - if (time != null) { - mTime.set(time); - } - if (mAgendaListView == null) { - // The view hasn't been set yet. Just return. - return; - } - mAgendaListView.goTo(time, mQuery, true); - } - - @Override - public long getSelectedTime() { - return mAgendaListView.getSelectedTime(); - } - - @Override - public boolean getAllDay() { - return false; - } - - @Override - public void eventsChanged() { - mAgendaListView.refresh(true); - } - - @Override - public long getSupportedEventTypes() { - return EventType.GO_TO | EventType.EVENTS_CHANGED | EventType.SEARCH; - } - - @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 - goTo(event.startTime, true); - } else if (event.eventType == EventType.SEARCH) { - search(event.query, event.startTime); - } else if (event.eventType == EventType.EVENTS_CHANGED) { - eventsChanged(); - } - } -} - diff --git a/src/com/android/calendar/AgendaItemView.java b/src/com/android/calendar/AgendaItemView.java deleted file mode 100644 index 7419e1ad..00000000 --- a/src/com/android/calendar/AgendaItemView.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.widget.RelativeLayout; - -import com.android.calendar.AgendaAdapter.ViewHolder; - -/** - * A custom layout for each item in the Agenda list view. - */ -public class AgendaItemView extends RelativeLayout { - Paint mPaint = new Paint(); - - public AgendaItemView(Context context) { - super(context); - } - - public AgendaItemView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - ViewHolder holder = (ViewHolder) getTag(); - if (holder != null) { - /* Draw vertical color stripe */ - mPaint.setColor(holder.calendarColor); - canvas.drawRect(0, 0, 5, getHeight(), mPaint); - - /* Gray out item if the event was declined */ - if (holder.overLayColor != 0) { - mPaint.setColor(holder.overLayColor); - canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); - } - } - } -} diff --git a/src/com/android/calendar/AgendaListView.java b/src/com/android/calendar/AgendaListView.java deleted file mode 100644 index 884a3fc0..00000000 --- a/src/com/android/calendar/AgendaListView.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import com.android.calendar.AgendaAdapter.ViewHolder; -import com.android.calendar.AgendaWindowAdapter.EventInfo; -import com.android.calendar.CalendarController.EventType; - -import android.content.Context; -import android.graphics.Rect; -import android.text.format.Time; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.AdapterView.OnItemClickListener; - -public class AgendaListView extends ListView implements OnItemClickListener { - - private static final String TAG = "AgendaListView"; - private static final boolean DEBUG = false; - - private AgendaWindowAdapter mWindowAdapter; - private DeleteEventHelper mDeleteEventHelper; - private Context mContext; - - - public AgendaListView(Context context) { - super(context, null); - mContext = context; - setOnItemClickListener(this); - setChoiceMode(ListView.CHOICE_MODE_SINGLE); - setVerticalScrollBarEnabled(false); - mWindowAdapter = new AgendaWindowAdapter(context, this); - setAdapter(mWindowAdapter); - mDeleteEventHelper = - new DeleteEventHelper(context, null, false /* don't exit when done */); - } - - @Override protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mWindowAdapter.close(); - } - - // Implementation of the interface OnItemClickListener - public void onItemClick(AdapterView a, View v, int position, long id) { - if (id != -1) { - // Switch to the EventInfo view - EventInfo event = mWindowAdapter.getEventByPosition(position); - if (event != null) { - CalendarController.getInstance(mContext).sendEventRelatedEvent(this, - EventType.VIEW_EVENT, event.id, event.begin, event.end, 0, 0); - } - } - } - - public void goTo(Time time, String searchQuery, boolean forced) { - if (time == null) { - time = new Time(); - long goToTime = getFirstVisibleTime(); - if (goToTime <= 0) { - goToTime = System.currentTimeMillis(); - } - time.set(goToTime); - } - mWindowAdapter.refresh(time, searchQuery, forced); - } - - public void refresh(boolean forced) { - Time time = new Time(); - long goToTime = getFirstVisibleTime(); - if (goToTime <= 0) { - goToTime = System.currentTimeMillis(); - } - time.set(goToTime); - mWindowAdapter.refresh(time, null, forced); - } - - public void deleteSelectedEvent() { - int position = getSelectedItemPosition(); - EventInfo event = mWindowAdapter.getEventByPosition(position); - if (event != null) { - mDeleteEventHelper.delete(event.begin, event.end, event.id, -1); - } - } - - @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(); - for (int i = 0; i < childCount; ++i) { - View listItem = getChildAt(i); - listItem.getLocalVisibleRect(r); - if (r.top >= 0) { // if visible - return listItem; - } - } - return null; - } - - public long getSelectedTime() { - int position = getSelectedItemPosition(); - if (position >= 0) { - EventInfo event = mWindowAdapter.getEventByPosition(position); - if (event != null) { - return event.begin; - } - } - return getFirstVisibleTime(); - } - - public long getFirstVisibleTime() { - int position = getFirstVisiblePosition(); - if (DEBUG) { - Log.v(TAG, "getFirstVisiblePosition = " + position); - } - - EventInfo event = mWindowAdapter.getEventByPosition(position); - if (event != null) { - return event.begin; - } - return 0; - } - - // Move the currently selected or visible focus down by offset amount. - // offset could be negative. - public void shiftSelection(int offset) { - shiftPosition(offset); - int position = getSelectedItemPosition(); - if (position != INVALID_POSITION) { - setSelectionFromTop(position + offset, 0); - } - } - - private void shiftPosition(int offset) { - if (DEBUG) { - Log.v(TAG, "Shifting position "+ offset); - } - - View firstVisibleItem = getFirstVisibleView(); - - if (firstVisibleItem != null) { - Rect r = new Rect(); - firstVisibleItem.getLocalVisibleRect(r); - // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is - // returning an item above the first visible item. - int position = getPositionForView(firstVisibleItem); - setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); - if (DEBUG) { - if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { - ViewHolder viewHolder = (AgendaAdapter.ViewHolder)firstVisibleItem.getTag(); - Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " - + viewHolder.title.getText()); - } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { - AgendaByDayAdapter.ViewHolder viewHolder = - (AgendaByDayAdapter.ViewHolder)firstVisibleItem.getTag(); - Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " - + viewHolder.dateView.getText()); - } else if (firstVisibleItem instanceof TextView) { - Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); - } - } - } else if (getSelectedItemPosition() >= 0) { - if (DEBUG) { - Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + " by " + offset); - } - setSelection(getSelectedItemPosition() + offset); - } - } - - public void setHideDeclinedEvents(boolean hideDeclined) { - mWindowAdapter.setHideDeclinedEvents(hideDeclined); - } - - public void onResume() { - mWindowAdapter.notifyDataSetChanged(); - } - public void onPause() { - mWindowAdapter.notifyDataSetInvalidated(); - } -} diff --git a/src/com/android/calendar/AgendaWindowAdapter.java b/src/com/android/calendar/AgendaWindowAdapter.java deleted file mode 100644 index ce9c29ee..00000000 --- a/src/com/android/calendar/AgendaWindowAdapter.java +++ /dev/null @@ -1,921 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.calendar; - -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.Calendar; -import android.provider.Calendar.Attendees; -import android.provider.Calendar.Calendars; -import android.provider.Calendar.Instances; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.OnClickListener; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import java.util.Formatter; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; -import java.util.concurrent.ConcurrentLinkedQueue; - -/* -Bugs Bugs Bugs: -- At rotation and launch time, the initial position is not set properly. This code is calling - listview.setSelection() in 2 rapid secessions but it dropped or didn't process the first one. -- Scroll using trackball isn't repositioning properly after a new adapter is added. -- Track ball clicks at the header/footer doesn't work. -- Potential ping pong effect if the prefetch window is big and data is limited -- Add index in calendar provider - -ToDo ToDo ToDo: -Get design of header and footer from designer - -Make scrolling smoother. -Test for correctness -Loading speed -Check for leaks and excessive allocations - */ - -public class AgendaWindowAdapter extends BaseAdapter { - - static final boolean BASICLOG = false; - static final boolean DEBUGLOG = false; - private static String TAG = "AgendaWindowAdapter"; - - private static final String AGENDA_SORT_ORDER = - Calendar.Instances.START_DAY + " ASC, " + - Calendar.Instances.BEGIN + " ASC, " + - Calendar.Events.TITLE + " ASC"; - - public static final int INDEX_TITLE = 1; - public static final int INDEX_EVENT_LOCATION = 2; - public static final int INDEX_ALL_DAY = 3; - public static final int INDEX_HAS_ALARM = 4; - public static final int INDEX_COLOR = 5; - public static final int INDEX_RRULE = 6; - public static final int INDEX_BEGIN = 7; - public static final int INDEX_END = 8; - public static final int INDEX_EVENT_ID = 9; - public static final int INDEX_START_DAY = 10; - public static final int INDEX_END_DAY = 11; - public static final int INDEX_SELF_ATTENDEE_STATUS = 12; - - private static final String[] PROJECTION = new String[] { - Instances._ID, // 0 - Instances.TITLE, // 1 - Instances.EVENT_LOCATION, // 2 - Instances.ALL_DAY, // 3 - Instances.HAS_ALARM, // 4 - Instances.COLOR, // 5 - Instances.RRULE, // 6 - Instances.BEGIN, // 7 - Instances.END, // 8 - Instances.EVENT_ID, // 9 - Instances.START_DAY, // 10 Julian start day - Instances.END_DAY, // 11 Julian end day - Instances.SELF_ATTENDEE_STATUS, // 12 - }; - - // Listview may have a bug where the index/position is not consistent when there's a header. - // TODO Need to look into this. - private static final int OFF_BY_ONE_BUG = 1; - - private static final int MAX_NUM_OF_ADAPTERS = 5; - - private static final int IDEAL_NUM_OF_EVENTS = 50; - - private static final int MIN_QUERY_DURATION = 7; // days - - private static final int MAX_QUERY_DURATION = 60; // days - - private static final int PREFETCH_BOUNDARY = 1; - - /** Times to auto-expand/retry query after getting no data */ - private static final int RETRIES_ON_NO_DATA = 1; - - private Context mContext; - - private QueryHandler mQueryHandler; - - private AgendaListView mAgendaListView; - - /** The sum of the rows in all the adapters */ - private int mRowCount; - - /** The number of times we have queried and gotten no results back */ - private int mEmptyCursorCount; - - /** Cached value of the last used adapter */ - private DayAdapterInfo mLastUsedInfo; - - private LinkedList mAdapterInfos = new LinkedList(); - - private ConcurrentLinkedQueue mQueryQueue = new ConcurrentLinkedQueue(); - - private TextView mHeaderView; - - private TextView mFooterView; - - private boolean mDoneSettingUpHeaderFooter = false; - - /** - * 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 - * mOlderRequests > mOlderRequestsProcessed. - */ - private int mOlderRequests; - - /** Number of "older" query that has been processed. */ - private int mOlderRequestsProcessed; - - /** - * When the user scrolled to the bottom, a query will be made for newer - * events and this will be incremented. Don't make more requests if - * mNewerRequests > mNewerRequestsProcessed. - */ - private int mNewerRequests; - - /** Number of "newer" query that has been processed. */ - private int mNewerRequestsProcessed; - - // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. - private Formatter mFormatter; - private StringBuilder mStringBuilder; - - private boolean mShuttingDown; - private boolean mHideDeclined; - - /** The current search query, or null if none */ - private String mSearchQuery; - - // Types of Query - private static final int QUERY_TYPE_OLDER = 0; // Query for older events - private static final int QUERY_TYPE_NEWER = 1; // Query for newer events - private static final int QUERY_TYPE_CLEAN = 2; // Delete everything and query around a date - - private static class QuerySpec { - long queryStartMillis; - - Time goToTime; - - int start; - - int end; - - String searchQuery; - - int queryType; - - public QuerySpec(int queryType) { - this.queryType = queryType; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + end; - result = prime * result + (int) (queryStartMillis ^ (queryStartMillis >>> 32)); - result = prime * result + queryType; - result = prime * result + start; - result = prime * result + searchQuery.hashCode(); - if (goToTime != null) { - long goToTimeMillis = goToTime.toMillis(false); - result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32)); - } - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - QuerySpec other = (QuerySpec) obj; - if (end != other.end || queryStartMillis != other.queryStartMillis - || queryType != other.queryType || start != other.start - || Utils.equals(searchQuery, other.searchQuery)) { - return false; - } - - if (goToTime != null) { - if (goToTime.toMillis(false) != other.goToTime.toMillis(false)) { - return false; - } - } else { - if (other.goToTime != null) { - return false; - } - } - return true; - } - } - - static class EventInfo { - long begin; - - long end; - - long id; - } - - static class DayAdapterInfo { - Cursor cursor; - - AgendaByDayAdapter dayAdapter; - - int start; // start day of the cursor's coverage - - int end; // end day of the cursor's coverage - - int offset; // offset in position in the list view - - int size; // dayAdapter.getCount() - - public DayAdapterInfo(Context context) { - dayAdapter = new AgendaByDayAdapter(context); - } - - @Override - public String toString() { - Time time = new Time(); - StringBuilder sb = new StringBuilder(); - time.setJulianDay(start); - time.normalize(false); - sb.append("Start:").append(time.toString()); - time.setJulianDay(end); - time.normalize(false); - sb.append(" End:").append(time.toString()); - sb.append(" Offset:").append(offset); - sb.append(" Size:").append(size); - return sb.toString(); - } - } - - public AgendaWindowAdapter(Context context, - AgendaListView agendaListView) { - mContext = context; - mAgendaListView = agendaListView; - mQueryHandler = new QueryHandler(context.getContentResolver()); - - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - - mSearchQuery = null; - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mHeaderView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); - mFooterView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); - mHeaderView.setText(R.string.loading); - mAgendaListView.addHeaderView(mHeaderView); - } - - // Method in Adapter - @Override - public int getViewTypeCount() { - return AgendaByDayAdapter.TYPE_LAST; - } - - // Method in BaseAdapter - @Override - public boolean areAllItemsEnabled() { - return false; - } - - // Method in Adapter - @Override - public int getItemViewType(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getItemViewType(position - info.offset); - } else { - return -1; - } - } - - // Method in BaseAdapter - @Override - public boolean isEnabled(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.isEnabled(position - info.offset); - } else { - return false; - } - } - - // Abstract Method in BaseAdapter - public int getCount() { - return mRowCount; - } - - // Abstract Method in BaseAdapter - public Object getItem(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return info.dayAdapter.getItem(position - info.offset); - } else { - return null; - } - } - - // Method in BaseAdapter - @Override - public boolean hasStableIds() { - return true; - } - - // Abstract Method in BaseAdapter - public long getItemId(int position) { - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - return ((position - info.offset) << 20) + info.start ; - } else { - return -1; - } - } - - // Abstract Method in BaseAdapter - public View getView(int position, View convertView, ViewGroup parent) { - if (position >= (mRowCount - PREFETCH_BOUNDARY) - && mNewerRequests <= mNewerRequestsProcessed) { - if (DEBUGLOG) Log.e(TAG, "queryForNewerEvents: "); - mNewerRequests++; - queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); - } - - if (position < PREFETCH_BOUNDARY - && mOlderRequests <= mOlderRequestsProcessed) { - if (DEBUGLOG) Log.e(TAG, "queryForOlderEvents: "); - mOlderRequests++; - queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); - } - - View v; - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info != null) { - v = info.dayAdapter.getView(position - info.offset, convertView, - parent); - } else { - //TODO - Log.e(TAG, "BUG: getAdapterInfoByPosition returned null!!! " + position); - TextView tv = new TextView(mContext); - tv.setText("Bug! " + position); - v = tv; - } - - if (DEBUGLOG) { - Log.e(TAG, "getView " + position + " = " + getViewTitle(v)); - } - return v; - } - - private int findDayPositionNearestTime(Time time) { - if (DEBUGLOG) Log.e(TAG, "findDayPositionNearestTime " + time); - - DayAdapterInfo info = getAdapterInfoByTime(time); - if (info != null) { - return info.offset + info.dayAdapter.findDayPositionNearestTime(time); - } else { - return -1; - } - } - - private DayAdapterInfo getAdapterInfoByPosition(int position) { - synchronized (mAdapterInfos) { - if (mLastUsedInfo != null && mLastUsedInfo.offset <= position - && position < (mLastUsedInfo.offset + mLastUsedInfo.size)) { - return mLastUsedInfo; - } - for (DayAdapterInfo info : mAdapterInfos) { - if (info.offset <= position - && position < (info.offset + info.size)) { - mLastUsedInfo = info; - return info; - } - } - } - return null; - } - - private DayAdapterInfo getAdapterInfoByTime(Time time) { - if (DEBUGLOG) Log.e(TAG, "getAdapterInfoByTime " + time.toString()); - - Time tmpTime = new Time(time); - long timeInMillis = tmpTime.normalize(true); - int day = Time.getJulianDay(timeInMillis, tmpTime.gmtoff); - synchronized (mAdapterInfos) { - for (DayAdapterInfo info : mAdapterInfos) { - if (info.start <= day && day < info.end) { - return info; - } - } - } - return null; - } - - public EventInfo getEventByPosition(int position) { - if (DEBUGLOG) Log.e(TAG, "getEventByPosition " + position); - - EventInfo event = new EventInfo(); - position -= OFF_BY_ONE_BUG; - DayAdapterInfo info = getAdapterInfoByPosition(position); - if (info == null) { - return null; - } - - position = info.dayAdapter.getCursorPosition(position - info.offset); - if (position == Integer.MIN_VALUE) { - return null; - } - - boolean isDayHeader = false; - if (position < 0) { - position = -position; - isDayHeader = true; - } - - if (position < info.cursor.getCount()) { - info.cursor.moveToPosition(position); - event.begin = info.cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - boolean allDay = info.cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; - - if (allDay) { // UTC - Time time = new Time(); - time.setJulianDay(Time.getJulianDay(event.begin, 0)); - event.begin = time.toMillis(false /* use isDst */); - } else if (isDayHeader) { // Trim to midnight. - Time time = new Time(); - time.set(event.begin); - time.hour = 0; - time.minute = 0; - time.second = 0; - event.begin = time.toMillis(false /* use isDst */); - } - - if (!isDayHeader) { - event.end = info.cursor.getLong(AgendaWindowAdapter.INDEX_END); - event.id = info.cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); - } - return event; - } - return null; - } - - public void refresh(Time goToTime, String searchQuery, boolean forced) { - if (searchQuery != null) { - mSearchQuery = searchQuery; - } - - if (DEBUGLOG) { - Log.e(TAG, "refresh " + goToTime.toString() + (forced ? " forced" : " not forced")); - } - - int startDay = Time.getJulianDay(goToTime.toMillis(false), goToTime.gmtoff); - - if (!forced && isInRange(startDay, startDay)) { - // No need to requery - mAgendaListView.setSelection(findDayPositionNearestTime(goToTime) + OFF_BY_ONE_BUG); - return; - } - - // Query for a total of MIN_QUERY_DURATION days - int endDay = startDay + MIN_QUERY_DURATION; - - queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN); - } - - public void close() { - mShuttingDown = true; - pruneAdapterInfo(QUERY_TYPE_CLEAN); - if (mQueryHandler != null) { - mQueryHandler.cancelOperation(0); - } - } - - private DayAdapterInfo pruneAdapterInfo(int queryType) { - synchronized (mAdapterInfos) { - DayAdapterInfo recycleMe = null; - if (!mAdapterInfos.isEmpty()) { - if (mAdapterInfos.size() >= MAX_NUM_OF_ADAPTERS) { - if (queryType == QUERY_TYPE_NEWER) { - recycleMe = mAdapterInfos.removeFirst(); - } else if (queryType == QUERY_TYPE_OLDER) { - recycleMe = mAdapterInfos.removeLast(); - // Keep the size only if the oldest items are removed. - recycleMe.size = 0; - } - if (recycleMe != null) { - if (recycleMe.cursor != null) { - recycleMe.cursor.close(); - } - return recycleMe; - } - } - - if (mRowCount == 0 || queryType == QUERY_TYPE_CLEAN) { - mRowCount = 0; - int deletedRows = 0; - DayAdapterInfo info; - do { - info = mAdapterInfos.poll(); - if (info != null) { - // TODO the following causes ANR's. Do this in a thread. - info.cursor.close(); - deletedRows += info.size; - recycleMe = info; - } - } while (info != null); - - if (recycleMe != null) { - recycleMe.cursor = null; - recycleMe.size = deletedRows; - } - } - } - return recycleMe; - } - } - - private String buildQuerySelection() { - // Respect the preference to show/hide declined events - - if (mHideDeclined) { - return Calendars.SELECTED + "=1 AND " - + Instances.SELF_ATTENDEE_STATUS + "!=" - + Attendees.ATTENDEE_STATUS_DECLINED; - } else { - return Calendars.SELECTED + "=1"; - } - } - - private Uri buildQueryUri(int start, int end, String searchQuery) { - Uri rootUri = searchQuery == null ? - Instances.CONTENT_BY_DAY_URI : - Instances.CONTENT_SEARCH_BY_DAY_URI; - Uri.Builder builder = rootUri.buildUpon(); - ContentUris.appendId(builder, start); - ContentUris.appendId(builder, end); - if (searchQuery != null) { - builder.appendPath(searchQuery); - } - return builder.build(); - } - - private boolean isInRange(int start, int end) { - synchronized (mAdapterInfos) { - if (mAdapterInfos.isEmpty()) { - return false; - } - return mAdapterInfos.getFirst().start <= start && end <= mAdapterInfos.getLast().end; - } - } - - private int calculateQueryDuration(int start, int end) { - int queryDuration = MAX_QUERY_DURATION; - if (mRowCount != 0) { - queryDuration = IDEAL_NUM_OF_EVENTS * (end - start + 1) / mRowCount; - } - - if (queryDuration > MAX_QUERY_DURATION) { - queryDuration = MAX_QUERY_DURATION; - } else if (queryDuration < MIN_QUERY_DURATION) { - queryDuration = MIN_QUERY_DURATION; - } - - return queryDuration; - } - - private boolean queueQuery(int start, int end, Time goToTime, - String searchQuery, int queryType) { - QuerySpec queryData = new QuerySpec(queryType); - queryData.goToTime = goToTime; - queryData.start = start; - queryData.end = end; - queryData.searchQuery = searchQuery; - return queueQuery(queryData); - } - - private boolean queueQuery(QuerySpec queryData) { - queryData.searchQuery = mSearchQuery; - Boolean queuedQuery; - synchronized (mQueryQueue) { - queuedQuery = false; - Boolean doQueryNow = mQueryQueue.isEmpty(); - mQueryQueue.add(queryData); - queuedQuery = true; - if (doQueryNow) { - doQuery(queryData); - } - } - return queuedQuery; - } - - private void doQuery(QuerySpec queryData) { - if (!mAdapterInfos.isEmpty()) { - int start = mAdapterInfos.getFirst().start; - int end = mAdapterInfos.getLast().end; - int queryDuration = calculateQueryDuration(start, end); - switch(queryData.queryType) { - case QUERY_TYPE_OLDER: - queryData.end = start - 1; - queryData.start = queryData.end - queryDuration; - break; - case QUERY_TYPE_NEWER: - queryData.start = end + 1; - queryData.end = queryData.start + queryDuration; - break; - } - } - - if (BASICLOG) { - Time time = new Time(); - time.setJulianDay(queryData.start); - Time time2 = new Time(); - time2.setJulianDay(queryData.end); - Log.v(TAG, "startQuery: " + time.toString() + " to " - + time2.toString() + " then go to " + queryData.goToTime); - } - - mQueryHandler.cancelOperation(0); - if (BASICLOG) queryData.queryStartMillis = System.nanoTime(); - - Uri queryUri = buildQueryUri( - queryData.start, queryData.end, queryData.searchQuery); - mQueryHandler.startQuery(0, queryData, queryUri, - PROJECTION, buildQuerySelection(), null, - AGENDA_SORT_ORDER); - } - - private String formatDateString(int julianDay) { - Time time = new Time(); - time.setJulianDay(julianDay); - long millis = time.toMillis(false); - mStringBuilder.setLength(0); - return DateUtils.formatDateRange(mContext, mFormatter, millis, millis, - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_ABBREV_MONTH).toString(); - } - - private void updateHeaderFooter(final int start, final int end) { - mHeaderView.setText(mContext.getString(R.string.show_older_events, - formatDateString(start))); - mFooterView.setText(mContext.getString(R.string.show_newer_events, - formatDateString(end))); - } - - private class QueryHandler extends AsyncQueryHandler { - - public QueryHandler(ContentResolver cr) { - super(cr); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - QuerySpec data = (QuerySpec)cookie; - if (BASICLOG) { - long queryEndMillis = System.nanoTime(); - Log.e(TAG, "Query time(ms): " - + (queryEndMillis - data.queryStartMillis) / 1000000 - + " Count: " + cursor.getCount()); - } - - if (mShuttingDown) { - cursor.close(); - return; - } - - // Notify Listview of changes and update position - int cursorSize = cursor.getCount(); - if (cursorSize > 0 || mAdapterInfos.isEmpty() || data.queryType == QUERY_TYPE_CLEAN) { - final int listPositionOffset = processNewCursor(data, cursor); - if (data.goToTime == null) { // Typical Scrolling type query - notifyDataSetChanged(); - if (listPositionOffset != 0) { - mAgendaListView.shiftSelection(listPositionOffset); - } - } else { // refresh() called. Go to the designated position - final Time goToTime = data.goToTime; - notifyDataSetChanged(); - int newPosition = findDayPositionNearestTime(goToTime); - if (newPosition >= 0) { - mAgendaListView.setSelection(newPosition + OFF_BY_ONE_BUG); - } - if (DEBUGLOG) { - Log.e(TAG, "Setting listview to " + - "findDayPositionNearestTime: " + (newPosition + OFF_BY_ONE_BUG)); - } - } - } else { - cursor.close(); - } - - // Update header and footer - if (!mDoneSettingUpHeaderFooter) { - OnClickListener headerFooterOnClickListener = new OnClickListener() { - public void onClick(View v) { - if (v == mHeaderView) { - queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); - } else { - queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); - } - }}; - mHeaderView.setOnClickListener(headerFooterOnClickListener); - mFooterView.setOnClickListener(headerFooterOnClickListener); - mAgendaListView.addFooterView(mFooterView); - mDoneSettingUpHeaderFooter = true; - } - synchronized (mQueryQueue) { - int totalAgendaRangeStart = -1; - int totalAgendaRangeEnd = -1; - - if (cursorSize != 0) { - // Remove the query that just completed - QuerySpec x = mQueryQueue.poll(); - if (BASICLOG && !x.equals(data)) { - Log.e(TAG, "onQueryComplete - cookie != head of queue"); - } - mEmptyCursorCount = 0; - if (data.queryType == QUERY_TYPE_NEWER) { - mNewerRequestsProcessed++; - } else if (data.queryType == QUERY_TYPE_OLDER) { - mOlderRequestsProcessed++; - } - - totalAgendaRangeStart = mAdapterInfos.getFirst().start; - totalAgendaRangeEnd = mAdapterInfos.getLast().end; - } else { // CursorSize == 0 - QuerySpec querySpec = mQueryQueue.peek(); - - // Update Adapter Info with new start and end date range - if (!mAdapterInfos.isEmpty()) { - DayAdapterInfo first = mAdapterInfos.getFirst(); - DayAdapterInfo last = mAdapterInfos.getLast(); - - if (first.start - 1 <= querySpec.end && querySpec.start < first.start) { - first.start = querySpec.start; - } - - if (querySpec.start <= last.end + 1 && last.end < querySpec.end) { - last.end = querySpec.end; - } - - totalAgendaRangeStart = first.start; - totalAgendaRangeEnd = last.end; - } else { - totalAgendaRangeStart = querySpec.start; - totalAgendaRangeEnd = querySpec.end; - } - - // Update query specification with expanded search range - // and maybe rerun query - switch (querySpec.queryType) { - case QUERY_TYPE_OLDER: - totalAgendaRangeStart = querySpec.start; - querySpec.start -= MAX_QUERY_DURATION; - break; - case QUERY_TYPE_NEWER: - totalAgendaRangeEnd = querySpec.end; - querySpec.end += MAX_QUERY_DURATION; - break; - case QUERY_TYPE_CLEAN: - totalAgendaRangeStart = querySpec.start; - totalAgendaRangeEnd = querySpec.end; - querySpec.start -= MAX_QUERY_DURATION / 2; - querySpec.end += MAX_QUERY_DURATION / 2; - break; - } - - if (++mEmptyCursorCount > RETRIES_ON_NO_DATA) { - // Nothing in the cursor again. Dropping query - mQueryQueue.poll(); - } - } - - updateHeaderFooter(totalAgendaRangeStart, totalAgendaRangeEnd); - - // Fire off the next query if any - Iterator it = mQueryQueue.iterator(); - while (it.hasNext()) { - QuerySpec queryData = it.next(); - if (!isInRange(queryData.start, queryData.end)) { - // Query accepted - if (DEBUGLOG) Log.e(TAG, "Query accepted. QueueSize:" + mQueryQueue.size()); - doQuery(queryData); - break; - } else { - // Query rejected - it.remove(); - if (DEBUGLOG) Log.e(TAG, "Query rejected. QueueSize:" + mQueryQueue.size()); - } - } - } - if (BASICLOG) { - for (DayAdapterInfo info3 : mAdapterInfos) { - Log.e(TAG, "> " + info3.toString()); - } - } - } - - /* - * Update the adapter info array with a the new cursor. Close out old - * cursors as needed. - * - * @return number of rows removed from the beginning - */ - private int processNewCursor(QuerySpec data, Cursor cursor) { - synchronized (mAdapterInfos) { - // Remove adapter info's from adapterInfos as needed - DayAdapterInfo info = pruneAdapterInfo(data.queryType); - int listPositionOffset = 0; - if (info == null) { - info = new DayAdapterInfo(mContext); - } else { - if (DEBUGLOG) - Log.e(TAG, "processNewCursor listPositionOffsetA=" - + -info.size); - listPositionOffset = -info.size; - } - - // Setup adapter info - info.start = data.start; - info.end = data.end; - info.cursor = cursor; - info.dayAdapter.changeCursor(info); - info.size = info.dayAdapter.getCount(); - - // Insert into adapterInfos - if (mAdapterInfos.isEmpty() - || data.end <= mAdapterInfos.getFirst().start) { - mAdapterInfos.addFirst(info); - listPositionOffset += info.size; - } else if (BASICLOG && data.start < mAdapterInfos.getLast().end) { - mAdapterInfos.addLast(info); - for (DayAdapterInfo info2 : mAdapterInfos) { - Log.e("========== BUG ==", info2.toString()); - } - } else { - mAdapterInfos.addLast(info); - } - - // Update offsets in adapterInfos - mRowCount = 0; - for (DayAdapterInfo info3 : mAdapterInfos) { - info3.offset = mRowCount; - mRowCount += info3.size; - } - mLastUsedInfo = null; - - return listPositionOffset; - } - } - } - - static String getViewTitle(View x) { - String title = ""; - if (x != null) { - Object yy = x.getTag(); - if (yy instanceof AgendaAdapter.ViewHolder) { - TextView tv = ((AgendaAdapter.ViewHolder) yy).title; - if (tv != null) { - title = (String) tv.getText(); - } - } else if (yy != null) { - TextView dateView = ((AgendaByDayAdapter.ViewHolder) yy).dateView; - if (dateView != null) { - title = (String) dateView.getText(); - } - } - } - return title; - } - - public void setHideDeclinedEvents(boolean hideDeclined) { - mHideDeclined = hideDeclined; - } -} diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java index b9f30460..b5eb991a 100644 --- a/src/com/android/calendar/AllInOneActivity.java +++ b/src/com/android/calendar/AllInOneActivity.java @@ -20,6 +20,7 @@ import com.android.calendar.CalendarController.EventHandler; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarController.ViewType; +import com.android.calendar.agenda.AgendaFragment; import com.android.calendar.selectcalendars.SelectCalendarsFragment; import android.app.ActionBar; diff --git a/src/com/android/calendar/CalendarController.java b/src/com/android/calendar/CalendarController.java index cd0d6e7d..b80ccaa5 100644 --- a/src/com/android/calendar/CalendarController.java +++ b/src/com/android/calendar/CalendarController.java @@ -103,15 +103,15 @@ public class CalendarController { } public static class EventInfo { - long eventType; // one of the EventType - int viewType; // one of the ViewType - long id; // event id - Time selectedTime; // the selected time in focus - Time startTime; // start of a range of time. - Time endTime; // end of a range of time. - int x; // x coordinate in the activity space - int y; // y coordinate in the activity space - String query; // query for a user search + public long eventType; // one of the EventType + public int viewType; // one of the ViewType + public long id; // event id + public Time selectedTime; // the selected time in focus + public Time startTime; // start of a range of time. + public Time endTime; // end of a range of time. + public int x; // x coordinate in the activity space + public int y; // y coordinate in the activity space + public String query; // query for a user search } // FRAG_TODO remove unneeded api's diff --git a/src/com/android/calendar/SearchActivity.java b/src/com/android/calendar/SearchActivity.java index 4239739d..0fdfca2c 100644 --- a/src/com/android/calendar/SearchActivity.java +++ b/src/com/android/calendar/SearchActivity.java @@ -21,6 +21,7 @@ import static android.provider.Calendar.EVENT_END_TIME; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarController.ViewType; +import com.android.calendar.agenda.AgendaFragment; import dalvik.system.VMRuntime; diff --git a/src/com/android/calendar/agenda/AgendaAdapter.java b/src/com/android/calendar/agenda/AgendaAdapter.java new file mode 100644 index 00000000..972b1c52 --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaAdapter.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import com.android.calendar.R; + +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.provider.Calendar.Attendees; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.ResourceCursorAdapter; +import android.widget.TextView; + +import java.util.Formatter; +import java.util.Locale; + +public class AgendaAdapter extends ResourceCursorAdapter { + private String mNoTitleLabel; + private Resources mResources; + private int mDeclinedColor; + // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. + private Formatter mFormatter; + private StringBuilder mStringBuilder; + + static class ViewHolder { + int overLayColor; // Used by AgendaItemView to gray out the entire item if so desired + + /* Event */ + TextView title; + TextView when; + TextView where; + int calendarColor; // Used by AgendaItemView to color the vertical stripe + } + + public AgendaAdapter(Context context, int resource) { + super(context, resource, null); + mResources = context.getResources(); + mNoTitleLabel = mResources.getString(R.string.no_title_label); + mDeclinedColor = mResources.getColor(R.drawable.agenda_item_declined); + mStringBuilder = new StringBuilder(50); + mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ViewHolder holder = null; + + // Listview may get confused and pass in a different type of view since + // we keep shifting data around. Not a big problem. + Object tag = view.getTag(); + if (tag instanceof ViewHolder) { + holder = (ViewHolder) view.getTag(); + } + + if (holder == null) { + holder = new ViewHolder(); + view.setTag(holder); + holder.title = (TextView) view.findViewById(R.id.title); + holder.when = (TextView) view.findViewById(R.id.when); + holder.where = (TextView) view.findViewById(R.id.where); + } + + // Fade text if event was declined. + int selfAttendeeStatus = cursor.getInt(AgendaWindowAdapter.INDEX_SELF_ATTENDEE_STATUS); + if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) { + holder.overLayColor = mDeclinedColor; + } else { + holder.overLayColor = 0; + } + + TextView title = holder.title; + TextView when = holder.when; + TextView where = holder.where; + + /* Calendar Color */ + int color = cursor.getInt(AgendaWindowAdapter.INDEX_COLOR); + holder.calendarColor = color; + + // What + String titleString = cursor.getString(AgendaWindowAdapter.INDEX_TITLE); + if (titleString == null || titleString.length() == 0) { + titleString = mNoTitleLabel; + } + title.setText(titleString); + title.setTextColor(color); + + // When + long begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); + long end = cursor.getLong(AgendaWindowAdapter.INDEX_END); + boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; + int flags; + String whenString; + if (allDay) { + flags = DateUtils.FORMAT_UTC; + } else { + flags = DateUtils.FORMAT_SHOW_TIME; + } + if (DateFormat.is24HourFormat(context)) { + flags |= DateUtils.FORMAT_24HOUR; + } + mStringBuilder.setLength(0); + whenString = DateUtils.formatDateRange(context, mFormatter, begin, end, flags).toString(); + when.setText(whenString); + + String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE); + if (rrule != null) { + when.setCompoundDrawablesWithIntrinsicBounds(null, null, + context.getResources().getDrawable(R.drawable.ic_repeat_dark), null); + when.setCompoundDrawablePadding(5); + } else { + when.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + /* + // Repeating info + View repeatContainer = view.findViewById(R.id.repeat_icon); + String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE); + if (rrule != null) { + repeatContainer.setVisibility(View.VISIBLE); + } else { + repeatContainer.setVisibility(View.GONE); + } + */ + + /* + // Reminder + boolean hasAlarm = cursor.getInt(AgendaWindowAdapter.INDEX_HAS_ALARM) != 0; + if (hasAlarm) { + updateReminder(view, context, begin, cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID)); + } + */ + + // Where + String whereString = cursor.getString(AgendaWindowAdapter.INDEX_EVENT_LOCATION); + if (whereString != null && whereString.length() > 0) { + where.setVisibility(View.VISIBLE); + where.setText(whereString); + } else { + where.setVisibility(View.GONE); + } + } + + /* + public static void updateReminder(View view, Context context, long begin, long eventId) { + ContentResolver cr = context.getContentResolver(); + Uri uri = Reminders.CONTENT_URI; + String where = String.format(REMINDERS_WHERE, eventId); + + Cursor remindersCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null); + if (remindersCursor != null) { + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LinearLayout parent = (LinearLayout) view.findViewById(R.id.reminders_container); + parent.removeAllViews(); + while (remindersCursor.moveToNext()) { + int alarm = remindersCursor.getInt(REMINDERS_INDEX_MINUTES); + String before = EditEvent.constructReminderLabel(context, alarm, true); + LinearLayout reminderItem = (LinearLayout) + inflater.inflate(R.layout.agenda_reminder_item, null); + TextView reminderItemText = (TextView) reminderItem.findViewById(R.id.reminder); + reminderItemText.setText(before); + parent.addView(reminderItem); + } + } + remindersCursor.close(); + } + */ +} + diff --git a/src/com/android/calendar/agenda/AgendaByDayAdapter.java b/src/com/android/calendar/agenda/AgendaByDayAdapter.java new file mode 100644 index 00000000..cf2ca5c3 --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaByDayAdapter.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import android.content.Context; +import android.database.Cursor; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; +import com.android.calendar.R; + +import java.util.ArrayList; +import java.util.Formatter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Locale; + +public class AgendaByDayAdapter extends BaseAdapter { + private static final int TYPE_DAY = 0; + private static final int TYPE_MEETING = 1; + static final int TYPE_LAST = 2; + + private final Context mContext; + private final AgendaAdapter mAgendaAdapter; + private final LayoutInflater mInflater; + private ArrayList mRowInfo; + private int mTodayJulianDay; + private Time mTmpTime = new Time(); + // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. + private Formatter mFormatter; + private StringBuilder mStringBuilder; + + static class ViewHolder { + TextView dateView; + } + + public AgendaByDayAdapter(Context context) { + mContext = context; + mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item); + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mStringBuilder = new StringBuilder(50); + mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); + } + + public int getCount() { + if (mRowInfo != null) { + return mRowInfo.size(); + } + return mAgendaAdapter.getCount(); + } + + public Object getItem(int position) { + if (mRowInfo != null) { + RowInfo row = mRowInfo.get(position); + if (row.mType == TYPE_DAY) { + return row; + } else { + return mAgendaAdapter.getItem(row.mData); + } + } + return mAgendaAdapter.getItem(position); + } + + public long getItemId(int position) { + if (mRowInfo != null) { + RowInfo row = mRowInfo.get(position); + if (row.mType == TYPE_DAY) { + return -position; + } else { + return mAgendaAdapter.getItemId(row.mData); + } + } + return mAgendaAdapter.getItemId(position); + } + + @Override + public int getViewTypeCount() { + return TYPE_LAST; + } + + @Override + public int getItemViewType(int position) { + return mRowInfo != null && mRowInfo.size() > position ? + mRowInfo.get(position).mType : TYPE_DAY; + } + + public View getView(int position, View convertView, ViewGroup parent) { + if ((mRowInfo == null) || (position > mRowInfo.size())) { + // If we have no row info, mAgendaAdapter returns the view. + return mAgendaAdapter.getView(position, convertView, parent); + } + + RowInfo row = mRowInfo.get(position); + if (row.mType == TYPE_DAY) { + ViewHolder holder = null; + View agendaDayView = null; + if ((convertView != null) && (convertView.getTag() != null)) { + // Listview may get confused and pass in a different type of + // view since we keep shifting data around. Not a big problem. + Object tag = convertView.getTag(); + if (tag instanceof ViewHolder) { + agendaDayView = convertView; + holder = (ViewHolder) tag; + } + } + + if (holder == null) { + // Create a new AgendaView with a ViewHolder for fast access to + // views w/o calling findViewById() + holder = new ViewHolder(); + agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false); + holder.dateView = (TextView) agendaDayView.findViewById(R.id.date); + agendaDayView.setTag(holder); + } + + // Re-use the member variable "mTime" which is set to the local timezone. + Time date = mTmpTime; + long millis = date.setJulianDay(row.mData); + int flags = DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_SHOW_DATE; + + mStringBuilder.setLength(0); + String dateViewText; + if (row.mData == mTodayJulianDay) { + dateViewText = mContext.getString(R.string.agenda_today, DateUtils.formatDateRange( + mContext, mFormatter, millis, millis, flags).toString()); + } else { + flags |= DateUtils.FORMAT_SHOW_WEEKDAY; + dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis, + flags).toString(); + } + + if (AgendaWindowAdapter.BASICLOG) { + dateViewText += " P:" + position; + } + holder.dateView.setText(dateViewText); + + return agendaDayView; + } else if (row.mType == TYPE_MEETING) { + View x = mAgendaAdapter.getView(row.mData, convertView, parent); + TextView y = ((AgendaAdapter.ViewHolder) x.getTag()).title; + if (AgendaWindowAdapter.BASICLOG) { + y.setText(y.getText() + " P:" + position); + } else { + y.setText(y.getText()); + } + return x; + } else { + // Error + throw new IllegalStateException("Unknown event type:" + row.mType); + } + } + + public void clearDayHeaderInfo() { + mRowInfo = null; + } + + public void changeCursor(DayAdapterInfo info) { + calculateDays(info); + mAgendaAdapter.changeCursor(info.cursor); + } + + public void calculateDays(DayAdapterInfo dayAdapterInfo) { + Cursor cursor = dayAdapterInfo.cursor; + ArrayList rowInfo = new ArrayList(); + int prevStartDay = -1; + Time time = new Time(); + long now = System.currentTimeMillis(); + time.set(now); + mTodayJulianDay = Time.getJulianDay(now, time.gmtoff); + LinkedList multipleDayList = new LinkedList(); + for (int position = 0; cursor.moveToNext(); position++) { + int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); + + // 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)); + } else { + // If there are any multiple-day events that span the empty + // range of days, then create day headers and events for + // those multiple-day events. + boolean dayHeaderAdded = false; + for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) { + dayHeaderAdded = false; + Iterator iter = multipleDayList.iterator(); + while (iter.hasNext()) { + MultipleDayInfo info = iter.next(); + // If this event has ended then remove it from the + // list. + if (info.mEndDay < currentDay) { + iter.remove(); + continue; + } + + // If this is the first event for the day, then + // insert a day header. + if (!dayHeaderAdded) { + rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); + dayHeaderAdded = true; + } + rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition)); + } + } + + // If the day header was not added for the start day, then + // add it now. + if (!dayHeaderAdded) { + rowInfo.add(new RowInfo(TYPE_DAY, startDay)); + } + } + prevStartDay = startDay; + } + + // Add in the event for this cursor position + rowInfo.add(new RowInfo(TYPE_MEETING, position)); + + // If this event spans multiple days, then add it to the multipleDay + // list. + int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); + + // 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)); + } + } + + // There are no more cursor events but we might still have multiple-day + // events left. So create day headers and events for those. + if (prevStartDay > 0) { + for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end; + currentDay++) { + boolean dayHeaderAdded = false; + Iterator iter = multipleDayList.iterator(); + while (iter.hasNext()) { + MultipleDayInfo info = iter.next(); + // If this event has ended then remove it from the + // list. + if (info.mEndDay < currentDay) { + iter.remove(); + continue; + } + + // If this is the first event for the day, then + // insert a day header. + if (!dayHeaderAdded) { + rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); + dayHeaderAdded = true; + } + rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition)); + } + } + } + mRowInfo = rowInfo; + } + + private static class RowInfo { + // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING) + final int mType; + + // If mType is TYPE_DAY, then mData is the Julian day. Otherwise + // mType is TYPE_MEETING and mData is the cursor position. + final int mData; + + RowInfo(int type, int data) { + mType = type; + mData = data; + } + } + + private static class MultipleDayInfo { + final int mPosition; + final int mEndDay; + + MultipleDayInfo(int position, int endDay) { + mPosition = position; + mEndDay = endDay; + } + } + + /** + * 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 + */ + public int findDayPositionNearestTime(Time time) { + 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 + int minIndex = 0; + int len = mRowInfo.size(); + for (int index = 0; index < len; index++) { + RowInfo row = mRowInfo.get(index); + if (row.mType == TYPE_DAY) { + int distance = Math.abs(julianDay - row.mData); + if (distance == 0) { + return index; + } + if (distance < minDistance) { + minDistance = distance; + minIndex = index; + } + } + } + + // We didn't find an exact match so take the nearest day that had + // events. + return minIndex; + } + + /** + * Finds the Julian day containing the event at the given position. + * + * @param position the list position of an event + * @return the Julian day containing that event + */ + public int findJulianDayFromPosition(int position) { + if (mRowInfo == null || position < 0) { + return 0; + } + + int len = mRowInfo.size(); + if (position >= len) return 0; // no row info at this position + + for (int index = position; index >= 0; index--) { + RowInfo row = mRowInfo.get(index); + if (row.mType == TYPE_DAY) { + return row.mData; + } + } + return 0; + } + + /** + * Converts a list position to a cursor position. The list contains + * day headers as well as events. The cursor contains only events. + * + * @param listPos the list position of an event + * @return the corresponding cursor position of that event + */ + public int getCursorPosition(int listPos) { + if (mRowInfo != null && listPos >= 0) { + RowInfo row = mRowInfo.get(listPos); + if (row.mType == TYPE_MEETING) { + return row.mData; + } else { + int nextPos = listPos + 1; + if (nextPos < mRowInfo.size()) { + nextPos = getCursorPosition(nextPos); + if (nextPos >= 0) { + return -nextPos; + } + } + } + } + return Integer.MIN_VALUE; + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + if (mRowInfo != null && position < mRowInfo.size()) { + RowInfo row = mRowInfo.get(position); + return row.mType == TYPE_MEETING; + } + return true; + } +} diff --git a/src/com/android/calendar/agenda/AgendaFragment.java b/src/com/android/calendar/agenda/AgendaFragment.java new file mode 100644 index 00000000..a3db52fb --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaFragment.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import android.app.Fragment; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.format.Time; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.calendar.CalendarController; +import com.android.calendar.CalendarController.EventInfo; +import com.android.calendar.CalendarController.EventType; +import com.android.calendar.CalendarPreferenceActivity; + +import dalvik.system.VMRuntime; + +public class AgendaFragment extends Fragment implements CalendarController.EventHandler { + + private static final String TAG = AgendaFragment.class.getSimpleName(); + private static boolean DEBUG = false; + + protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; + private static final long INITIAL_HEAP_SIZE = 4*1024*1024; + + private AgendaListView mAgendaListView; + private Time mTime; + + private String mQuery; + + public AgendaFragment() { + this(0); + } + + public AgendaFragment(long timeMillis) { + mTime = new Time(); + if (timeMillis == 0) { + mTime.setToNow(); + } else { + mTime.set(timeMillis); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + // Eliminate extra GCs during startup by setting the initial heap size to 4MB. + // TODO: We should restore the old heap size once the activity reaches the idle state + VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Context context = getActivity(); + mAgendaListView = new AgendaListView(context); + mAgendaListView.goTo(mTime, mQuery, false); + return mAgendaListView; + } + + @Override + public void onResume() { + super.onResume(); + if (DEBUG) { + Log.v(TAG, "OnResume to " + mTime.toString()); + } + + SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences( + getActivity()); + boolean hideDeclined = prefs.getBoolean( + CalendarPreferenceActivity.KEY_HIDE_DECLINED, false); + + mAgendaListView.setHideDeclinedEvents(hideDeclined); + mAgendaListView.goTo(mTime, mQuery, true); + mAgendaListView.onResume(); + +// // Register for Intent broadcasts +// IntentFilter filter = new IntentFilter(); +// filter.addAction(Intent.ACTION_TIME_CHANGED); +// filter.addAction(Intent.ACTION_DATE_CHANGED); +// filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); +// registerReceiver(mIntentReceiver, filter); +// +// mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + long firstVisibleTime = mAgendaListView.getFirstVisibleTime(); + if (firstVisibleTime > 0) { + mTime.set(firstVisibleTime); + outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); + if (DEBUG) { + Log.v(TAG, "onSaveInstanceState " + mTime.toString()); + } + } + } + + @Override + public void onPause() { + super.onPause(); + + mAgendaListView.onPause(); +// mContentResolver.unregisterContentObserver(mObserver); +// unregisterReceiver(mIntentReceiver); + + // Record Agenda View as the (new) default detailed view. +// Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); + } + + /* Navigator interface methods */ + @Override + public void goToToday() { + if (mAgendaListView == null) { + // The view hasn't been set yet. Just save the time and use it later. + mTime.setToNow(); + return; + } + Time now = new Time(); + now.setToNow(); + mAgendaListView.goTo(now, mQuery, true); // Force refresh + } + + @Override + public void goTo(Time time, boolean animate) { + if (mAgendaListView == null) { + // The view hasn't been set yet. Just save the time and use it + // later. + mTime.set(time); + return; + } + mAgendaListView.goTo(time, mQuery, false); + } + + private void search(String query, Time time) { + mQuery = query; + if (time != null) { + mTime.set(time); + } + if (mAgendaListView == null) { + // The view hasn't been set yet. Just return. + return; + } + mAgendaListView.goTo(time, mQuery, true); + } + + @Override + public long getSelectedTime() { + return mAgendaListView.getSelectedTime(); + } + + @Override + public boolean getAllDay() { + return false; + } + + @Override + public void eventsChanged() { + mAgendaListView.refresh(true); + } + + @Override + public long getSupportedEventTypes() { + return EventType.GO_TO | EventType.EVENTS_CHANGED | EventType.SEARCH; + } + + @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 + goTo(event.startTime, true); + } else if (event.eventType == EventType.SEARCH) { + search(event.query, event.startTime); + } else if (event.eventType == EventType.EVENTS_CHANGED) { + eventsChanged(); + } + } +} + diff --git a/src/com/android/calendar/agenda/AgendaItemView.java b/src/com/android/calendar/agenda/AgendaItemView.java new file mode 100644 index 00000000..9f314ea4 --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaItemView.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.widget.RelativeLayout; + +import com.android.calendar.agenda.AgendaAdapter.ViewHolder; + +/** + * A custom layout for each item in the Agenda list view. + */ +public class AgendaItemView extends RelativeLayout { + Paint mPaint = new Paint(); + + public AgendaItemView(Context context) { + super(context); + } + + public AgendaItemView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + ViewHolder holder = (ViewHolder) getTag(); + if (holder != null) { + /* Draw vertical color stripe */ + mPaint.setColor(holder.calendarColor); + canvas.drawRect(0, 0, 5, getHeight(), mPaint); + + /* Gray out item if the event was declined */ + if (holder.overLayColor != 0) { + mPaint.setColor(holder.overLayColor); + canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); + } + } + } +} diff --git a/src/com/android/calendar/agenda/AgendaListView.java b/src/com/android/calendar/agenda/AgendaListView.java new file mode 100644 index 00000000..ebf756f2 --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaListView.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import com.android.calendar.CalendarController; +import com.android.calendar.CalendarController.EventType; +import com.android.calendar.DeleteEventHelper; +import com.android.calendar.agenda.AgendaAdapter.ViewHolder; +import com.android.calendar.agenda.AgendaWindowAdapter.EventInfo; + +import android.content.Context; +import android.graphics.Rect; +import android.text.format.Time; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +public class AgendaListView extends ListView implements OnItemClickListener { + + private static final String TAG = "AgendaListView"; + private static final boolean DEBUG = false; + + private AgendaWindowAdapter mWindowAdapter; + private DeleteEventHelper mDeleteEventHelper; + private Context mContext; + + + public AgendaListView(Context context) { + super(context, null); + mContext = context; + setOnItemClickListener(this); + setChoiceMode(ListView.CHOICE_MODE_SINGLE); + setVerticalScrollBarEnabled(false); + mWindowAdapter = new AgendaWindowAdapter(context, this); + setAdapter(mWindowAdapter); + mDeleteEventHelper = + new DeleteEventHelper(context, null, false /* don't exit when done */); + } + + @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mWindowAdapter.close(); + } + + // Implementation of the interface OnItemClickListener + public void onItemClick(AdapterView a, View v, int position, long id) { + if (id != -1) { + // Switch to the EventInfo view + EventInfo event = mWindowAdapter.getEventByPosition(position); + if (event != null) { + CalendarController.getInstance(mContext).sendEventRelatedEvent(this, + EventType.VIEW_EVENT, event.id, event.begin, event.end, 0, 0); + } + } + } + + public void goTo(Time time, String searchQuery, boolean forced) { + if (time == null) { + time = new Time(); + long goToTime = getFirstVisibleTime(); + if (goToTime <= 0) { + goToTime = System.currentTimeMillis(); + } + time.set(goToTime); + } + mWindowAdapter.refresh(time, searchQuery, forced); + } + + public void refresh(boolean forced) { + Time time = new Time(); + long goToTime = getFirstVisibleTime(); + if (goToTime <= 0) { + goToTime = System.currentTimeMillis(); + } + time.set(goToTime); + mWindowAdapter.refresh(time, null, forced); + } + + public void deleteSelectedEvent() { + int position = getSelectedItemPosition(); + EventInfo event = mWindowAdapter.getEventByPosition(position); + if (event != null) { + mDeleteEventHelper.delete(event.begin, event.end, event.id, -1); + } + } + + @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(); + for (int i = 0; i < childCount; ++i) { + View listItem = getChildAt(i); + listItem.getLocalVisibleRect(r); + if (r.top >= 0) { // if visible + return listItem; + } + } + return null; + } + + public long getSelectedTime() { + int position = getSelectedItemPosition(); + if (position >= 0) { + EventInfo event = mWindowAdapter.getEventByPosition(position); + if (event != null) { + return event.begin; + } + } + return getFirstVisibleTime(); + } + + public long getFirstVisibleTime() { + int position = getFirstVisiblePosition(); + if (DEBUG) { + Log.v(TAG, "getFirstVisiblePosition = " + position); + } + + EventInfo event = mWindowAdapter.getEventByPosition(position); + if (event != null) { + return event.begin; + } + return 0; + } + + // Move the currently selected or visible focus down by offset amount. + // offset could be negative. + public void shiftSelection(int offset) { + shiftPosition(offset); + int position = getSelectedItemPosition(); + if (position != INVALID_POSITION) { + setSelectionFromTop(position + offset, 0); + } + } + + private void shiftPosition(int offset) { + if (DEBUG) { + Log.v(TAG, "Shifting position "+ offset); + } + + View firstVisibleItem = getFirstVisibleView(); + + if (firstVisibleItem != null) { + Rect r = new Rect(); + firstVisibleItem.getLocalVisibleRect(r); + // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is + // returning an item above the first visible item. + int position = getPositionForView(firstVisibleItem); + setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); + if (DEBUG) { + if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { + ViewHolder viewHolder = (AgendaAdapter.ViewHolder)firstVisibleItem.getTag(); + Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " + + viewHolder.title.getText()); + } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { + AgendaByDayAdapter.ViewHolder viewHolder = + (AgendaByDayAdapter.ViewHolder)firstVisibleItem.getTag(); + Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " + + viewHolder.dateView.getText()); + } else if (firstVisibleItem instanceof TextView) { + Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); + } + } + } else if (getSelectedItemPosition() >= 0) { + if (DEBUG) { + Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + " by " + offset); + } + setSelection(getSelectedItemPosition() + offset); + } + } + + public void setHideDeclinedEvents(boolean hideDeclined) { + mWindowAdapter.setHideDeclinedEvents(hideDeclined); + } + + public void onResume() { + mWindowAdapter.notifyDataSetChanged(); + } + public void onPause() { + mWindowAdapter.notifyDataSetInvalidated(); + } +} diff --git a/src/com/android/calendar/agenda/AgendaWindowAdapter.java b/src/com/android/calendar/agenda/AgendaWindowAdapter.java new file mode 100644 index 00000000..540e1753 --- /dev/null +++ b/src/com/android/calendar/agenda/AgendaWindowAdapter.java @@ -0,0 +1,924 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.agenda; + +import com.android.calendar.R; +import com.android.calendar.Utils; + +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.Calendar; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.Instances; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.util.Formatter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Locale; +import java.util.concurrent.ConcurrentLinkedQueue; + +/* +Bugs Bugs Bugs: +- At rotation and launch time, the initial position is not set properly. This code is calling + listview.setSelection() in 2 rapid secessions but it dropped or didn't process the first one. +- Scroll using trackball isn't repositioning properly after a new adapter is added. +- Track ball clicks at the header/footer doesn't work. +- Potential ping pong effect if the prefetch window is big and data is limited +- Add index in calendar provider + +ToDo ToDo ToDo: +Get design of header and footer from designer + +Make scrolling smoother. +Test for correctness +Loading speed +Check for leaks and excessive allocations + */ + +public class AgendaWindowAdapter extends BaseAdapter { + + static final boolean BASICLOG = false; + static final boolean DEBUGLOG = false; + private static String TAG = "AgendaWindowAdapter"; + + private static final String AGENDA_SORT_ORDER = + Calendar.Instances.START_DAY + " ASC, " + + Calendar.Instances.BEGIN + " ASC, " + + Calendar.Events.TITLE + " ASC"; + + public static final int INDEX_TITLE = 1; + public static final int INDEX_EVENT_LOCATION = 2; + public static final int INDEX_ALL_DAY = 3; + public static final int INDEX_HAS_ALARM = 4; + public static final int INDEX_COLOR = 5; + public static final int INDEX_RRULE = 6; + public static final int INDEX_BEGIN = 7; + public static final int INDEX_END = 8; + public static final int INDEX_EVENT_ID = 9; + public static final int INDEX_START_DAY = 10; + public static final int INDEX_END_DAY = 11; + public static final int INDEX_SELF_ATTENDEE_STATUS = 12; + + private static final String[] PROJECTION = new String[] { + Instances._ID, // 0 + Instances.TITLE, // 1 + Instances.EVENT_LOCATION, // 2 + Instances.ALL_DAY, // 3 + Instances.HAS_ALARM, // 4 + Instances.COLOR, // 5 + Instances.RRULE, // 6 + Instances.BEGIN, // 7 + Instances.END, // 8 + Instances.EVENT_ID, // 9 + Instances.START_DAY, // 10 Julian start day + Instances.END_DAY, // 11 Julian end day + Instances.SELF_ATTENDEE_STATUS, // 12 + }; + + // Listview may have a bug where the index/position is not consistent when there's a header. + // TODO Need to look into this. + private static final int OFF_BY_ONE_BUG = 1; + + private static final int MAX_NUM_OF_ADAPTERS = 5; + + private static final int IDEAL_NUM_OF_EVENTS = 50; + + private static final int MIN_QUERY_DURATION = 7; // days + + private static final int MAX_QUERY_DURATION = 60; // days + + private static final int PREFETCH_BOUNDARY = 1; + + /** Times to auto-expand/retry query after getting no data */ + private static final int RETRIES_ON_NO_DATA = 1; + + private Context mContext; + + private QueryHandler mQueryHandler; + + private AgendaListView mAgendaListView; + + /** The sum of the rows in all the adapters */ + private int mRowCount; + + /** The number of times we have queried and gotten no results back */ + private int mEmptyCursorCount; + + /** Cached value of the last used adapter */ + private DayAdapterInfo mLastUsedInfo; + + private LinkedList mAdapterInfos = new LinkedList(); + + private ConcurrentLinkedQueue mQueryQueue = new ConcurrentLinkedQueue(); + + private TextView mHeaderView; + + private TextView mFooterView; + + private boolean mDoneSettingUpHeaderFooter = false; + + /** + * 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 + * mOlderRequests > mOlderRequestsProcessed. + */ + private int mOlderRequests; + + /** Number of "older" query that has been processed. */ + private int mOlderRequestsProcessed; + + /** + * When the user scrolled to the bottom, a query will be made for newer + * events and this will be incremented. Don't make more requests if + * mNewerRequests > mNewerRequestsProcessed. + */ + private int mNewerRequests; + + /** Number of "newer" query that has been processed. */ + private int mNewerRequestsProcessed; + + // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. + private Formatter mFormatter; + private StringBuilder mStringBuilder; + + private boolean mShuttingDown; + private boolean mHideDeclined; + + /** The current search query, or null if none */ + private String mSearchQuery; + + // Types of Query + private static final int QUERY_TYPE_OLDER = 0; // Query for older events + private static final int QUERY_TYPE_NEWER = 1; // Query for newer events + private static final int QUERY_TYPE_CLEAN = 2; // Delete everything and query around a date + + private static class QuerySpec { + long queryStartMillis; + + Time goToTime; + + int start; + + int end; + + String searchQuery; + + int queryType; + + public QuerySpec(int queryType) { + this.queryType = queryType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + end; + result = prime * result + (int) (queryStartMillis ^ (queryStartMillis >>> 32)); + result = prime * result + queryType; + result = prime * result + start; + result = prime * result + searchQuery.hashCode(); + if (goToTime != null) { + long goToTimeMillis = goToTime.toMillis(false); + result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32)); + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + QuerySpec other = (QuerySpec) obj; + if (end != other.end || queryStartMillis != other.queryStartMillis + || queryType != other.queryType || start != other.start + || Utils.equals(searchQuery, other.searchQuery)) { + return false; + } + + if (goToTime != null) { + if (goToTime.toMillis(false) != other.goToTime.toMillis(false)) { + return false; + } + } else { + if (other.goToTime != null) { + return false; + } + } + return true; + } + } + + static class EventInfo { + long begin; + + long end; + + long id; + } + + static class DayAdapterInfo { + Cursor cursor; + + AgendaByDayAdapter dayAdapter; + + int start; // start day of the cursor's coverage + + int end; // end day of the cursor's coverage + + int offset; // offset in position in the list view + + int size; // dayAdapter.getCount() + + public DayAdapterInfo(Context context) { + dayAdapter = new AgendaByDayAdapter(context); + } + + @Override + public String toString() { + Time time = new Time(); + StringBuilder sb = new StringBuilder(); + time.setJulianDay(start); + time.normalize(false); + sb.append("Start:").append(time.toString()); + time.setJulianDay(end); + time.normalize(false); + sb.append(" End:").append(time.toString()); + sb.append(" Offset:").append(offset); + sb.append(" Size:").append(size); + return sb.toString(); + } + } + + public AgendaWindowAdapter(Context context, + AgendaListView agendaListView) { + mContext = context; + mAgendaListView = agendaListView; + mQueryHandler = new QueryHandler(context.getContentResolver()); + + mStringBuilder = new StringBuilder(50); + mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); + + mSearchQuery = null; + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mHeaderView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); + mFooterView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); + mHeaderView.setText(R.string.loading); + mAgendaListView.addHeaderView(mHeaderView); + } + + // Method in Adapter + @Override + public int getViewTypeCount() { + return AgendaByDayAdapter.TYPE_LAST; + } + + // Method in BaseAdapter + @Override + public boolean areAllItemsEnabled() { + return false; + } + + // Method in Adapter + @Override + public int getItemViewType(int position) { + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info != null) { + return info.dayAdapter.getItemViewType(position - info.offset); + } else { + return -1; + } + } + + // Method in BaseAdapter + @Override + public boolean isEnabled(int position) { + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info != null) { + return info.dayAdapter.isEnabled(position - info.offset); + } else { + return false; + } + } + + // Abstract Method in BaseAdapter + public int getCount() { + return mRowCount; + } + + // Abstract Method in BaseAdapter + public Object getItem(int position) { + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info != null) { + return info.dayAdapter.getItem(position - info.offset); + } else { + return null; + } + } + + // Method in BaseAdapter + @Override + public boolean hasStableIds() { + return true; + } + + // Abstract Method in BaseAdapter + public long getItemId(int position) { + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info != null) { + return ((position - info.offset) << 20) + info.start ; + } else { + return -1; + } + } + + // Abstract Method in BaseAdapter + public View getView(int position, View convertView, ViewGroup parent) { + if (position >= (mRowCount - PREFETCH_BOUNDARY) + && mNewerRequests <= mNewerRequestsProcessed) { + if (DEBUGLOG) Log.e(TAG, "queryForNewerEvents: "); + mNewerRequests++; + queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); + } + + if (position < PREFETCH_BOUNDARY + && mOlderRequests <= mOlderRequestsProcessed) { + if (DEBUGLOG) Log.e(TAG, "queryForOlderEvents: "); + mOlderRequests++; + queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); + } + + View v; + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info != null) { + v = info.dayAdapter.getView(position - info.offset, convertView, + parent); + } else { + //TODO + Log.e(TAG, "BUG: getAdapterInfoByPosition returned null!!! " + position); + TextView tv = new TextView(mContext); + tv.setText("Bug! " + position); + v = tv; + } + + if (DEBUGLOG) { + Log.e(TAG, "getView " + position + " = " + getViewTitle(v)); + } + return v; + } + + private int findDayPositionNearestTime(Time time) { + if (DEBUGLOG) Log.e(TAG, "findDayPositionNearestTime " + time); + + DayAdapterInfo info = getAdapterInfoByTime(time); + if (info != null) { + return info.offset + info.dayAdapter.findDayPositionNearestTime(time); + } else { + return -1; + } + } + + private DayAdapterInfo getAdapterInfoByPosition(int position) { + synchronized (mAdapterInfos) { + if (mLastUsedInfo != null && mLastUsedInfo.offset <= position + && position < (mLastUsedInfo.offset + mLastUsedInfo.size)) { + return mLastUsedInfo; + } + for (DayAdapterInfo info : mAdapterInfos) { + if (info.offset <= position + && position < (info.offset + info.size)) { + mLastUsedInfo = info; + return info; + } + } + } + return null; + } + + private DayAdapterInfo getAdapterInfoByTime(Time time) { + if (DEBUGLOG) Log.e(TAG, "getAdapterInfoByTime " + time.toString()); + + Time tmpTime = new Time(time); + long timeInMillis = tmpTime.normalize(true); + int day = Time.getJulianDay(timeInMillis, tmpTime.gmtoff); + synchronized (mAdapterInfos) { + for (DayAdapterInfo info : mAdapterInfos) { + if (info.start <= day && day < info.end) { + return info; + } + } + } + return null; + } + + public EventInfo getEventByPosition(int position) { + if (DEBUGLOG) Log.e(TAG, "getEventByPosition " + position); + + EventInfo event = new EventInfo(); + position -= OFF_BY_ONE_BUG; + DayAdapterInfo info = getAdapterInfoByPosition(position); + if (info == null) { + return null; + } + + position = info.dayAdapter.getCursorPosition(position - info.offset); + if (position == Integer.MIN_VALUE) { + return null; + } + + boolean isDayHeader = false; + if (position < 0) { + position = -position; + isDayHeader = true; + } + + if (position < info.cursor.getCount()) { + info.cursor.moveToPosition(position); + event.begin = info.cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); + boolean allDay = info.cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; + + if (allDay) { // UTC + Time time = new Time(); + time.setJulianDay(Time.getJulianDay(event.begin, 0)); + event.begin = time.toMillis(false /* use isDst */); + } else if (isDayHeader) { // Trim to midnight. + Time time = new Time(); + time.set(event.begin); + time.hour = 0; + time.minute = 0; + time.second = 0; + event.begin = time.toMillis(false /* use isDst */); + } + + if (!isDayHeader) { + event.end = info.cursor.getLong(AgendaWindowAdapter.INDEX_END); + event.id = info.cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); + } + return event; + } + return null; + } + + public void refresh(Time goToTime, String searchQuery, boolean forced) { + if (searchQuery != null) { + mSearchQuery = searchQuery; + } + + if (DEBUGLOG) { + Log.e(TAG, "refresh " + goToTime.toString() + (forced ? " forced" : " not forced")); + } + + int startDay = Time.getJulianDay(goToTime.toMillis(false), goToTime.gmtoff); + + if (!forced && isInRange(startDay, startDay)) { + // No need to requery + mAgendaListView.setSelection(findDayPositionNearestTime(goToTime) + OFF_BY_ONE_BUG); + return; + } + + // Query for a total of MIN_QUERY_DURATION days + int endDay = startDay + MIN_QUERY_DURATION; + + queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN); + } + + public void close() { + mShuttingDown = true; + pruneAdapterInfo(QUERY_TYPE_CLEAN); + if (mQueryHandler != null) { + mQueryHandler.cancelOperation(0); + } + } + + private DayAdapterInfo pruneAdapterInfo(int queryType) { + synchronized (mAdapterInfos) { + DayAdapterInfo recycleMe = null; + if (!mAdapterInfos.isEmpty()) { + if (mAdapterInfos.size() >= MAX_NUM_OF_ADAPTERS) { + if (queryType == QUERY_TYPE_NEWER) { + recycleMe = mAdapterInfos.removeFirst(); + } else if (queryType == QUERY_TYPE_OLDER) { + recycleMe = mAdapterInfos.removeLast(); + // Keep the size only if the oldest items are removed. + recycleMe.size = 0; + } + if (recycleMe != null) { + if (recycleMe.cursor != null) { + recycleMe.cursor.close(); + } + return recycleMe; + } + } + + if (mRowCount == 0 || queryType == QUERY_TYPE_CLEAN) { + mRowCount = 0; + int deletedRows = 0; + DayAdapterInfo info; + do { + info = mAdapterInfos.poll(); + if (info != null) { + // TODO the following causes ANR's. Do this in a thread. + info.cursor.close(); + deletedRows += info.size; + recycleMe = info; + } + } while (info != null); + + if (recycleMe != null) { + recycleMe.cursor = null; + recycleMe.size = deletedRows; + } + } + } + return recycleMe; + } + } + + private String buildQuerySelection() { + // Respect the preference to show/hide declined events + + if (mHideDeclined) { + return Calendars.SELECTED + "=1 AND " + + Instances.SELF_ATTENDEE_STATUS + "!=" + + Attendees.ATTENDEE_STATUS_DECLINED; + } else { + return Calendars.SELECTED + "=1"; + } + } + + private Uri buildQueryUri(int start, int end, String searchQuery) { + Uri rootUri = searchQuery == null ? + Instances.CONTENT_BY_DAY_URI : + Instances.CONTENT_SEARCH_BY_DAY_URI; + Uri.Builder builder = rootUri.buildUpon(); + ContentUris.appendId(builder, start); + ContentUris.appendId(builder, end); + if (searchQuery != null) { + builder.appendPath(searchQuery); + } + return builder.build(); + } + + private boolean isInRange(int start, int end) { + synchronized (mAdapterInfos) { + if (mAdapterInfos.isEmpty()) { + return false; + } + return mAdapterInfos.getFirst().start <= start && end <= mAdapterInfos.getLast().end; + } + } + + private int calculateQueryDuration(int start, int end) { + int queryDuration = MAX_QUERY_DURATION; + if (mRowCount != 0) { + queryDuration = IDEAL_NUM_OF_EVENTS * (end - start + 1) / mRowCount; + } + + if (queryDuration > MAX_QUERY_DURATION) { + queryDuration = MAX_QUERY_DURATION; + } else if (queryDuration < MIN_QUERY_DURATION) { + queryDuration = MIN_QUERY_DURATION; + } + + return queryDuration; + } + + private boolean queueQuery(int start, int end, Time goToTime, + String searchQuery, int queryType) { + QuerySpec queryData = new QuerySpec(queryType); + queryData.goToTime = goToTime; + queryData.start = start; + queryData.end = end; + queryData.searchQuery = searchQuery; + return queueQuery(queryData); + } + + private boolean queueQuery(QuerySpec queryData) { + queryData.searchQuery = mSearchQuery; + Boolean queuedQuery; + synchronized (mQueryQueue) { + queuedQuery = false; + Boolean doQueryNow = mQueryQueue.isEmpty(); + mQueryQueue.add(queryData); + queuedQuery = true; + if (doQueryNow) { + doQuery(queryData); + } + } + return queuedQuery; + } + + private void doQuery(QuerySpec queryData) { + if (!mAdapterInfos.isEmpty()) { + int start = mAdapterInfos.getFirst().start; + int end = mAdapterInfos.getLast().end; + int queryDuration = calculateQueryDuration(start, end); + switch(queryData.queryType) { + case QUERY_TYPE_OLDER: + queryData.end = start - 1; + queryData.start = queryData.end - queryDuration; + break; + case QUERY_TYPE_NEWER: + queryData.start = end + 1; + queryData.end = queryData.start + queryDuration; + break; + } + } + + if (BASICLOG) { + Time time = new Time(); + time.setJulianDay(queryData.start); + Time time2 = new Time(); + time2.setJulianDay(queryData.end); + Log.v(TAG, "startQuery: " + time.toString() + " to " + + time2.toString() + " then go to " + queryData.goToTime); + } + + mQueryHandler.cancelOperation(0); + if (BASICLOG) queryData.queryStartMillis = System.nanoTime(); + + Uri queryUri = buildQueryUri( + queryData.start, queryData.end, queryData.searchQuery); + mQueryHandler.startQuery(0, queryData, queryUri, + PROJECTION, buildQuerySelection(), null, + AGENDA_SORT_ORDER); + } + + private String formatDateString(int julianDay) { + Time time = new Time(); + time.setJulianDay(julianDay); + long millis = time.toMillis(false); + mStringBuilder.setLength(0); + return DateUtils.formatDateRange(mContext, mFormatter, millis, millis, + DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_ABBREV_MONTH).toString(); + } + + private void updateHeaderFooter(final int start, final int end) { + mHeaderView.setText(mContext.getString(R.string.show_older_events, + formatDateString(start))); + mFooterView.setText(mContext.getString(R.string.show_newer_events, + formatDateString(end))); + } + + private class QueryHandler extends AsyncQueryHandler { + + public QueryHandler(ContentResolver cr) { + super(cr); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + QuerySpec data = (QuerySpec)cookie; + if (BASICLOG) { + long queryEndMillis = System.nanoTime(); + Log.e(TAG, "Query time(ms): " + + (queryEndMillis - data.queryStartMillis) / 1000000 + + " Count: " + cursor.getCount()); + } + + if (mShuttingDown) { + cursor.close(); + return; + } + + // Notify Listview of changes and update position + int cursorSize = cursor.getCount(); + if (cursorSize > 0 || mAdapterInfos.isEmpty() || data.queryType == QUERY_TYPE_CLEAN) { + final int listPositionOffset = processNewCursor(data, cursor); + if (data.goToTime == null) { // Typical Scrolling type query + notifyDataSetChanged(); + if (listPositionOffset != 0) { + mAgendaListView.shiftSelection(listPositionOffset); + } + } else { // refresh() called. Go to the designated position + final Time goToTime = data.goToTime; + notifyDataSetChanged(); + int newPosition = findDayPositionNearestTime(goToTime); + if (newPosition >= 0) { + mAgendaListView.setSelection(newPosition + OFF_BY_ONE_BUG); + } + if (DEBUGLOG) { + Log.e(TAG, "Setting listview to " + + "findDayPositionNearestTime: " + (newPosition + OFF_BY_ONE_BUG)); + } + } + } else { + cursor.close(); + } + + // Update header and footer + if (!mDoneSettingUpHeaderFooter) { + OnClickListener headerFooterOnClickListener = new OnClickListener() { + public void onClick(View v) { + if (v == mHeaderView) { + queueQuery(new QuerySpec(QUERY_TYPE_OLDER)); + } else { + queueQuery(new QuerySpec(QUERY_TYPE_NEWER)); + } + }}; + mHeaderView.setOnClickListener(headerFooterOnClickListener); + mFooterView.setOnClickListener(headerFooterOnClickListener); + mAgendaListView.addFooterView(mFooterView); + mDoneSettingUpHeaderFooter = true; + } + synchronized (mQueryQueue) { + int totalAgendaRangeStart = -1; + int totalAgendaRangeEnd = -1; + + if (cursorSize != 0) { + // Remove the query that just completed + QuerySpec x = mQueryQueue.poll(); + if (BASICLOG && !x.equals(data)) { + Log.e(TAG, "onQueryComplete - cookie != head of queue"); + } + mEmptyCursorCount = 0; + if (data.queryType == QUERY_TYPE_NEWER) { + mNewerRequestsProcessed++; + } else if (data.queryType == QUERY_TYPE_OLDER) { + mOlderRequestsProcessed++; + } + + totalAgendaRangeStart = mAdapterInfos.getFirst().start; + totalAgendaRangeEnd = mAdapterInfos.getLast().end; + } else { // CursorSize == 0 + QuerySpec querySpec = mQueryQueue.peek(); + + // Update Adapter Info with new start and end date range + if (!mAdapterInfos.isEmpty()) { + DayAdapterInfo first = mAdapterInfos.getFirst(); + DayAdapterInfo last = mAdapterInfos.getLast(); + + if (first.start - 1 <= querySpec.end && querySpec.start < first.start) { + first.start = querySpec.start; + } + + if (querySpec.start <= last.end + 1 && last.end < querySpec.end) { + last.end = querySpec.end; + } + + totalAgendaRangeStart = first.start; + totalAgendaRangeEnd = last.end; + } else { + totalAgendaRangeStart = querySpec.start; + totalAgendaRangeEnd = querySpec.end; + } + + // Update query specification with expanded search range + // and maybe rerun query + switch (querySpec.queryType) { + case QUERY_TYPE_OLDER: + totalAgendaRangeStart = querySpec.start; + querySpec.start -= MAX_QUERY_DURATION; + break; + case QUERY_TYPE_NEWER: + totalAgendaRangeEnd = querySpec.end; + querySpec.end += MAX_QUERY_DURATION; + break; + case QUERY_TYPE_CLEAN: + totalAgendaRangeStart = querySpec.start; + totalAgendaRangeEnd = querySpec.end; + querySpec.start -= MAX_QUERY_DURATION / 2; + querySpec.end += MAX_QUERY_DURATION / 2; + break; + } + + if (++mEmptyCursorCount > RETRIES_ON_NO_DATA) { + // Nothing in the cursor again. Dropping query + mQueryQueue.poll(); + } + } + + updateHeaderFooter(totalAgendaRangeStart, totalAgendaRangeEnd); + + // Fire off the next query if any + Iterator it = mQueryQueue.iterator(); + while (it.hasNext()) { + QuerySpec queryData = it.next(); + if (!isInRange(queryData.start, queryData.end)) { + // Query accepted + if (DEBUGLOG) Log.e(TAG, "Query accepted. QueueSize:" + mQueryQueue.size()); + doQuery(queryData); + break; + } else { + // Query rejected + it.remove(); + if (DEBUGLOG) Log.e(TAG, "Query rejected. QueueSize:" + mQueryQueue.size()); + } + } + } + if (BASICLOG) { + for (DayAdapterInfo info3 : mAdapterInfos) { + Log.e(TAG, "> " + info3.toString()); + } + } + } + + /* + * Update the adapter info array with a the new cursor. Close out old + * cursors as needed. + * + * @return number of rows removed from the beginning + */ + private int processNewCursor(QuerySpec data, Cursor cursor) { + synchronized (mAdapterInfos) { + // Remove adapter info's from adapterInfos as needed + DayAdapterInfo info = pruneAdapterInfo(data.queryType); + int listPositionOffset = 0; + if (info == null) { + info = new DayAdapterInfo(mContext); + } else { + if (DEBUGLOG) + Log.e(TAG, "processNewCursor listPositionOffsetA=" + + -info.size); + listPositionOffset = -info.size; + } + + // Setup adapter info + info.start = data.start; + info.end = data.end; + info.cursor = cursor; + info.dayAdapter.changeCursor(info); + info.size = info.dayAdapter.getCount(); + + // Insert into adapterInfos + if (mAdapterInfos.isEmpty() + || data.end <= mAdapterInfos.getFirst().start) { + mAdapterInfos.addFirst(info); + listPositionOffset += info.size; + } else if (BASICLOG && data.start < mAdapterInfos.getLast().end) { + mAdapterInfos.addLast(info); + for (DayAdapterInfo info2 : mAdapterInfos) { + Log.e("========== BUG ==", info2.toString()); + } + } else { + mAdapterInfos.addLast(info); + } + + // Update offsets in adapterInfos + mRowCount = 0; + for (DayAdapterInfo info3 : mAdapterInfos) { + info3.offset = mRowCount; + mRowCount += info3.size; + } + mLastUsedInfo = null; + + return listPositionOffset; + } + } + } + + static String getViewTitle(View x) { + String title = ""; + if (x != null) { + Object yy = x.getTag(); + if (yy instanceof AgendaAdapter.ViewHolder) { + TextView tv = ((AgendaAdapter.ViewHolder) yy).title; + if (tv != null) { + title = (String) tv.getText(); + } + } else if (yy != null) { + TextView dateView = ((AgendaByDayAdapter.ViewHolder) yy).dateView; + if (dateView != null) { + title = (String) dateView.getText(); + } + } + } + return title; + } + + public void setHideDeclinedEvents(boolean hideDeclined) { + mHideDeclined = hideDeclined; + } +} -- cgit v1.2.3