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