1 /* 2 * Copyright (C) 2006 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 static android.provider.Calendar.EVENT_BEGIN_TIME; 20 import dalvik.system.VMRuntime; 21 22 import android.app.Activity; 23 import android.content.BroadcastReceiver; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.SharedPreferences; 29 import android.database.ContentObserver; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.provider.Calendar.Events; 33 import android.text.TextUtils; 34 import android.text.format.DateFormat; 35 import android.text.format.DateUtils; 36 import android.text.format.Time; 37 import android.view.Menu; 38 import android.view.MenuItem; 39 import android.view.View; 40 import android.view.animation.Animation; 41 import android.view.animation.Animation.AnimationListener; 42 import android.view.animation.AnimationUtils; 43 import android.widget.Gallery.LayoutParams; 44 import android.widget.ProgressBar; 45 import android.widget.TextView; 46 import android.widget.ViewSwitcher; 47 48 import java.util.Calendar; 49 import java.util.Locale; 50 import java.util.TimeZone; 51 52 public class MonthActivity extends Activity implements ViewSwitcher.ViewFactory, 53 Navigator, AnimationListener { 54 private static final int INITIAL_HEAP_SIZE = 4 * 1024 * 1024; 55 private Animation mInAnimationPast; 56 private Animation mInAnimationFuture; 57 private Animation mOutAnimationPast; 58 private Animation mOutAnimationFuture; 59 private ViewSwitcher mSwitcher; 60 private Time mTime; 61 62 private ContentResolver mContentResolver; 63 EventLoader mEventLoader; 64 private int mStartDay; 65 66 private ProgressBar mProgressBar; 67 68 // This gets run if the time zone is updated in the db 69 private Runnable mUpdateTZ = new Runnable() { 70 @Override 71 public void run() { 72 // We want mTime to stay on the same day, so we swap the tz 73 mTime.timezone = Utils.getTimeZone(MonthActivity.this, this); 74 mTime.normalize(true); 75 updateTitle(mTime); 76 } 77 }; 78 79 private static final int DAY_OF_WEEK_LABEL_IDS[] = { 80 R.id.day0, R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6 81 }; 82 private static final int DAY_OF_WEEK_KINDS[] = { 83 Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, 84 Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY 85 }; 86 87 protected void startProgressSpinner() { 88 // start the progress spinner 89 mProgressBar.setVisibility(View.VISIBLE); 90 } 91 92 protected void stopProgressSpinner() { 93 // stop the progress spinner 94 mProgressBar.setVisibility(View.GONE); 95 } 96 97 /* ViewSwitcher.ViewFactory interface methods */ 98 public View makeView() { 99 MonthView mv = new MonthView(this, this); 100 mv.setLayoutParams(new ViewSwitcher.LayoutParams( 101 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 102 mv.setSelectedTime(mTime); 103 return mv; 104 } 105 106 public void updateTitle(Time time) { 107 TextView title = (TextView) findViewById(R.id.title); 108 StringBuffer date = new StringBuffer(Utils.formatMonthYear(this, time)); 109 if (!TextUtils.equals(Utils.getTimeZone(this, mUpdateTZ), Time.getCurrentTimezone())) { 110 int flags = DateUtils.FORMAT_SHOW_TIME; 111 if (DateFormat.is24HourFormat(this)) { 112 flags |= DateUtils.FORMAT_24HOUR; 113 } 114 long start = System.currentTimeMillis(); 115 String tz = Utils.getTimeZone(this, mUpdateTZ); 116 boolean isDST = time.isDst != 0; 117 TimeZone timeZone = TimeZone.getTimeZone(tz); 118 date.append(" (").append(Utils.formatDateRange(this, start, start, flags)).append(" ") 119 .append(timeZone.getDisplayName(isDST, TimeZone.SHORT, Locale.getDefault())) 120 .append(")"); 121 } 122 title.setText(date.toString()); 123 } 124 125 /* Navigator interface methods */ 126 public void goTo(Time time, boolean animate) { 127 updateTitle(time); 128 129 MonthView current = (MonthView) mSwitcher.getCurrentView(); 130 current.dismissPopup(); 131 132 Time currentTime = current.getTime(); 133 134 // Compute a month number that is monotonically increasing for any 135 // two adjacent months. 136 // This is faster than calling getSelectedTime() because we avoid 137 // a call to Time#normalize(). 138 if (animate) { 139 int currentMonth = currentTime.month + currentTime.year * 12; 140 int nextMonth = time.month + time.year * 12; 141 if (nextMonth < currentMonth) { 142 mSwitcher.setInAnimation(mInAnimationPast); 143 mSwitcher.setOutAnimation(mOutAnimationPast); 144 } else { 145 mSwitcher.setInAnimation(mInAnimationFuture); 146 mSwitcher.setOutAnimation(mOutAnimationFuture); 147 } 148 } 149 150 MonthView next = (MonthView) mSwitcher.getNextView(); 151 next.setSelectionMode(current.getSelectionMode()); 152 next.setSelectedTime(time); 153 next.reloadEvents(); 154 next.animationStarted(); 155 mSwitcher.showNext(); 156 next.requestFocus(); 157 mTime = time; 158 } 159 160 public void goToToday() { 161 Time now = new Time(Utils.getTimeZone(this, mUpdateTZ)); 162 now.set(System.currentTimeMillis()); 163 now.minute = 0; 164 now.second = 0; 165 now.normalize(false); 166 167 TextView title = (TextView) findViewById(R.id.title); 168 title.setText(Utils.formatMonthYear(this, now)); 169 mTime = now; 170 171 MonthView view = (MonthView) mSwitcher.getCurrentView(); 172 view.setSelectedTime(now); 173 view.reloadEvents(); 174 } 175 176 public long getSelectedTime() { 177 MonthView mv = (MonthView) mSwitcher.getCurrentView(); 178 return mv.getSelectedTimeInMillis(); 179 } 180 181 public boolean getAllDay() { 182 return false; 183 } 184 185 int getStartDay() { 186 return mStartDay; 187 } 188 189 void eventsChanged() { 190 MonthView view = (MonthView) mSwitcher.getCurrentView(); 191 view.reloadEvents(); 192 } 193 194 /** 195 * Listens for intent broadcasts 196 */ 197 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 198 @Override 199 public void onReceive(Context context, Intent intent) { 200 String action = intent.getAction(); 201 if (action.equals(Intent.ACTION_TIME_CHANGED) 202 || action.equals(Intent.ACTION_DATE_CHANGED) 203 || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { 204 eventsChanged(); 205 } 206 } 207 }; 208 209 // Create an observer so that we can update the views whenever a 210 // Calendar event changes. 211 private ContentObserver mObserver = new ContentObserver(new Handler()) 212 { 213 @Override 214 public boolean deliverSelfNotifications() { 215 return true; 216 } 217 218 @Override 219 public void onChange(boolean selfChange) { 220 eventsChanged(); 221 } 222 }; 223 224 public void onAnimationStart(Animation animation) { 225 } 226 227 // Notifies the MonthView when an animation has finished. 228 public void onAnimationEnd(Animation animation) { 229 MonthView monthView = (MonthView) mSwitcher.getCurrentView(); 230 monthView.animationFinished(); 231 } 232 233 public void onAnimationRepeat(Animation animation) { 234 } 235 236 @Override 237 protected void onCreate(Bundle icicle) { 238 super.onCreate(icicle); 239 240 // Eliminate extra GCs during startup by setting the initial heap size to 4MB. 241 // TODO: We should restore the old heap size once the activity reaches the idle state 242 VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); 243 244 setContentView(R.layout.month_activity); 245 mContentResolver = getContentResolver(); 246 247 long time; 248 if (icicle != null) { 249 time = icicle.getLong(EVENT_BEGIN_TIME); 250 } else { 251 time = Utils.timeFromIntentInMillis(getIntent()); 252 } 253 254 mTime = new Time(Utils.getTimeZone(this, mUpdateTZ)); 255 mTime.set(time); 256 mTime.normalize(true); 257 258 // Get first day of week based on locale and populate the day headers 259 mStartDay = Calendar.getInstance().getFirstDayOfWeek(); 260 int diff = mStartDay - Calendar.SUNDAY - 1; 261 final int startDay = Utils.getFirstDayOfWeek(); 262 final int sundayColor = getResources().getColor(R.color.sunday_text_color); 263 final int saturdayColor = getResources().getColor(R.color.saturday_text_color); 264 265 for (int day = 0; day < 7; day++) { 266 final String dayString = DateUtils.getDayOfWeekString( 267 (DAY_OF_WEEK_KINDS[day] + diff) % 7 + 1, DateUtils.LENGTH_MEDIUM); 268 final TextView label = (TextView) findViewById(DAY_OF_WEEK_LABEL_IDS[day]); 269 label.setText(dayString); 270 if (Utils.isSunday(day, startDay)) { 271 label.setTextColor(sundayColor); 272 } else if (Utils.isSaturday(day, startDay)) { 273 label.setTextColor(saturdayColor); 274 } 275 } 276 277 // Set the initial title 278 TextView title = (TextView) findViewById(R.id.title); 279 title.setText(Utils.formatMonthYear(this, mTime)); 280 281 mEventLoader = new EventLoader(this); 282 mProgressBar = (ProgressBar) findViewById(R.id.progress_circular); 283 284 mSwitcher = (ViewSwitcher) findViewById(R.id.switcher); 285 mSwitcher.setFactory(this); 286 mSwitcher.getCurrentView().requestFocus(); 287 288 mInAnimationPast = AnimationUtils.loadAnimation(this, R.anim.slide_down_in); 289 mOutAnimationPast = AnimationUtils.loadAnimation(this, R.anim.slide_down_out); 290 mInAnimationFuture = AnimationUtils.loadAnimation(this, R.anim.slide_up_in); 291 mOutAnimationFuture = AnimationUtils.loadAnimation(this, R.anim.slide_up_out); 292 293 mInAnimationPast.setAnimationListener(this); 294 mInAnimationFuture.setAnimationListener(this); 295 } 296 297 @Override 298 protected void onNewIntent(Intent intent) { 299 long timeMillis = Utils.timeFromIntentInMillis(intent); 300 if (timeMillis > 0) { 301 Time time = new Time(Utils.getTimeZone(this, mUpdateTZ)); 302 time.set(timeMillis); 303 goTo(time, false); 304 } 305 } 306 307 @Override 308 protected void onPause() { 309 super.onPause(); 310 if (isFinishing()) { 311 mEventLoader.stopBackgroundThread(); 312 } 313 mContentResolver.unregisterContentObserver(mObserver); 314 unregisterReceiver(mIntentReceiver); 315 316 MonthView view = (MonthView) mSwitcher.getCurrentView(); 317 view.dismissPopup(); 318 view = (MonthView) mSwitcher.getNextView(); 319 view.dismissPopup(); 320 mEventLoader.stopBackgroundThread(); 321 } 322 323 @Override 324 protected void onResume() { 325 super.onResume(); 326 mUpdateTZ.run(); 327 mEventLoader.startBackgroundThread(); 328 eventsChanged(); 329 330 MonthView view1 = (MonthView) mSwitcher.getCurrentView(); 331 MonthView view2 = (MonthView) mSwitcher.getNextView(); 332 SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this); 333 String str = prefs.getString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, 334 CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW); 335 view1.updateView(); 336 view2.updateView(); 337 view1.setDetailedView(str); 338 view2.setDetailedView(str); 339 340 // Register for Intent broadcasts 341 IntentFilter filter = new IntentFilter(); 342 343 filter.addAction(Intent.ACTION_TIME_CHANGED); 344 filter.addAction(Intent.ACTION_DATE_CHANGED); 345 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 346 registerReceiver(mIntentReceiver, filter); 347 348 mContentResolver.registerContentObserver(Events.CONTENT_URI, 349 true, mObserver); 350 351 // Record Month View as the (new) start view 352 Utils.setDefaultView(this, CalendarApplication.MONTH_VIEW_ID); 353 } 354 355 @Override 356 protected void onSaveInstanceState(Bundle outState) { 357 super.onSaveInstanceState(outState); 358 outState.putLong(EVENT_BEGIN_TIME, mTime.toMillis(true)); 359 } 360 361 @Override 362 public boolean onPrepareOptionsMenu(Menu menu) { 363 MenuHelper.onPrepareOptionsMenu(this, menu); 364 return super.onPrepareOptionsMenu(menu); 365 } 366 367 @Override 368 public boolean onCreateOptionsMenu(Menu menu) { 369 MenuHelper.onCreateOptionsMenu(menu); 370 return super.onCreateOptionsMenu(menu); 371 } 372 373 @Override 374 public boolean onOptionsItemSelected(MenuItem item) { 375 MenuHelper.onOptionsItemSelected(this, item, this); 376 return super.onOptionsItemSelected(item); 377 } 378 } 379