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