/* * 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 java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.LinkedList; public class AgendaByDayAdapter extends BaseAdapter { private static final int TYPE_DAY = 0; private static final int TYPE_MEETING = 1; private static final int TYPE_LAST = 2; private final Context mContext; private final AgendaAdapter mAgendaAdapter; private final LayoutInflater mInflater; private ArrayList<RowInfo> mRowInfo; private int mTodayJulianDay; private Time mTime = new Time(); public AgendaByDayAdapter(Context context, AgendaAdapter agendaAdapter) { mContext = context; mAgendaAdapter = agendaAdapter; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } 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; } private static class ViewHolder { TextView dateView; } 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; View agendaDayView; if ((convertView == null) || (convertView.getTag() == 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); } else { agendaDayView = convertView; holder = (ViewHolder) convertView.getTag(); } // Re-use the member variable "mTime" which is set to the local timezone. Time date = mTime; long millis = date.setJulianDay(row.mData); int flags = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; if (row.mData == mTodayJulianDay) { String dayText = mContext.getResources().getText(R.string.agenda_today) + ", "; holder.dateView.setText(dayText + DateUtils.formatDateTime(mContext, millis, flags)); } else { flags |= DateUtils.FORMAT_SHOW_WEEKDAY; holder.dateView.setText(DateUtils.formatDateTime(mContext, millis, flags)); } return agendaDayView; } else if (row.mType == TYPE_MEETING) { return mAgendaAdapter.getView(row.mData, convertView, parent); } else { // Error throw new IllegalStateException("Unknown event type:" + row.mType); } } public void clearDayHeaderInfo() { mRowInfo = null; } public void calculateDays(Cursor cursor) { ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>(); int prevStartDay = -1; Time time = new Time(); long now = System.currentTimeMillis(); time.set(now); mTodayJulianDay = Time.getJulianDay(now, time.gmtoff); LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>(); for (int position = 0; cursor.moveToNext(); position++) { boolean allDay = cursor.getInt(AgendaActivity.INDEX_ALL_DAY) != 0; int startDay = cursor.getInt(AgendaActivity.INDEX_START_DAY); 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<MultipleDayInfo> 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(AgendaActivity.INDEX_END_DAY); 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) { // Get the Julian day for the last day of this month. To do that, // we set the date to one less than the first day of the next month, // and then normalize. time.setJulianDay(prevStartDay); time.month += 1; time.monthDay = 0; // monthDay starts with 1, so this is the previous day long millis = time.normalize(true /* ignore isDst */); int lastDayOfMonth = Time.getJulianDay(millis, time.gmtoff); for (int currentDay = prevStartDay + 1; currentDay <= lastDayOfMonth; currentDay++) { boolean dayHeaderAdded = false; Iterator<MultipleDayInfo> 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; } } return listPos; } @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; } }