1 /* 2 * Copyright (C) 2007 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 17 package com.android.calendar; 18 19 import dalvik.system.VMRuntime; 20 21 import android.app.Activity; 22 import android.content.BroadcastReceiver; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.database.ContentObserver; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.provider.Calendar; 31 import android.text.format.Time; 32 import android.view.GestureDetector; 33 import android.view.Menu; 34 import android.view.MenuItem; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.animation.Animation; 38 import android.view.animation.AnimationUtils; 39 import android.view.animation.TranslateAnimation; 40 import android.widget.ProgressBar; 41 import android.widget.ViewSwitcher; 42 43 /** 44 * This is the base class for Day and Week Activities. 45 */ 46 public class CalendarActivity extends Activity implements Navigator { 47 48 private static final long INITIAL_HEAP_SIZE = 4*1024*1024; 49 private static final long ANIMATION_DURATION = 400; 50 51 protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; 52 53 private ContentResolver mContentResolver; 54 55 protected ProgressBar mProgressBar; 56 protected ViewSwitcher mViewSwitcher; 57 protected Animation mInAnimationForward; 58 protected Animation mOutAnimationForward; 59 protected Animation mInAnimationBackward; 60 protected Animation mOutAnimationBackward; 61 EventLoader mEventLoader; 62 63 Time mSelectedDay; 64 65 // This gets run if the time zone is updated in the db 66 private Runnable mUpdateTZ = new Runnable() { 67 @Override 68 public void run() { 69 // We want this to keep the same day so we swap the tz 70 mSelectedDay.timezone = Utils.getTimeZone(CalendarActivity.this, this); 71 mSelectedDay.normalize(true); 72 } 73 }; 74 75 /* package */ GestureDetector mGestureDetector; 76 77 /** 78 * Listens for intent broadcasts 79 */ 80 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 81 @Override 82 public void onReceive(Context context, Intent intent) { 83 String action = intent.getAction(); 84 if (action.equals(Intent.ACTION_TIME_CHANGED) 85 || action.equals(Intent.ACTION_DATE_CHANGED) 86 || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { 87 eventsChanged(); 88 } 89 } 90 }; 91 92 // Create an observer so that we can update the views whenever a 93 // Calendar event changes. 94 private ContentObserver mObserver = new ContentObserver(new Handler()) 95 { 96 @Override 97 public boolean deliverSelfNotifications() { 98 return true; 99 } 100 101 @Override 102 public void onChange(boolean selfChange) { 103 eventsChanged(); 104 } 105 }; 106 107 @Override 108 protected void onCreate(Bundle icicle) { 109 super.onCreate(icicle); 110 111 mSelectedDay = new Time(Utils.getTimeZone(this, mUpdateTZ)); 112 113 // Eliminate extra GCs during startup by setting the initial heap size to 4MB. 114 // TODO: We should restore the old heap size once the activity reaches the idle state 115 VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); 116 117 setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); 118 mContentResolver = getContentResolver(); 119 120 mInAnimationForward = AnimationUtils.loadAnimation(this, R.anim.slide_left_in); 121 mOutAnimationForward = AnimationUtils.loadAnimation(this, R.anim.slide_left_out); 122 mInAnimationBackward = AnimationUtils.loadAnimation(this, R.anim.slide_right_in); 123 mOutAnimationBackward = AnimationUtils.loadAnimation(this, R.anim.slide_right_out); 124 125 mGestureDetector = new GestureDetector(this, new CalendarGestureListener()); 126 mEventLoader = new EventLoader(this); 127 } 128 129 @Override 130 protected void onRestoreInstanceState(Bundle savedInstanceState) { 131 super.onRestoreInstanceState(savedInstanceState); 132 133 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 134 Time time = new Time(Utils.getTimeZone(this, mUpdateTZ)); 135 time.set(savedInstanceState.getLong(BUNDLE_KEY_RESTORE_TIME)); 136 view.setSelectedDay(time); 137 } 138 139 @Override 140 protected void onNewIntent(Intent intent) { 141 long timeMillis = Utils.timeFromIntentInMillis(intent); 142 if (timeMillis > 0) { 143 Time time = new Time(Utils.getTimeZone(this, mUpdateTZ)); 144 time.set(timeMillis); 145 goTo(time, false); 146 } 147 } 148 149 @Override 150 protected void onResume() { 151 super.onResume(); 152 mEventLoader.startBackgroundThread(); 153 eventsChanged(); 154 CalendarView view = (CalendarView) mViewSwitcher.getNextView(); 155 view.updateIs24HourFormat(); 156 view.updateView(); 157 158 view = (CalendarView) mViewSwitcher.getCurrentView(); 159 view.updateIs24HourFormat(); 160 view.updateView(); 161 162 // Register for Intent broadcasts 163 IntentFilter filter = new IntentFilter(); 164 165 filter.addAction(Intent.ACTION_TIME_CHANGED); 166 filter.addAction(Intent.ACTION_DATE_CHANGED); 167 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 168 registerReceiver(mIntentReceiver, filter); 169 170 mContentResolver.registerContentObserver(Calendar.Events.CONTENT_URI, 171 true, mObserver); 172 } 173 174 @Override 175 protected void onSaveInstanceState(Bundle outState) { 176 super.onSaveInstanceState(outState); 177 178 outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTimeInMillis()); 179 } 180 181 @Override 182 protected void onPause() { 183 super.onPause(); 184 mContentResolver.unregisterContentObserver(mObserver); 185 unregisterReceiver(mIntentReceiver); 186 187 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 188 view.cleanup(); 189 view = (CalendarView) mViewSwitcher.getNextView(); 190 view.cleanup(); 191 mEventLoader.stopBackgroundThread(); 192 } 193 194 void startProgressSpinner() { 195 // start the progress spinner 196 mProgressBar.setVisibility(View.VISIBLE); 197 } 198 199 void stopProgressSpinner() { 200 // stop the progress spinner 201 mProgressBar.setVisibility(View.GONE); 202 } 203 204 /* Navigator interface methods */ 205 public void goTo(Time time, boolean animate) { 206 if (animate) { 207 CalendarView current = (CalendarView) mViewSwitcher.getCurrentView(); 208 if (current.getSelectedTime().before(time)) { 209 mViewSwitcher.setInAnimation(mInAnimationForward); 210 mViewSwitcher.setOutAnimation(mOutAnimationForward); 211 } else { 212 mViewSwitcher.setInAnimation(mInAnimationBackward); 213 mViewSwitcher.setOutAnimation(mOutAnimationBackward); 214 } 215 } 216 217 CalendarView next = (CalendarView) mViewSwitcher.getNextView(); 218 next.setSelectedDay(time); 219 next.reloadEvents(); 220 mViewSwitcher.showNext(); 221 next.requestFocus(); 222 } 223 224 /** 225 * Returns the selected time in milliseconds. The milliseconds are measured 226 * in UTC milliseconds from the epoch and uniquely specifies any selectable 227 * time. 228 * 229 * @return the selected time in milliseconds 230 */ 231 public long getSelectedTimeInMillis() { 232 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 233 return view.getSelectedTimeInMillis(); 234 } 235 236 public long getSelectedTime() { 237 return getSelectedTimeInMillis(); 238 } 239 240 public void goToToday() { 241 mSelectedDay.set(System.currentTimeMillis()); 242 mSelectedDay.normalize(true); 243 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 244 view.setSelectedDay(mSelectedDay); 245 view.reloadEvents(); 246 } 247 248 public boolean getAllDay() { 249 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 250 return view.mSelectionAllDay; 251 } 252 253 void eventsChanged() { 254 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 255 view.clearCachedEvents(); 256 view.reloadEvents(); 257 } 258 259 Event getSelectedEvent() { 260 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 261 return view.getSelectedEvent(); 262 } 263 264 boolean isEventSelected() { 265 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 266 return view.isEventSelected(); 267 } 268 269 Event getNewEvent() { 270 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 271 return view.getNewEvent(); 272 } 273 274 public CalendarView getNextView() { 275 return (CalendarView) mViewSwitcher.getNextView(); 276 } 277 278 public View switchViews(boolean forward, float xOffSet, float width) { 279 float progress = Math.abs(xOffSet) / width; 280 if (progress > 1.0f) { 281 progress = 1.0f; 282 } 283 284 float inFromXValue, inToXValue; 285 float outFromXValue, outToXValue; 286 if (forward) { 287 inFromXValue = 1.0f - progress; 288 inToXValue = 0.0f; 289 outFromXValue = -progress; 290 outToXValue = -1.0f; 291 } else { 292 inFromXValue = progress - 1.0f; 293 inToXValue = 0.0f; 294 outFromXValue = progress; 295 outToXValue = 1.0f; 296 } 297 298 // We have to allocate these animation objects each time we switch views 299 // because that is the only way to set the animation parameters. 300 TranslateAnimation inAnimation = new TranslateAnimation( 301 Animation.RELATIVE_TO_SELF, inFromXValue, 302 Animation.RELATIVE_TO_SELF, inToXValue, 303 Animation.ABSOLUTE, 0.0f, 304 Animation.ABSOLUTE, 0.0f); 305 306 TranslateAnimation outAnimation = new TranslateAnimation( 307 Animation.RELATIVE_TO_SELF, outFromXValue, 308 Animation.RELATIVE_TO_SELF, outToXValue, 309 Animation.ABSOLUTE, 0.0f, 310 Animation.ABSOLUTE, 0.0f); 311 312 // Reduce the animation duration based on how far we have already swiped. 313 long duration = (long) (ANIMATION_DURATION * (1.0f - progress)); 314 inAnimation.setDuration(duration); 315 outAnimation.setDuration(duration); 316 mViewSwitcher.setInAnimation(inAnimation); 317 mViewSwitcher.setOutAnimation(outAnimation); 318 319 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 320 view.cleanup(); 321 mViewSwitcher.showNext(); 322 view = (CalendarView) mViewSwitcher.getCurrentView(); 323 view.requestFocus(); 324 view.reloadEvents(); 325 return view; 326 } 327 328 @Override 329 public boolean onPrepareOptionsMenu(Menu menu) { 330 MenuHelper.onPrepareOptionsMenu(this, menu); 331 return super.onPrepareOptionsMenu(menu); 332 } 333 334 @Override 335 public boolean onCreateOptionsMenu(Menu menu) { 336 if (! MenuHelper.onCreateOptionsMenu(menu)) { 337 return false; 338 } 339 return super.onCreateOptionsMenu(menu); 340 } 341 342 @Override 343 public boolean onOptionsItemSelected(MenuItem item) { 344 if (MenuHelper.onOptionsItemSelected(this, item, this)) { 345 return true; 346 } 347 return super.onOptionsItemSelected(item); 348 } 349 350 @Override 351 public boolean onTouchEvent(MotionEvent ev) { 352 if (mGestureDetector.onTouchEvent(ev)) { 353 return true; 354 } 355 return super.onTouchEvent(ev); 356 } 357 358 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { 359 @Override 360 public boolean onSingleTapUp(MotionEvent ev) { 361 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 362 view.doSingleTapUp(ev); 363 return true; 364 } 365 366 @Override 367 public void onLongPress(MotionEvent ev) { 368 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 369 view.doLongPress(ev); 370 } 371 372 @Override 373 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 374 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 375 view.doScroll(e1, e2, distanceX, distanceY); 376 return true; 377 } 378 379 @Override 380 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 381 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 382 view.doFling(e1, e2, velocityX, velocityY); 383 return true; 384 } 385 386 @Override 387 public boolean onDown(MotionEvent ev) { 388 CalendarView view = (CalendarView) mViewSwitcher.getCurrentView(); 389 view.doDown(ev); 390 return true; 391 } 392 } 393 } 394 395