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