blob: 45a1bea1277ff5afc1325a4aa330a402c073616e [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.calendar.month;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Message;
import android.text.format.Time;
import android.util.Log;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView.LayoutParams;
import com.android.calendar.CalendarController;
import com.android.calendar.CalendarController.EventType;
import com.android.calendar.CalendarController.ViewType;
import com.android.calendar.Event;
import com.android.calendar.R;
import com.android.calendar.Utils;
import java.util.ArrayList;
import java.util.HashMap;
public class MonthByWeekAdapter extends SimpleWeeksAdapter {
private static final String TAG = "MonthByWeekAdapter";
public static final String WEEK_PARAMS_IS_MINI = "mini_month";
protected static int DEFAULT_QUERY_DAYS = 7 * 8; // 8 weeks
private static final long ANIMATE_TODAY_TIMEOUT = 1000;
protected CalendarController mController;
protected String mHomeTimeZone;
protected Time mTempTime;
protected Time mToday;
protected int mFirstJulianDay;
protected int mQueryDays;
protected boolean mIsMiniMonth = true;
protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
private final boolean mShowAgendaWithMonth;
protected ArrayList<ArrayList<Event>> mEventDayList = new ArrayList<ArrayList<Event>>();
protected ArrayList<Event> mEvents = null;
private boolean mAnimateToday = false;
private long mAnimateTime = 0;
private Handler mEventDialogHandler;
MonthWeekEventsView mClickedView;
MonthWeekEventsView mSingleTapUpView;
MonthWeekEventsView mLongClickedView;
float mClickedXLocation; // Used to find which day was clicked
long mClickTime; // Used to calculate minimum click animation time
// Used to insure minimal time for seeing the click animation before switching views
private static final int mOnTapDelay = 100;
// Minimal time for a down touch action before stating the click animation, this insures that
// there is no click animation on flings
private static int mOnDownDelay;
private static int mTotalClickDelay;
// Minimal distance to move the finger in order to cancel the click animation
private static float mMovedPixelToCancel;
public MonthByWeekAdapter(Context context, HashMap<String, Integer> params) {
super(context, params);
if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0;
}
mShowAgendaWithMonth = Utils.getConfigBool(context, R.bool.show_agenda_with_month);
ViewConfiguration vc = ViewConfiguration.get(context);
mOnDownDelay = ViewConfiguration.getTapTimeout();
mMovedPixelToCancel = vc.getScaledTouchSlop();
mTotalClickDelay = mOnDownDelay + mOnTapDelay;
}
public void animateToday() {
mAnimateToday = true;
mAnimateTime = System.currentTimeMillis();
}
@Override
protected void init() {
super.init();
mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
mController = CalendarController.getInstance(mContext);
mHomeTimeZone = Utils.getTimeZone(mContext, null);
mSelectedDay.switchTimezone(mHomeTimeZone);
mToday = new Time(mHomeTimeZone);
mToday.setToNow();
mTempTime = new Time(mHomeTimeZone);
}
private void updateTimeZones() {
mSelectedDay.timezone = mHomeTimeZone;
mSelectedDay.normalize(true);
mToday.timezone = mHomeTimeZone;
mToday.setToNow();
mTempTime.switchTimezone(mHomeTimeZone);
}
@Override
public void setSelectedDay(Time selectedTime) {
mSelectedDay.set(selectedTime);
long millis = mSelectedDay.normalize(true);
mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
notifyDataSetChanged();
}
public void setEvents(int firstJulianDay, int numDays, ArrayList<Event> events) {
if (mIsMiniMonth) {
if (Log.isLoggable(TAG, Log.ERROR)) {
Log.e(TAG, "Attempted to set events for mini view. Events only supported in full"
+ " view.");
}
return;
}
mEvents = events;
mFirstJulianDay = firstJulianDay;
mQueryDays = numDays;
// Create a new list, this is necessary since the weeks are referencing
// pieces of the old list
ArrayList<ArrayList<Event>> eventDayList = new ArrayList<ArrayList<Event>>();
for (int i = 0; i < numDays; i++) {
eventDayList.add(new ArrayList<Event>());
}
if (events == null || events.size() == 0) {
if(Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "No events. Returning early--go schedule something fun.");
}
mEventDayList = eventDayList;
refresh();
return;
}
// Compute the new set of days with events
for (Event event : events) {
int startDay = event.startDay - mFirstJulianDay;
int endDay = event.endDay - mFirstJulianDay + 1;
if (startDay < numDays || endDay >= 0) {
if (startDay < 0) {
startDay = 0;
}
if (startDay > numDays) {
continue;
}
if (endDay < 0) {
continue;
}
if (endDay > numDays) {
endDay = numDays;
}
for (int j = startDay; j < endDay; j++) {
eventDayList.get(j).add(event);
}
}
}
if(Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Processed " + events.size() + " events.");
}
mEventDayList = eventDayList;
refresh();
}
@SuppressWarnings("unchecked")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mIsMiniMonth) {
return super.getView(position, convertView, parent);
}
MonthWeekEventsView v;
LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
HashMap<String, Integer> drawingParams = null;
boolean isAnimatingToday = false;
if (convertView != null) {
v = (MonthWeekEventsView) convertView;
// Checking updateToday uses the current params instead of the new
// params, so this is assuming the view is relatively stable
if (mAnimateToday && v.updateToday(mSelectedDay.timezone)) {
long currentTime = System.currentTimeMillis();
// If it's been too long since we tried to start the animation
// don't show it. This can happen if the user stops a scroll
// before reaching today.
if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
mAnimateToday = false;
mAnimateTime = 0;
} else {
isAnimatingToday = true;
// There is a bug that causes invalidates to not work some
// of the time unless we recreate the view.
v = new MonthWeekEventsView(mContext);
}
} else {
drawingParams = (HashMap<String, Integer>) v.getTag();
}
} else {
v = new MonthWeekEventsView(mContext);
}
if (drawingParams == null) {
drawingParams = new HashMap<String, Integer>();
}
drawingParams.clear();
v.setLayoutParams(params);
v.setClickable(true);
v.setOnTouchListener(this);
int selectedDay = -1;
if (mSelectedWeek == position) {
selectedDay = mSelectedDay.weekDay;
}
drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
(parent.getHeight() + parent.getTop()) / mNumWeeks);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation);
if (isAnimatingToday) {
drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1);
mAnimateToday = false;
}
v.setWeekParams(drawingParams, mSelectedDay.timezone);
return v;
}
@Override
protected void refresh() {
mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
mShowWeekNumber = Utils.getShowWeekNumber(mContext);
mHomeTimeZone = Utils.getTimeZone(mContext, null);
mOrientation = mContext.getResources().getConfiguration().orientation;
updateTimeZones();
notifyDataSetChanged();
}
@Override
protected void onDayTapped(Time day) {
setDayParameters(day);
if (mShowAgendaWithMonth || mIsMiniMonth) {
// If agenda view is visible with month view , refresh the views
// with the selected day's info
mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
} else {
// Else , switch to the detailed view
mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
ViewType.DETAIL,
CalendarController.EXTRA_GOTO_DATE
| CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null);
}
}
private void setDayParameters(Time day) {
day.timezone = mHomeTimeZone;
Time currTime = new Time(mHomeTimeZone);
currTime.set(mController.getTime());
day.hour = currTime.hour;
day.minute = currTime.minute;
day.allDay = false;
day.normalize(true);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!(v instanceof MonthWeekEventsView)) {
return super.onTouch(v, event);
}
int action = event.getAction();
// Event was tapped - switch to the detailed view making sure the click animation
// is done first.
if (mGestureDetector.onTouchEvent(event)) {
mSingleTapUpView = (MonthWeekEventsView) v;
long delay = System.currentTimeMillis() - mClickTime;
// Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
mListView.postDelayed(mDoSingleTapUp,
delay > mTotalClickDelay ? 0 : mTotalClickDelay - delay);
return true;
} else {
// Animate a click - on down: show the selected day in the "clicked" color.
// On Up/scroll/move/cancel: hide the "clicked" color.
switch (action) {
case MotionEvent.ACTION_DOWN:
mClickedView = (MonthWeekEventsView)v;
mClickedXLocation = event.getX();
mClickTime = System.currentTimeMillis();
mListView.postDelayed(mDoClick, mOnDownDelay);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_SCROLL:
case MotionEvent.ACTION_CANCEL:
clearClickedView((MonthWeekEventsView)v);
break;
case MotionEvent.ACTION_MOVE:
// No need to cancel on vertical movement, ACTION_SCROLL will do that.
if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
clearClickedView((MonthWeekEventsView)v);
}
break;
default:
break;
}
}
// Do not tell the frameworks we consumed the touch action so that fling actions can be
// processed by the fragment.
return false;
}
/**
* This is here so we can identify events and process them
*/
protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (mLongClickedView != null) {
Time day = mLongClickedView.getDayFromLocation(mClickedXLocation);
if (day != null) {
mLongClickedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Message message = new Message();
message.obj = day;
}
mLongClickedView.clearClickedDay();
mLongClickedView = null;
}
}
}
// Clear the visual cues of the click animation and related running code.
private void clearClickedView(MonthWeekEventsView v) {
mListView.removeCallbacks(mDoClick);
synchronized(v) {
v.clearClickedDay();
}
mClickedView = null;
}
// Perform the tap animation in a runnable to allow a delay before showing the tap color.
// This is done to prevent a click animation when a fling is done.
private final Runnable mDoClick = new Runnable() {
@Override
public void run() {
if (mClickedView != null) {
synchronized(mClickedView) {
mClickedView.setClickedDay(mClickedXLocation);
}
mLongClickedView = mClickedView;
mClickedView = null;
// This is a workaround , sometimes the top item on the listview doesn't refresh on
// invalidate, so this forces a re-draw.
mListView.invalidate();
}
}
};
// Performs the single tap operation: go to the tapped day.
// This is done in a runnable to allow the click animation to finish before switching views
private final Runnable mDoSingleTapUp = new Runnable() {
@Override
public void run() {
if (mSingleTapUpView != null) {
Time day = mSingleTapUpView.getDayFromLocation(mClickedXLocation);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Touched day at Row=" + mSingleTapUpView.mWeek + " day=" + day.toString());
}
if (day != null) {
onDayTapped(day);
}
clearClickedView(mSingleTapUpView);
mSingleTapUpView = null;
}
}
};
}