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