MonthByWeekFragment.java revision ab348bbe0f26896407e0ae22dae76106839fc87b
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 += " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
159                    + Attendees.ATTENDEE_STATUS_DECLINED;
160        }
161        return where;
162    }
163
164    private void stopLoader() {
165        synchronized (mUpdateLoader) {
166            mHandler.removeCallbacks(mUpdateLoader);
167            if (mLoader != null) {
168                mLoader.stopLoading();
169                if (Log.isLoggable(TAG, Log.DEBUG)) {
170                    Log.d(TAG, "Stopped loader from loading");
171                }
172            }
173        }
174    }
175
176    class MonthGestureListener extends SimpleOnGestureListener {
177        @Override
178        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
179                float velocityY) {
180            // TODO decide how to handle flings
181//            float absX = Math.abs(velocityX);
182//            float absY = Math.abs(velocityY);
183//            Log.d(TAG, "velX: " + velocityX + " velY: " + velocityY);
184//            if (absX > absY && absX > mMinimumFlingVelocity) {
185//                mTempTime.set(mFirstDayOfMonth);
186//                if(velocityX > 0) {
187//                    mTempTime.month++;
188//                } else {
189//                    mTempTime.month--;
190//                }
191//                mTempTime.normalize(true);
192//                goTo(mTempTime, true, false, true);
193//
194//            } else if (absY > absX && absY > mMinimumFlingVelocity) {
195//                mTempTime.set(mFirstDayOfMonth);
196//                int diff = 1;
197//                if (absY > mMinimumTwoMonthFlingVelocity) {
198//                    diff = 2;
199//                }
200//                if(velocityY < 0) {
201//                    mTempTime.month += diff;
202//                } else {
203//                    mTempTime.month -= diff;
204//                }
205//                mTempTime.normalize(true);
206//
207//                goTo(mTempTime, true, false, true);
208//            }
209            return false;
210        }
211    }
212
213    @Override
214    public void onAttach(Activity activity) {
215        super.onAttach(activity);
216        mTZUpdater.run();
217
218        mGestureDetector = new GestureDetector(activity, new MonthGestureListener());
219        ViewConfiguration viewConfig = ViewConfiguration.get(activity);
220        mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity() / 2;
221
222        if (mScale == 0) {
223            mScale = activity.getResources().getDisplayMetrics().density;
224            if (mScale != 1) {
225                SPACING_WEEK_NUMBER *= mScale;
226            }
227        }
228    }
229
230    @Override
231    protected void setUpAdapter() {
232        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
233        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
234
235        HashMap<String, Integer> weekParams = new HashMap<String, Integer>();
236        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks);
237        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_SHOW_WEEK, mShowWeekNumber ? 1 : 0);
238        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek);
239        weekParams.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, mIsMiniMonth ? 1 : 0);
240        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_JULIAN_DAY,
241                Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff));
242        weekParams.put(MonthByWeekSimpleAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek);
243        if (mAdapter == null) {
244            mAdapter = new MonthByWeekAdapter(getActivity(), weekParams);
245            mAdapter.registerDataSetObserver(mObserver);
246        } else {
247            mAdapter.updateParams(weekParams);
248        }
249        mAdapter.notifyDataSetChanged();
250    }
251
252    @Override
253    public View onCreateView(
254            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
255        View v;
256        if (mIsMiniMonth) {
257            v = inflater.inflate(R.layout.month_by_week, container, false);
258        } else {
259            v = inflater.inflate(R.layout.full_month_by_week, container, false);
260        }
261        mDayNamesHeader = (ViewGroup) v.findViewById(R.id.day_names);
262        return v;
263    }
264
265    @Override
266    public void onActivityCreated(Bundle savedInstanceState) {
267        super.onActivityCreated(savedInstanceState);
268        mListView.setOnTouchListener(this);
269        getLoaderManager().initLoader(0, null, this);
270    }
271
272    public MonthByWeekFragment() {
273        this(System.currentTimeMillis(), true);
274    }
275
276    public MonthByWeekFragment(long initialTime, boolean isMiniMonth) {
277        super(initialTime);
278        mIsMiniMonth = isMiniMonth;
279    }
280
281    @Override
282    protected void setUpViewParams() {
283        if (mIsMiniMonth) {
284            super.setUpViewParams();
285            return;
286        }
287
288        mDayLabels = new String[7];
289        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
290            mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(
291                    i, DateUtils.LENGTH_MEDIUM);
292        }
293    }
294
295    // TODO
296    @Override
297    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
298        if (mIsMiniMonth) {
299            return null;
300        }
301        synchronized (mUpdateLoader) {
302            mFirstLoadedJulianDay =
303                    Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
304                    - (mNumWeeks * 7 / 2);
305            mEventUri = updateUri();
306            String where = updateWhere();
307
308            mLoader = new CursorLoader(
309                    getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
310                    null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER);
311        }
312        if (Log.isLoggable(TAG, Log.DEBUG)) {
313            Log.d(TAG, "Returning new loader with uri: " + mEventUri);
314        }
315        return mLoader;
316    }
317
318    @Override
319    public void doResumeUpdates() {
320        mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext);
321        mShowWeekNumber = Utils.getShowWeekNumber(mContext);
322        boolean prevHideDeclined = mHideDeclined;
323        mHideDeclined = Utils.getHideDeclinedEvents(mContext);
324        if (prevHideDeclined != mHideDeclined && mLoader != null) {
325            mLoader.setSelection(updateWhere());
326        }
327        mDaysPerWeek = Utils.getDaysPerWeek(mContext);
328        updateHeader();
329        mTZUpdater.run();
330        goTo(mSelectedDay.toMillis(true), false, true, false);
331        mAdapter.setSelectedDay(mSelectedDay);
332    }
333
334    @Override
335    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
336        synchronized (mUpdateLoader) {
337            if (Log.isLoggable(TAG, Log.DEBUG)) {
338                Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri);
339            }
340            CursorLoader cLoader = (CursorLoader) loader;
341            if (cLoader.getUri().compareTo(mEventUri) != 0) {
342                // We've started a new query since this loader ran so ignore the
343                // result
344                return;
345            }
346            ArrayList<Event> events = new ArrayList<Event>();
347            Event.buildEventsFromCursor(
348                    events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay);
349            ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay,
350                    mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events);
351        }
352    }
353
354    @Override
355    public void eventsChanged() {
356        // TODO Auto-generated method stub
357        // request loader requery if we're not moving
358    }
359
360    @Override
361    public long getSupportedEventTypes() {
362        return EventType.GO_TO;
363    }
364
365    @Override
366    public void handleEvent(EventInfo event) {
367        if (event.eventType == EventType.GO_TO) {
368            goTo(event.startTime.toMillis(true), true, true, false);
369        }
370    }
371
372    @Override
373    protected void setMonthDisplayed(Time time) {
374        super.setMonthDisplayed(time);
375        if (!mIsMiniMonth) {
376            mSelectedDay.set(time);
377            mAdapter.setSelectedDay(time);
378            CalendarController controller = CalendarController.getInstance(mContext);
379            if (time.toMillis(true) != controller.getTime()) {
380                controller.setTime(time.toMillis(true) + DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3);
381            }
382        }
383    }
384
385    @Override
386    public void onScrollStateChanged(AbsListView view, int scrollState) {
387
388        synchronized (mUpdateLoader) {
389            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
390                mShouldLoad = false;
391                stopLoader();
392            } else {
393                mHandler.removeCallbacks(mUpdateLoader);
394                mShouldLoad = true;
395                mHandler.postDelayed(mUpdateLoader, LOADER_DELAY);
396            }
397        }
398
399        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
400    }
401
402    @Override
403    public boolean onTouch(View v, MotionEvent event) {
404        return mGestureDetector.onTouchEvent(event);
405        // TODO post a cleanup to push us back onto the grid if something went
406        // wrong in a scroll such as the user stopping the view but not
407        // scrolling
408    }
409
410}
411