• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.calendar.month;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.text.format.Time;
22 import android.util.Log;
23 import android.view.GestureDetector;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.view.ViewConfiguration;
27 import android.view.ViewGroup;
28 import android.widget.AbsListView.LayoutParams;
29 
30 import com.android.calendar.CalendarController;
31 import com.android.calendar.CalendarController.EventType;
32 import com.android.calendar.CalendarController.ViewType;
33 import com.android.calendar.Event;
34 import com.android.calendar.R;
35 import com.android.calendar.Utils;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 
40 public class MonthByWeekAdapter extends SimpleWeeksAdapter {
41     private static final String TAG = "MonthByWeek";
42 
43     public static final String WEEK_PARAMS_IS_MINI = "mini_month";
44     protected static int DEFAULT_QUERY_DAYS = 7 * 8; // 8 weeks
45     private static final long ANIMATE_TODAY_TIMEOUT = 1000;
46 
47     protected CalendarController mController;
48     protected String mHomeTimeZone;
49     protected Time mTempTime;
50     protected Time mToday;
51     protected int mFirstJulianDay;
52     protected int mQueryDays;
53     protected boolean mIsMiniMonth = true;
54     protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
55     private final boolean mShowAgendaWithMonth;
56 
57     protected ArrayList<ArrayList<Event>> mEventDayList = new ArrayList<ArrayList<Event>>();
58     protected ArrayList<Event> mEvents = null;
59 
60     private boolean mAnimateToday = false;
61     private long mAnimateTime = 0;
62 
63     MonthWeekEventsView mClickedView;
64     MonthWeekEventsView mSingleTapUpView;
65 
66     float mClickedXLocation;                // Used to find which day was clicked
67     long mClickTime;                        // Used to calculate minimum click animation time
68     // Used to insure minimal time for seeing the click animation before switching views
69     private static final int mOnTapDelay = 100;
70     // Minimal time for a down touch action before stating the click animation, this insures that
71     // there is no click animation on flings
72     private static int mOnDownDelay;
73     private static int mTotalClickDelay;
74     // Minimal distance to move the finger in order to cancel the click animation
75     private static float mMovedPixelToCancel;
76 
MonthByWeekAdapter(Context context, HashMap<String, Integer> params)77     public MonthByWeekAdapter(Context context, HashMap<String, Integer> params) {
78         super(context, params);
79         if (params.containsKey(WEEK_PARAMS_IS_MINI)) {
80             mIsMiniMonth = params.get(WEEK_PARAMS_IS_MINI) != 0;
81         }
82         mShowAgendaWithMonth = Utils.getConfigBool(context, R.bool.show_agenda_with_month);
83         ViewConfiguration vc = ViewConfiguration.get(context);
84         mOnDownDelay = ViewConfiguration.getTapTimeout();
85         mMovedPixelToCancel = vc.getScaledTouchSlop();
86         mTotalClickDelay = mOnDownDelay + mOnTapDelay;
87 
88     }
89 
animateToday()90     public void animateToday() {
91         mAnimateToday = true;
92         mAnimateTime = System.currentTimeMillis();
93     }
94 
95     @Override
init()96     protected void init() {
97         super.init();
98         mController = CalendarController.getInstance(mContext);
99         mHomeTimeZone = Utils.getTimeZone(mContext, null);
100         mSelectedDay.switchTimezone(mHomeTimeZone);
101         mToday = new Time(mHomeTimeZone);
102         mToday.setToNow();
103         mTempTime = new Time(mHomeTimeZone);
104     }
105 
updateTimeZones()106     private void updateTimeZones() {
107         mSelectedDay.timezone = mHomeTimeZone;
108         mSelectedDay.normalize(true);
109         mToday.timezone = mHomeTimeZone;
110         mToday.setToNow();
111         mTempTime.switchTimezone(mHomeTimeZone);
112     }
113 
114     @Override
setSelectedDay(Time selectedTime)115     public void setSelectedDay(Time selectedTime) {
116         mSelectedDay.set(selectedTime);
117         long millis = mSelectedDay.normalize(true);
118         mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
119                 Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
120         notifyDataSetChanged();
121     }
122 
setEvents(int firstJulianDay, int numDays, ArrayList<Event> events)123     public void setEvents(int firstJulianDay, int numDays, ArrayList<Event> events) {
124         if (mIsMiniMonth) {
125             if (Log.isLoggable(TAG, Log.ERROR)) {
126                 Log.e(TAG, "Attempted to set events for mini view. Events only supported in full"
127                         + " view.");
128             }
129             return;
130         }
131         mEvents = events;
132         mFirstJulianDay = firstJulianDay;
133         mQueryDays = numDays;
134         // Create a new list, this is necessary since the weeks are referencing
135         // pieces of the old list
136         ArrayList<ArrayList<Event>> eventDayList = new ArrayList<ArrayList<Event>>();
137         for (int i = 0; i < numDays; i++) {
138             eventDayList.add(new ArrayList<Event>());
139         }
140 
141         if (events == null || events.size() == 0) {
142             if(Log.isLoggable(TAG, Log.DEBUG)) {
143                 Log.d(TAG, "No events. Returning early--go schedule something fun.");
144             }
145             mEventDayList = eventDayList;
146             refresh();
147             return;
148         }
149 
150         // Compute the new set of days with events
151         for (Event event : events) {
152             int startDay = event.startDay - mFirstJulianDay;
153             int endDay = event.endDay - mFirstJulianDay + 1;
154             if (startDay < numDays || endDay >= 0) {
155                 if (startDay < 0) {
156                     startDay = 0;
157                 }
158                 if (startDay > numDays) {
159                     continue;
160                 }
161                 if (endDay < 0) {
162                     continue;
163                 }
164                 if (endDay > numDays) {
165                     endDay = numDays;
166                 }
167                 for (int j = startDay; j < endDay; j++) {
168                     eventDayList.get(j).add(event);
169                 }
170             }
171         }
172         if(Log.isLoggable(TAG, Log.DEBUG)) {
173             Log.d(TAG, "Processed " + events.size() + " events.");
174         }
175         mEventDayList = eventDayList;
176         refresh();
177     }
178 
179     @SuppressWarnings("unchecked")
180     @Override
getView(int position, View convertView, ViewGroup parent)181     public View getView(int position, View convertView, ViewGroup parent) {
182         if (mIsMiniMonth) {
183             return super.getView(position, convertView, parent);
184         }
185         MonthWeekEventsView v;
186         LayoutParams params = new LayoutParams(
187                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
188         HashMap<String, Integer> drawingParams = null;
189         boolean isAnimatingToday = false;
190         if (convertView != null) {
191             v = (MonthWeekEventsView) convertView;
192             // Checking updateToday uses the current params instead of the new
193             // params, so this is assuming the view is relatively stable
194             if (mAnimateToday && v.updateToday(mSelectedDay.timezone)) {
195                 long currentTime = System.currentTimeMillis();
196                 // If it's been too long since we tried to start the animation
197                 // don't show it. This can happen if the user stops a scroll
198                 // before reaching today.
199                 if (currentTime - mAnimateTime > ANIMATE_TODAY_TIMEOUT) {
200                     mAnimateToday = false;
201                     mAnimateTime = 0;
202                 } else {
203                     isAnimatingToday = true;
204                     // There is a bug that causes invalidates to not work some
205                     // of the time unless we recreate the view.
206                     v = new MonthWeekEventsView(mContext);
207                 }
208             } else {
209                 drawingParams = (HashMap<String, Integer>) v.getTag();
210             }
211         } else {
212             v = new MonthWeekEventsView(mContext);
213         }
214         if (drawingParams == null) {
215             drawingParams = new HashMap<String, Integer>();
216         }
217         drawingParams.clear();
218 
219         v.setLayoutParams(params);
220         v.setClickable(true);
221         v.setOnTouchListener(this);
222 
223         int selectedDay = -1;
224         if (mSelectedWeek == position) {
225             selectedDay = mSelectedDay.weekDay;
226         }
227 
228         drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
229                 (parent.getHeight() + parent.getTop()) / mNumWeeks);
230         drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
231         drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
232         drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
233         drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
234         drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
235         drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
236         drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ORIENTATION, mOrientation);
237 
238         if (isAnimatingToday) {
239             drawingParams.put(MonthWeekEventsView.VIEW_PARAMS_ANIMATE_TODAY, 1);
240             mAnimateToday = false;
241         }
242 
243         v.setWeekParams(drawingParams, mSelectedDay.timezone);
244         sendEventsToView(v);
245         return v;
246     }
247 
sendEventsToView(MonthWeekEventsView v)248     private void sendEventsToView(MonthWeekEventsView v) {
249         if (mEventDayList.size() == 0) {
250             if (Log.isLoggable(TAG, Log.DEBUG)) {
251                 Log.d(TAG, "No events loaded, did not pass any events to view.");
252             }
253             v.setEvents(null, null);
254             return;
255         }
256         int viewJulianDay = v.getFirstJulianDay();
257         int start = viewJulianDay - mFirstJulianDay;
258         int end = start + v.mNumDays;
259         if (start < 0 || end > mEventDayList.size()) {
260             if (Log.isLoggable(TAG, Log.DEBUG)) {
261                 Log.d(TAG, "Week is outside range of loaded events. viewStart: " + viewJulianDay
262                         + " eventsStart: " + mFirstJulianDay);
263             }
264             v.setEvents(null, null);
265             return;
266         }
267         v.setEvents(mEventDayList.subList(start, end), mEvents);
268     }
269 
270     @Override
refresh()271     protected void refresh() {
272         mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
273         mShowWeekNumber = Utils.getShowWeekNumber(mContext);
274         mHomeTimeZone = Utils.getTimeZone(mContext, null);
275         mOrientation = mContext.getResources().getConfiguration().orientation;
276         updateTimeZones();
277         notifyDataSetChanged();
278     }
279 
280     @Override
onDayTapped(Time day)281     protected void onDayTapped(Time day) {
282         day.timezone = mHomeTimeZone;
283         Time currTime = new Time(mHomeTimeZone);
284         currTime.set(mController.getTime());
285         day.hour = currTime.hour;
286         day.minute = currTime.minute;
287         day.allDay = false;
288         day.normalize(true);
289          if (mShowAgendaWithMonth || mIsMiniMonth) {
290             // If agenda view is visible with month view , refresh the views
291             // with the selected day's info
292             mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
293                     ViewType.CURRENT, CalendarController.EXTRA_GOTO_DATE, null, null);
294         } else {
295             // Else , switch to the detailed view
296             mController.sendEvent(mContext, EventType.GO_TO, day, day, -1,
297                     ViewType.DETAIL,
298                             CalendarController.EXTRA_GOTO_DATE
299                             | CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS, null, null);
300         }
301     }
302 
303     @Override
onTouch(View v, MotionEvent event)304     public boolean onTouch(View v, MotionEvent event) {
305         if (!(v instanceof MonthWeekEventsView)) {
306             return super.onTouch(v, event);
307         }
308 
309         int action = event.getAction();
310 
311         // Event was tapped - switch to the detailed view making sure the click animation
312         // is done first.
313         if (mGestureDetector.onTouchEvent(event)) {
314             mSingleTapUpView = (MonthWeekEventsView) v;
315             long delay = System.currentTimeMillis() - mClickTime;
316             // Make sure the animation is visible for at least mOnTapDelay - mOnDownDelay ms
317             mListView.postDelayed(mDoSingleTapUp,
318                     delay > mTotalClickDelay ? 0 : mTotalClickDelay - delay);
319             return true;
320         } else {
321             // Animate a click - on down: show the selected day in the "clicked" color.
322             // On Up/scroll/move/cancel: hide the "clicked" color.
323             switch (action) {
324                 case MotionEvent.ACTION_DOWN:
325                     mClickedView = (MonthWeekEventsView)v;
326                     mClickedXLocation = event.getX();
327                     mClickTime = System.currentTimeMillis();
328                     mListView.postDelayed(mDoClick, mOnDownDelay);
329                     break;
330                 case MotionEvent.ACTION_UP:
331                 case MotionEvent.ACTION_SCROLL:
332                 case MotionEvent.ACTION_CANCEL:
333                     clearClickedView((MonthWeekEventsView)v);
334                     break;
335                 case MotionEvent.ACTION_MOVE:
336                     // No need to cancel on vertical movement, ACTION_SCROLL will do that.
337                     if (Math.abs(event.getX() - mClickedXLocation) > mMovedPixelToCancel) {
338                         clearClickedView((MonthWeekEventsView)v);
339                     }
340                     break;
341                 default:
342                     break;
343             }
344         }
345         // Do not tell the frameworks we consumed the touch action so that fling actions can be
346         // processed by the fragment.
347         return false;
348     }
349 
350     /**
351      * This is here so we can identify events and process them
352      */
353     protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
354         @Override
onSingleTapUp(MotionEvent e)355         public boolean onSingleTapUp(MotionEvent e) {
356             return true;
357         }
358     }
359 
360     // Clear the visual cues of the click animation and related running code.
clearClickedView(MonthWeekEventsView v)361     private void clearClickedView(MonthWeekEventsView v) {
362         mListView.removeCallbacks(mDoClick);
363         synchronized(v) {
364             v.clearClickedDay();
365         }
366         mClickedView = null;
367     }
368 
369     // Perform the tap animation in a runnable to allow a delay before showing the tap color.
370     // This is done to prevent a click animation when a fling is done.
371     private final Runnable mDoClick = new Runnable() {
372         @Override
373         public void run() {
374             if (mClickedView != null) {
375                 synchronized(mClickedView) {
376                     mClickedView.setClickedDay(mClickedXLocation);
377                 }
378                 mClickedView = null;
379                 // This is a workaround , sometimes the top item on the listview doesn't refresh on
380                 // invalidate, so this forces a re-draw.
381                 mListView.invalidate();
382             }
383         }
384     };
385 
386     // Performs the single tap operation: go to the tapped day.
387     // This is done in a runnable to allow the click animation to finish before switching views
388     private final Runnable mDoSingleTapUp = new Runnable() {
389         @Override
390         public void run() {
391             if (mSingleTapUpView != null) {
392                 Time day = mSingleTapUpView.getDayFromLocation(mClickedXLocation);
393                 if (Log.isLoggable(TAG, Log.DEBUG)) {
394                     Log.d(TAG, "Touched day at Row=" + mSingleTapUpView.mWeek + " day=" + day.toString());
395                 }
396                 if (day != null) {
397                     onDayTapped(day);
398                 }
399                 clearClickedView(mSingleTapUpView);
400                 mSingleTapUpView = null;
401             }
402         }
403     };
404 }
405