blob: 6cfc7e5b87d648b9968936c2c5debb4a73e1404b [file] [log] [blame]
/*
* 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.R;
import com.android.calendar.Utils;
import com.android.calendar.agenda.AgendaAdapter.ViewHolder;
import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo;
import com.android.calendar.agenda.AgendaWindowAdapter.AgendaItem;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.provider.CalendarContract.Attendees;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
public class AgendaListView extends ListView implements OnItemClickListener {
private static final String TAG = "AgendaListView";
private static final boolean DEBUG = false;
private static final int EVENT_UPDATE_TIME = 300000; // 5 minutes
private AgendaWindowAdapter mWindowAdapter;
private DeleteEventHelper mDeleteEventHelper;
private Context mContext;
private String mTimeZone;
private Time mTime;
private boolean mShowEventDetailsWithAgenda;
private Handler mHandler = null;
private final Runnable mTZUpdater = new Runnable() {
@Override
public void run() {
mTimeZone = Utils.getTimeZone(mContext, this);
mTime.switchTimezone(mTimeZone);
}
};
// runs every midnight and refreshes the view in order to update the past/present
// separator
private final Runnable mMidnightUpdater = new Runnable() {
@Override
public void run() {
refresh(true);
Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone);
}
};
// Runs every EVENT_UPDATE_TIME to gray out past events
private final Runnable mPastEventUpdater = new Runnable() {
@Override
public void run() {
if (updatePastEvents() == true) {
refresh(true);
}
setPastEventsUpdater();
}
};
public AgendaListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
mContext = context;
mTimeZone = Utils.getTimeZone(context, mTZUpdater);
mTime = new Time(mTimeZone);
setOnItemClickListener(this);
setVerticalScrollBarEnabled(false);
mWindowAdapter = new AgendaWindowAdapter(context, this,
Utils.getConfigBool(context, R.bool.show_event_details_with_agenda));
mWindowAdapter.setSelectedInstanceId(-1/* TODO:instanceId */);
setAdapter(mWindowAdapter);
setCacheColorHint(context.getResources().getColor(R.color.agenda_item_not_selected));
mDeleteEventHelper =
new DeleteEventHelper(context, null, false /* don't exit when done */);
mShowEventDetailsWithAgenda = Utils.getConfigBool(mContext,
R.bool.show_event_details_with_agenda);
// Hide ListView dividers, they are done in the item views themselves
setDivider(null);
setDividerHeight(0);
mHandler = new Handler();
}
// Sets a thread to run every EVENT_UPDATE_TIME in order to update the list
// with grayed out past events
private void setPastEventsUpdater() {
// Run the thread in the nearest rounded EVENT_UPDATE_TIME
long now = System.currentTimeMillis();
long roundedTime = (now / EVENT_UPDATE_TIME) * EVENT_UPDATE_TIME;
mHandler.removeCallbacks(mPastEventUpdater);
mHandler.postDelayed(mPastEventUpdater, EVENT_UPDATE_TIME - (now - roundedTime));
}
// Stop the past events thread
private void resetPastEventsUpdater() {
mHandler.removeCallbacks(mPastEventUpdater);
}
// Go over all visible views and checks if all past events are grayed out.
// Returns true is there is at least one event that ended and it is not
// grayed out.
private boolean updatePastEvents() {
int childCount = getChildCount();
boolean needUpdate = false;
long now = System.currentTimeMillis();
Time time = new Time(mTimeZone);
time.set(now);
int todayJulianDay = Time.getJulianDay(now, time.gmtoff);
// Go over views in list
for (int i = 0; i < childCount; ++i) {
View listItem = getChildAt(i);
Object o = listItem.getTag();
if (o instanceof AgendaByDayAdapter.ViewHolder) {
// day view - check if day in the past and not grayed yet
AgendaByDayAdapter.ViewHolder holder = (AgendaByDayAdapter.ViewHolder) o;
if (holder.julianDay <= todayJulianDay && !holder.grayed) {
needUpdate = true;
break;
}
} else if (o instanceof AgendaAdapter.ViewHolder) {
// meeting view - check if event in the past or started already and not grayed yet
// All day meetings for a day are grayed out
AgendaAdapter.ViewHolder holder = (AgendaAdapter.ViewHolder) o;
if (!holder.grayed && ((!holder.allDay && holder.startTimeMilli <= now) ||
(holder.allDay && holder.julianDay <= todayJulianDay))) {
needUpdate = true;
break;
}
}
}
return needUpdate;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mWindowAdapter.close();
}
// Implementation of the interface OnItemClickListener
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
if (id != -1) {
// Switch to the EventInfo view
AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position);
long oldInstanceId = mWindowAdapter.getSelectedInstanceId();
mWindowAdapter.setSelectedView(v);
// If events are shown to the side of the agenda list , do nothing
// when the same event is selected , otherwise show the selected event.
if (item != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() ||
!mShowEventDetailsWithAgenda)) {
long startTime = item.begin;
long endTime = item.end;
// Holder in view holds the start of the specific part of a multi-day event ,
// use it for the goto
long holderStartTime;
Object holder = v.getTag();
if (holder instanceof AgendaAdapter.ViewHolder) {
holderStartTime = ((AgendaAdapter.ViewHolder) holder).startTimeMilli;
} else {
holderStartTime = startTime;
}
if (item.allDay) {
startTime = Utils.convertAlldayLocalToUTC(mTime, startTime, mTimeZone);
endTime = Utils.convertAlldayLocalToUTC(mTime, endTime, mTimeZone);
}
mTime.set(startTime);
CalendarController controller = CalendarController.getInstance(mContext);
controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id,
startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong(
Attendees.ATTENDEE_STATUS_NONE, item.allDay), holderStartTime);
}
}
}
public void goTo(Time time, long id, String searchQuery, boolean forced,
boolean refreshEventInfo) {
if (time == null) {
time = mTime;
long goToTime = getFirstVisibleTime(null);
if (goToTime <= 0) {
goToTime = System.currentTimeMillis();
}
time.set(goToTime);
}
mTime.set(time);
mTime.switchTimezone(mTimeZone);
mTime.normalize(true);
if (DEBUG) {
Log.d(TAG, "Goto with time " + mTime.toString());
}
mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo);
}
public void refresh(boolean forced) {
mWindowAdapter.refresh(mTime, -1, null, forced, false);
}
public void deleteSelectedEvent() {
int position = getSelectedItemPosition();
AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(position);
if (agendaItem != null) {
mDeleteEventHelper.delete(agendaItem.begin, agendaItem.end, agendaItem.id, -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) {
AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position);
if (item != null) {
return item.begin;
}
}
return getFirstVisibleTime(null);
}
public AgendaAdapter.ViewHolder getSelectedViewHolder() {
return mWindowAdapter.getSelectedViewHolder();
}
public long getFirstVisibleTime(AgendaItem item) {
AgendaItem agendaItem = item;
if (item == null) {
agendaItem = getFirstVisibleAgendaItem();
}
if (agendaItem != null) {
Time t = new Time(mTimeZone);
t.set(agendaItem.begin);
// Save and restore the time since setJulianDay sets the time to 00:00:00
int hour = t.hour;
int minute = t.minute;
int second = t.second;
t.setJulianDay(agendaItem.startDay);
t.hour = hour;
t.minute = minute;
t.second = second;
if (DEBUG) {
t.normalize(true);
Log.d(TAG, "first position had time " + t.toString());
}
return t.normalize(false);
}
return 0;
}
public AgendaItem getFirstVisibleAgendaItem() {
int position = getFirstVisiblePosition();
if (DEBUG) {
Log.v(TAG, "getFirstVisiblePosition = " + position);
}
// mShowEventDetailsWithAgenda == true implies we have a sticky header. In that case
// we may need to take the second visible position, since the first one maybe the one
// under the sticky header.
if (mShowEventDetailsWithAgenda) {
View v = getFirstVisibleView ();
if (v != null) {
Rect r = new Rect ();
v.getLocalVisibleRect(r);
if (r.bottom - r.top <= mWindowAdapter.getStickyHeaderHeight()) {
position ++;
}
}
}
return mWindowAdapter.getAgendaItemByPosition(position,
false /* startDay = date separator date instead of actual event startday */);
}
public int getJulianDayFromPosition(int position) {
DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position);
if (info != null) {
return info.dayAdapter.findJulianDayFromPosition(position - info.offset);
}
return 0;
}
// Finds is a specific event (defined by start time and id) is visible
public boolean isAgendaItemVisible(Time startTime, long id) {
if (id == -1 || startTime == null) {
return false;
}
View child = getChildAt(0);
// View not set yet, so not child - return
if (child == null) {
return false;
}
int start = getPositionForView(child);
long milliTime = startTime.toMillis(true);
int childCount = getChildCount();
int eventsInAdapter = mWindowAdapter.getCount();
for (int i = 0; i < childCount; i++) {
if (i + start >= eventsInAdapter) {
break;
}
AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(i + start);
if (agendaItem == null) {
continue;
}
if (agendaItem.id == id && agendaItem.begin == milliTime) {
View listItem = getChildAt(i);
if (listItem.getTop() <= getHeight() &&
listItem.getTop() >= mWindowAdapter.getStickyHeaderHeight()) {
return true;
}
}
}
return false;
}
public long getSelectedInstanceId() {
return mWindowAdapter.getSelectedInstanceId();
}
public void setSelectedInstanceId(long id) {
mWindowAdapter.setSelectedInstanceId(id);
}
// 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() {
mTZUpdater.run();
Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone);
setPastEventsUpdater();
mWindowAdapter.onResume();
}
public void onPause() {
Utils.resetMidnightUpdater(mHandler, mMidnightUpdater);
resetPastEventsUpdater();
}
}