MonthByWeekFragment.java revision 144edfa3f69dd13d2ae5f107a8f4d422f11dc620
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
17package com.android.calendar.month;
18
19import com.android.calendar.CalendarController;
20import com.android.calendar.CalendarController.EventInfo;
21import com.android.calendar.CalendarController.EventType;
22import com.android.calendar.Event;
23import com.android.calendar.R;
24import com.android.calendar.Utils;
25
26import android.app.Activity;
27import android.app.LoaderManager;
28import android.content.ContentUris;
29import android.content.CursorLoader;
30import android.content.Loader;
31import android.database.Cursor;
32import android.net.Uri;
33import android.os.Bundle;
34import android.provider.Calendar.Attendees;
35import android.provider.Calendar.Calendars;
36import android.provider.Calendar.Instances;
37import android.text.format.DateUtils;
38import android.text.format.Time;
39import android.util.Log;
40import android.view.GestureDetector;
41import android.view.GestureDetector.SimpleOnGestureListener;
42import android.view.LayoutInflater;
43import android.view.MotionEvent;
44import android.view.View;
45import android.view.View.OnTouchListener;
46import android.view.ViewConfiguration;
47import android.view.ViewGroup;
48import android.widget.AbsListView;
49import android.widget.AbsListView.OnScrollListener;
50
51import java.util.ArrayList;
52import java.util.Calendar;
53import java.util.HashMap;
54
55public class MonthByWeekFragment extends MonthByWeekSimpleFragment implements
56        CalendarController.EventHandler, LoaderManager.LoaderCallbacks<Cursor>, OnScrollListener,
57        OnTouchListener {
58    private static final String TAG = "MonthFragment";
59
60    // Selection and selection args for adding event queries
61    private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
62    private static final String[] WHERE_CALENDARS_SELECTED_ARGS = {"1"};
63    private static final String INSTANCES_SORT_ORDER = Instances.START_DAY + ","
64            + Instances.START_MINUTE + "," + Instances.TITLE;
65
66    protected float mMinimumTwoMonthFlingVelocity;
67    protected boolean mIsMiniMonth;
68    protected boolean mHideDeclined;
69
70    protected int mFirstLoadedJulianDay;
71    protected int mLastLoadedJulianDay;
72
73    private static final int WEEKS_BUFFER = 1;
74    // How long to wait after scroll stops before starting the loader
75    // Using scroll duration because scroll state changes don't update
76    // correctly when a scroll is triggered programmatically.
77    private static final int LOADER_DELAY = 200;
78
79    private CursorLoader mLoader;
80    private Uri mEventUri;
81    private GestureDetector mGestureDetector;
82
83    private volatile boolean mShouldLoad = true;
84
85    private static float mScale = 0;
86    private static int SPACING_WEEK_NUMBER = 19;
87
88    private Runnable mTZUpdater = new Runnable() {
89        @Override
90        public void run() {
91            String tz = Utils.getTimeZone(mContext, mTZUpdater);
92            mSelectedDay.timezone = tz;
93            mSelectedDay.normalize(true);
94            mTempTime.timezone = tz;
95            mFirstDayOfMonth.timezone = tz;
96            mFirstDayOfMonth.normalize(true);
97            mFirstVisibleDay.timezone = tz;
98            mFirstVisibleDay.normalize(true);
99            if (mAdapter != null) {
100                mAdapter.refresh();
101            }
102        }
103    };
104
105
106    private Runnable mUpdateLoader = new Runnable() {
107        @Override
108        public void run() {
109            synchronized (this) {
110                if (!mShouldLoad || mLoader == null) {
111                    return;
112                }
113                // Stop any previous loads while we update the uri
114                stopLoader();
115
116                // Start the loader again
117                mEventUri = updateUri();
118                mLoader.setUri(mEventUri);
119                mLoader.startLoading();
120                if (Log.isLoggable(TAG, Log.DEBUG)) {
121                    Log.d(TAG, "Started loader with uri: " + mEventUri);
122                }
123            }
124        }
125    };
126
127    /**
128     * Updates the uri used by the loader according to the current position of
129     * the listview.
130     *
131     * @return The new Uri to use
132     */
133    private Uri updateUri() {
134        MonthWeekSimpleView child = (MonthWeekSimpleView) mListView.getChildAt(0);
135        if (child != null) {
136            int julianDay = child.getFirstJulianDay();
137            mFirstLoadedJulianDay = julianDay;
138        }
139        // -1 to ensure we get all day events from any time zone
140        mTempTime.setJulianDay(mFirstLoadedJulianDay - 1);
141        long start = mTempTime.toMillis(true);
142        mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7;
143        // +1 to ensure we get all day events from any time zone
144        mTempTime.setJulianDay(mLastLoadedJulianDay + 1);
145        long end = mTempTime.toMillis(true);
146
147        // Create a new uri with the updated times
148        Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
149        ContentUris.appendId(builder, start);
150        ContentUris.appendId(builder, end);
151        return builder.build();
152    }
153
154    protected String updateWhere() {
155        // TODO fix selection/selection args after b/3206641 is fixed
156        String where = WHERE_CALENDARS_SELECTED;
157        if (mHideDeclined) {
158            where += Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
159        }
160        return where;
161    }
162
163    private void stopLoader() {
164        synchronized (mUpdateLoader) {
165            mHandler.removeCallbacks(mUpdateLoader);
166            if (mLoader != null) {
167                mLoader.stopLoading();
168                if (Log.isLoggable(TAG, Log.DEBUG)) {
169                    Log.d(TAG, "Stopped loader from loading");
170                }
171            }
172        }
173    }
174
175    class MonthGestureListener extends SimpleOnGestureListener {
176        @Override
177        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
178                float velocityY) {
179            // TODO decide how to handle flings
180//            float absX = Math.abs(velocityX);
181//            float absY = Math.abs(velocityY);
182//            Log.d(TAG, "velX: " + velocityX + " velY: " + velocityY);
183//            if (absX > absY && absX > mMinimumFlingVelocity) {
184//                mTempTime.set(mFirstDayOfMonth);
185//                if(velocityX > 0) {
186//                    mTempTime.month++;
187//                } else {
188//                    mTempTime.month--;
189//                }
190//                mTempTime.normalize(true);
191//                goTo(mTempTime, true, false, true);
192//
193//            } else if (absY > absX && absY > mMinimumFlingVelocity) {
194//                mTempTime.set(mFirstDayOfMonth);
195//                int diff = 1;
196//                if (absY > mMinimumTwoMonthFlingVelocity) {
197//                    diff = 2;
198//                }
199//                if(velocityY < 0) {
200//                    mTempTime.month += diff;
201//                } else {
202//                    mTempTime.month -= diff;
203//                }
204//                mTempTime.normalize(true);
205//
206//                goTo(mTempTime, true, false, true);
207//            }
208            return false;
209        }
210    }
211
212    @Override
213    public void onAttach(Activity activity) {
214        super.onAttach(activity);
215        mTZUpdater.run();
216
217        mGestureDetector = new GestureDetector(activity, new MonthGestureListener());
218        ViewConfiguration viewConfig = ViewConfiguration.get(activity);
219        mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity() / 2;
220
221        if (mScale == 0) {
222            mScale = activity.getResources().getDisplayMetrics().density;
223            if (mScale != 1) {
224                SPACING_WEEK_NUMBER *= mScale;
225            }
226        }
227    }
228
229    @Override
230    protected void setUpAdapter() {
231        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
232        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
233
234        HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
235        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
236        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
237        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
238        weekParams.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, mIsMiniMonth ? 1 : 0);
239        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_JULIAN_DAY,
240                Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff));
241        if (mAdapter == null) {
242            mAdapter = new MonthByWeekAdapter(getActivity(), weekParams);
243            mAdapter.registerDataSetObserver(mObserver);
244        } else {
245            mAdapter.updateParams(weekParams);
246        }
247        mAdapter.notifyDataSetChanged();
248    }
249
250    @Override
251    public View onCreateView(
252            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
253        View v;
254        if (mIsMiniMonth) {
255            v = inflater.inflate(R.layout.month_by_week, container, false);
256        } else {
257            v = inflater.inflate(R.layout.full_month_by_week, container, false);
258        }
259        mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
260        return v;
261    }
262
263    @Override
264    public void onActivityCreated(Bundle savedInstanceState) {
265        super.onActivityCreated(savedInstanceState);
266        mListView.setOnTouchListener(this);
267        getLoaderManager().initLoader(0, null, this);
268    }
269
270    public MonthByWeekFragment() {
271        this(System.currentTimeMillis(), true);
272    }
273
274    public MonthByWeekFragment(long initialTime, boolean isMiniMonth) {
275        super(initialTime);
276        mIsMiniMonth = isMiniMonth;
277    }
278
279    @Override
280    protected void setUpViewParams() {
281        if (mIsMiniMonth) {
282            super.setUpViewParams();
283            return;
284        }
285
286        mDayLabels = new String[7];
287        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
288            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(
289                    i, DateUtils.LENGTH_MEDIUM);
290        }
291    }
292
293    // TODO
294    @Override
295    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
296        if (mIsMiniMonth) {
297            return null;
298        }
299        synchronized (mUpdateLoader) {
300            mFirstLoadedJulianDay =
301                    Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
302                    - (mNumWeeks * 7 / 2);
303            mEventUri = updateUri();
304            String where = updateWhere();
305
306            mLoader = new CursorLoader(
307                    getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
308                    null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER);
309        }
310        if (Log.isLoggable(TAG, Log.DEBUG)) {
311            Log.d(TAG, "Returning new loader with uri: " + mEventUri);
312        }
313        return mLoader;
314    }
315
316    @Override
317    public void doResumeUpdates() {
318        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
319        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
320        boolean prevHideDeclined = mHideDeclined;
321        mHideDeclined = Utils.getHideDeclinedEvents(mContext);
322        if (prevHideDeclined != mHideDeclined) {
323            mLoader.setSelection(updateWhere());
324        }
325        updateHeader();
326        mTZUpdater.run();
327        goTo(mSelectedDay.toMillis(true), false, true, false);
328        mAdapter.setSelectedDay(mSelectedDay);
329    }
330
331    @Override
332    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
333        synchronized (mUpdateLoader) {
334            if (Log.isLoggable(TAG, Log.DEBUG)) {
335                Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri);
336            }
337            CursorLoader cLoader = (CursorLoader) loader;
338            if (cLoader.getUri().compareTo(mEventUri) != 0) {
339                // We've started a new query since this loader ran so ignore the
340                // result
341                return;
342            }
343            ArrayList<Event> events = new ArrayList<Event>();
344            Event.buildEventsFromCursor(
345                    events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay);
346            ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay,
347                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events);
348        }
349    }
350
351    @Override
352    public boolean getAllDay() {
353        return false;
354    }
355
356    @Override
357    public void eventsChanged() {
358        // TODO Auto-generated method stub
359        // request loader requery if we're not moving
360    }
361
362    @Override
363    public long getSupportedEventTypes() {
364        return EventType.GO_TO;
365    }
366
367    @Override
368    public void goTo(Time time, boolean animate) {
369        if (time == null) {
370            return;
371        }
372        goTo(time.toMillis(true), animate, true, false);
373    }
374
375    @Override
376    public void goToToday() {
377        // TODO Auto-generated method stub
378
379    }
380
381    @Override
382    public void handleEvent(EventInfo event) {
383        if (event.eventType == EventType.GO_TO) {
384            goTo(event.startTime, true);
385        }
386    }
387
388    @Override
389    protected void setMonthDisplayed(Time time) {
390        super.setMonthDisplayed(time);
391        if (!mIsMiniMonth) {
392            mSelectedDay.set(time);
393            mAdapter.setSelectedDay(time);
394            CalendarController controller = CalendarController.getInstance(mContext);
395            if (time.toMillis(true) != controller.getTime()) {
396                controller.setTime(time.toMillis(true) + DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3);
397            }
398        }
399    }
400
401    @Override
402    public void onScrollStateChanged(AbsListView view, int scrollState) {
403
404        synchronized (mUpdateLoader) {
405            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
406                mShouldLoad = false;
407                stopLoader();
408            } else {
409                mHandler.removeCallbacks(mUpdateLoader);
410                mShouldLoad = true;
411                mHandler.postDelayed(mUpdateLoader, LOADER_DELAY);
412            }
413        }
414
415        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
416    }
417
418    @Override
419    public boolean onTouch(View v, MotionEvent event) {
420        return mGestureDetector.onTouchEvent(event);
421        // TODO post a cleanup to push us back onto the grid if something went
422        // wrong in a scroll such as the user stopping the view but not
423        // scrolling
424    }
425
426}
427