• 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.app.Activity;
20 import android.app.LoaderManager;
21 import android.content.ContentUris;
22 import android.content.CursorLoader;
23 import android.content.Loader;
24 import android.content.res.Resources;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.provider.CalendarContract.Attendees;
29 import android.provider.CalendarContract.Calendars;
30 import android.provider.CalendarContract.Instances;
31 import android.text.format.DateUtils;
32 import android.text.format.Time;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.View.OnTouchListener;
38 import android.view.ViewConfiguration;
39 import android.view.ViewGroup;
40 import android.widget.AbsListView;
41 import android.widget.AbsListView.OnScrollListener;
42 
43 import com.android.calendar.CalendarController;
44 import com.android.calendar.CalendarController.EventInfo;
45 import com.android.calendar.CalendarController.EventType;
46 import com.android.calendar.CalendarController.ViewType;
47 import com.android.calendar.Event;
48 import com.android.calendar.R;
49 import com.android.calendar.Utils;
50 
51 import java.util.ArrayList;
52 import java.util.Calendar;
53 import java.util.HashMap;
54 import java.util.List;
55 
56 public class MonthByWeekFragment extends SimpleDayPickerFragment implements
57         CalendarController.EventHandler, LoaderManager.LoaderCallbacks<Cursor>, OnScrollListener,
58         OnTouchListener {
59     private static final String TAG = "MonthFragment";
60 
61     // Selection and selection args for adding event queries
62     private static final String WHERE_CALENDARS_VISIBLE = Calendars.VISIBLE + "=1";
63     private static final String INSTANCES_SORT_ORDER = Instances.START_DAY + ","
64             + Instances.START_MINUTE + "," + Instances.TITLE;
65     protected static boolean mShowDetailsInMonth = false;
66 
67     protected float mMinimumTwoMonthFlingVelocity;
68     protected boolean mIsMiniMonth;
69     protected boolean mHideDeclined;
70 
71     protected int mFirstLoadedJulianDay;
72     protected int mLastLoadedJulianDay;
73 
74     private static final int WEEKS_BUFFER = 1;
75     // How long to wait after scroll stops before starting the loader
76     // Using scroll duration because scroll state changes don't update
77     // correctly when a scroll is triggered programmatically.
78     private static final int LOADER_DELAY = 200;
79     // The minimum time between requeries of the data if the db is
80     // changing
81     private static final int LOADER_THROTTLE_DELAY = 500;
82 
83     private CursorLoader mLoader;
84     private Uri mEventUri;
85     private final Time mDesiredDay = new Time();
86 
87     private volatile boolean mShouldLoad = true;
88     private boolean mUserScrolled = false;
89 
90     private int mEventsLoadingDelay;
91     private boolean mShowCalendarControls;
92     private boolean mIsDetached;
93 
94     private final Runnable mTZUpdater = new Runnable() {
95         @Override
96         public void run() {
97             String tz = Utils.getTimeZone(mContext, mTZUpdater);
98             mSelectedDay.timezone = tz;
99             mSelectedDay.normalize(true);
100             mTempTime.timezone = tz;
101             mFirstDayOfMonth.timezone = tz;
102             mFirstDayOfMonth.normalize(true);
103             mFirstVisibleDay.timezone = tz;
104             mFirstVisibleDay.normalize(true);
105             if (mAdapter != null) {
106                 mAdapter.refresh();
107             }
108         }
109     };
110 
111 
112     private final Runnable mUpdateLoader = new Runnable() {
113         @Override
114         public void run() {
115             synchronized (this) {
116                 if (!mShouldLoad || mLoader == null) {
117                     return;
118                 }
119                 // Stop any previous loads while we update the uri
120                 stopLoader();
121 
122                 // Start the loader again
123                 mEventUri = updateUri();
124 
125                 mLoader.setUri(mEventUri);
126                 mLoader.startLoading();
127                 mLoader.onContentChanged();
128                 if (Log.isLoggable(TAG, Log.DEBUG)) {
129                     Log.d(TAG, "Started loader with uri: " + mEventUri);
130                 }
131             }
132         }
133     };
134     // Used to load the events when a delay is needed
135     Runnable mLoadingRunnable = new Runnable() {
136         @Override
137         public void run() {
138             if (!mIsDetached) {
139                 mLoader = (CursorLoader) getLoaderManager().initLoader(0, null,
140                         MonthByWeekFragment.this);
141             }
142         }
143     };
144 
145 
146     /**
147      * Updates the uri used by the loader according to the current position of
148      * the listview.
149      *
150      * @return The new Uri to use
151      */
updateUri()152     private Uri updateUri() {
153         SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0);
154         if (child != null) {
155             int julianDay = child.getFirstJulianDay();
156             mFirstLoadedJulianDay = julianDay;
157         }
158         // -1 to ensure we get all day events from any time zone
159         mTempTime.setJulianDay(mFirstLoadedJulianDay - 1);
160         long start = mTempTime.toMillis(true);
161         mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7;
162         // +1 to ensure we get all day events from any time zone
163         mTempTime.setJulianDay(mLastLoadedJulianDay + 1);
164         long end = mTempTime.toMillis(true);
165 
166         // Create a new uri with the updated times
167         Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
168         ContentUris.appendId(builder, start);
169         ContentUris.appendId(builder, end);
170         return builder.build();
171     }
172 
173     // Extract range of julian days from URI
updateLoadedDays()174     private void updateLoadedDays() {
175         List<String> pathSegments = mEventUri.getPathSegments();
176         int size = pathSegments.size();
177         if (size <= 2) {
178             return;
179         }
180         long first = Long.parseLong(pathSegments.get(size - 2));
181         long last = Long.parseLong(pathSegments.get(size - 1));
182         mTempTime.set(first);
183         mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff);
184         mTempTime.set(last);
185         mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff);
186     }
187 
updateWhere()188     protected String updateWhere() {
189         // TODO fix selection/selection args after b/3206641 is fixed
190         String where = WHERE_CALENDARS_VISIBLE;
191         if (mHideDeclined || !mShowDetailsInMonth) {
192             where += " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
193                     + Attendees.ATTENDEE_STATUS_DECLINED;
194         }
195         return where;
196     }
197 
stopLoader()198     private void stopLoader() {
199         synchronized (mUpdateLoader) {
200             mHandler.removeCallbacks(mUpdateLoader);
201             if (mLoader != null) {
202                 mLoader.stopLoading();
203                 if (Log.isLoggable(TAG, Log.DEBUG)) {
204                     Log.d(TAG, "Stopped loader from loading");
205                 }
206             }
207         }
208     }
209 
210     @Override
onAttach(Activity activity)211     public void onAttach(Activity activity) {
212         super.onAttach(activity);
213         mTZUpdater.run();
214         if (mAdapter != null) {
215             mAdapter.setSelectedDay(mSelectedDay);
216         }
217         mIsDetached = false;
218 
219         ViewConfiguration viewConfig = ViewConfiguration.get(activity);
220         mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity() / 2;
221         Resources res = activity.getResources();
222         mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls);
223         // Synchronized the loading time of the month's events with the animation of the
224         // calendar controls.
225         if (mShowCalendarControls) {
226             mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time);
227         }
228         mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month);
229     }
230 
231     @Override
onDetach()232     public void onDetach() {
233         mIsDetached = true;
234         super.onDetach();
235         if (mShowCalendarControls) {
236             if (mListView != null) {
237                 mListView.removeCallbacks(mLoadingRunnable);
238             }
239         }
240     }
241 
242     @Override
setUpAdapter()243     protected void setUpAdapter() {
244         mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
245         mShowWeekNumber = Utils.getShowWeekNumber(mContext);
246 
247         HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
248         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
249         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
250         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
251         weekParams.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, mIsMiniMonth ? 1 : 0);
252         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
253                 Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff));
254         weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek);
255         if (mAdapter == null) {
256             mAdapter = new MonthByWeekAdapter(getActivity(), weekParams);
257             mAdapter.registerDataSetObserver(mObserver);
258         } else {
259             mAdapter.updateParams(weekParams);
260         }
261         mAdapter.notifyDataSetChanged();
262     }
263 
264     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)265     public View onCreateView(
266             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
267         View v;
268         if (mIsMiniMonth) {
269             v = inflater.inflate(R.layout.month_by_week, container, false);
270         } else {
271             v = inflater.inflate(R.layout.full_month_by_week, container, false);
272         }
273         mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
274         return v;
275     }
276 
277     @Override
onActivityCreated(Bundle savedInstanceState)278     public void onActivityCreated(Bundle savedInstanceState) {
279         super.onActivityCreated(savedInstanceState);
280         mListView.setOnTouchListener(this);
281         if (!mIsMiniMonth) {
282             mListView.setBackgroundColor(getResources().getColor(R.color.month_bgcolor));
283         }
284 
285         // To get a smoother transition when showing this fragment, delay loading of events until
286         // the fragment is expended fully and the calendar controls are gone.
287         if (mShowCalendarControls) {
288             mListView.postDelayed(mLoadingRunnable, mEventsLoadingDelay);
289         } else {
290             mLoader = (CursorLoader) getLoaderManager().initLoader(0, null, this);
291         }
292         mAdapter.setListView(mListView);
293     }
294 
MonthByWeekFragment()295     public MonthByWeekFragment() {
296         this(System.currentTimeMillis(), true);
297     }
298 
MonthByWeekFragment(long initialTime, boolean isMiniMonth)299     public MonthByWeekFragment(long initialTime, boolean isMiniMonth) {
300         super(initialTime);
301         mIsMiniMonth = isMiniMonth;
302     }
303 
304     @Override
setUpHeader()305     protected void setUpHeader() {
306         if (mIsMiniMonth) {
307             super.setUpHeader();
308             return;
309         }
310 
311         mDayLabels = new String[7];
312         for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
313             mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(i,
314                     DateUtils.LENGTH_MEDIUM).toUpperCase();
315         }
316     }
317 
318     // TODO
319     @Override
onCreateLoader(int id, Bundle args)320     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
321         if (mIsMiniMonth) {
322             return null;
323         }
324         CursorLoader loader;
325         synchronized (mUpdateLoader) {
326             mFirstLoadedJulianDay =
327                     Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
328                     - (mNumWeeks * 7 / 2);
329             mEventUri = updateUri();
330             String where = updateWhere();
331 
332             loader = new CursorLoader(
333                     getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
334                     null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER);
335             loader.setUpdateThrottle(LOADER_THROTTLE_DELAY);
336         }
337         if (Log.isLoggable(TAG, Log.DEBUG)) {
338             Log.d(TAG, "Returning new loader with uri: " + mEventUri);
339         }
340         return loader;
341     }
342 
343     @Override
doResumeUpdates()344     public void doResumeUpdates() {
345         mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
346         mShowWeekNumber = Utils.getShowWeekNumber(mContext);
347         boolean prevHideDeclined = mHideDeclined;
348         mHideDeclined = Utils.getHideDeclinedEvents(mContext);
349         if (prevHideDeclined != mHideDeclined && mLoader != null) {
350             mLoader.setSelection(updateWhere());
351         }
352         mDaysPerWeek = Utils.getDaysPerWeek(mContext);
353         updateHeader();
354         mAdapter.setSelectedDay(mSelectedDay);
355         mTZUpdater.run();
356         mTodayUpdater.run();
357         goTo(mSelectedDay.toMillis(true), false, true, false);
358     }
359 
360     @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)361     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
362         synchronized (mUpdateLoader) {
363             if (Log.isLoggable(TAG, Log.DEBUG)) {
364                 Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri);
365             }
366             CursorLoader cLoader = (CursorLoader) loader;
367             if (mEventUri == null) {
368                 mEventUri = cLoader.getUri();
369                 updateLoadedDays();
370             }
371             if (cLoader.getUri().compareTo(mEventUri) != 0) {
372                 // We've started a new query since this loader ran so ignore the
373                 // result
374                 return;
375             }
376             ArrayList<Event> events = new ArrayList<Event>();
377             Event.buildEventsFromCursor(
378                     events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay);
379             ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay,
380                     mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events);
381         }
382     }
383 
384     @Override
onLoaderReset(Loader<Cursor> loader)385     public void onLoaderReset(Loader<Cursor> loader) {
386     }
387 
388     @Override
eventsChanged()389     public void eventsChanged() {
390         // TODO remove this after b/3387924 is resolved
391         if (mLoader != null) {
392             mLoader.forceLoad();
393         }
394     }
395 
396     @Override
getSupportedEventTypes()397     public long getSupportedEventTypes() {
398         return EventType.GO_TO | EventType.EVENTS_CHANGED;
399     }
400 
401     @Override
handleEvent(EventInfo event)402     public void handleEvent(EventInfo event) {
403         if (event.eventType == EventType.GO_TO) {
404             boolean animate = true;
405             if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
406                     Time.getJulianDay(event.selectedTime.toMillis(true), event.selectedTime.gmtoff)
407                     - Time.getJulianDay(mFirstVisibleDay.toMillis(true), mFirstVisibleDay.gmtoff)
408                     - mDaysPerWeek * mNumWeeks / 2)) {
409                 animate = false;
410             }
411             mDesiredDay.set(event.selectedTime);
412             mDesiredDay.normalize(true);
413             boolean animateToday = (event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0;
414             boolean delayAnimation = goTo(event.selectedTime.toMillis(true), animate, true, false);
415             if (animateToday) {
416                 // If we need to flash today start the animation after any
417                 // movement from listView has ended.
418                 mHandler.postDelayed(new Runnable() {
419                     @Override
420                     public void run() {
421                         ((MonthByWeekAdapter) mAdapter).animateToday();
422                         mAdapter.notifyDataSetChanged();
423                     }
424                 }, delayAnimation ? GOTO_SCROLL_DURATION : 0);
425             }
426         } else if (event.eventType == EventType.EVENTS_CHANGED) {
427             eventsChanged();
428         }
429     }
430 
431     @Override
setMonthDisplayed(Time time, boolean updateHighlight)432     protected void setMonthDisplayed(Time time, boolean updateHighlight) {
433         super.setMonthDisplayed(time, updateHighlight);
434         if (!mIsMiniMonth) {
435             boolean useSelected = false;
436             if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
437                 mSelectedDay.set(mDesiredDay);
438                 mAdapter.setSelectedDay(mDesiredDay);
439                 useSelected = true;
440             } else {
441                 mSelectedDay.set(time);
442                 mAdapter.setSelectedDay(time);
443             }
444             CalendarController controller = CalendarController.getInstance(mContext);
445             if (mSelectedDay.minute >= 30) {
446                 mSelectedDay.minute = 30;
447             } else {
448                 mSelectedDay.minute = 0;
449             }
450             long newTime = mSelectedDay.normalize(true);
451             if (newTime != controller.getTime() && mUserScrolled) {
452                 long offset = useSelected ? 0 : DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3;
453                 controller.setTime(newTime + offset);
454             }
455             controller.sendEvent(this, EventType.UPDATE_TITLE, time, time, time, -1,
456                     ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
457                             | DateUtils.FORMAT_SHOW_YEAR, null, null);
458         }
459     }
460 
461     @Override
onScrollStateChanged(AbsListView view, int scrollState)462     public void onScrollStateChanged(AbsListView view, int scrollState) {
463 
464         synchronized (mUpdateLoader) {
465             if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
466                 mShouldLoad = false;
467                 stopLoader();
468                 mDesiredDay.setToNow();
469             } else {
470                 mHandler.removeCallbacks(mUpdateLoader);
471                 mShouldLoad = true;
472                 mHandler.postDelayed(mUpdateLoader, LOADER_DELAY);
473             }
474         }
475         if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
476             mUserScrolled = true;
477         }
478 
479         mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
480     }
481 
482     @Override
onTouch(View v, MotionEvent event)483     public boolean onTouch(View v, MotionEvent event) {
484         mDesiredDay.setToNow();
485         return false;
486         // TODO post a cleanup to push us back onto the grid if something went
487         // wrong in a scroll such as the user stopping the view but not
488         // scrolling
489     }
490 }
491