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