2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.calendar;
19 import android.app.Activity;
20 import android.content.AsyncQueryHandler;
21 import android.content.BroadcastReceiver;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.SharedPreferences;
28 import android.database.ContentObserver;
29 import android.database.Cursor;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.preference.PreferenceManager;
34 import android.provider.Calendar;
35 import android.provider.Calendar.Attendees;
36 import android.provider.Calendar.Calendars;
37 import android.provider.Calendar.Events;
38 import android.provider.Calendar.Instances;
39 import android.text.format.Time;
40 import android.view.KeyEvent;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.Window;
45 import android.widget.AdapterView;
46 import android.widget.ListView;
47 import android.widget.ViewSwitcher;
48 import dalvik.system.VMRuntime;
50 public class AgendaActivity extends Activity implements ViewSwitcher.ViewFactory, Navigator {
52 protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
54 static final String[] PROJECTION = new String[] {
57 Instances.EVENT_LOCATION, // 2
58 Instances.ALL_DAY, // 3
59 Instances.HAS_ALARM, // 4
64 Instances.EVENT_ID, // 9
65 Instances.START_DAY, // 10 Julian start day
66 Instances.END_DAY, // 11 Julian end day
67 Instances.SELF_ATTENDEE_STATUS, // 12
70 public static final int INDEX_TITLE = 1;
71 public static final int INDEX_EVENT_LOCATION = 2;
72 public static final int INDEX_ALL_DAY = 3;
73 public static final int INDEX_HAS_ALARM = 4;
74 public static final int INDEX_COLOR = 5;
75 public static final int INDEX_RRULE = 6;
76 public static final int INDEX_BEGIN = 7;
77 public static final int INDEX_END = 8;
78 public static final int INDEX_EVENT_ID = 9;
79 public static final int INDEX_START_DAY = 10;
80 public static final int INDEX_END_DAY = 11;
81 public static final int INDEX_SELF_ATTENDEE_STATUS = 12;
83 public static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC";
85 private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
87 private ContentResolver mContentResolver;
89 private ViewSwitcher mViewSwitcher;
91 private QueryHandler mQueryHandler;
92 private DeleteEventHelper mDeleteEventHelper;
96 * This records the start time parameter for the last query sent to the
97 * AsyncQueryHandler so that we don't send it duplicate query requests.
99 private Time mLastQueryTime = new Time();
101 private class QueryHandler extends AsyncQueryHandler {
102 public QueryHandler(ContentResolver cr) {
107 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
109 // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
110 if (!isFinishing()) {
111 AgendaListView next = (AgendaListView) mViewSwitcher.getNextView();
112 next.setCursor(cursor);
113 mViewSwitcher.showNext();
121 private class AgendaListView extends ListView {
122 private Cursor mCursor;
123 private AgendaByDayAdapter mDayAdapter;
124 private AgendaAdapter mAdapter;
126 public AgendaListView(Context context) {
127 super(context, null);
128 setOnItemClickListener(mOnItemClickListener);
129 setChoiceMode(ListView.CHOICE_MODE_SINGLE);
130 mAdapter = new AgendaAdapter(AgendaActivity.this, R.layout.agenda_item);
131 mDayAdapter = new AgendaByDayAdapter(AgendaActivity.this, mAdapter);
134 public void setCursor(Cursor cursor) {
135 if (mCursor != null) {
139 mDayAdapter.calculateDays(cursor);
140 mAdapter.changeCursor(cursor);
141 setAdapter(mDayAdapter);
144 public Cursor getCursor() {
148 public AgendaByDayAdapter getDayAdapter() {
152 @Override protected void onDetachedFromWindow() {
153 super.onDetachedFromWindow();
154 if (mCursor != null) {
159 private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
160 public void onItemClick(AdapterView a, View v, int position, long id) {
162 // Switch to the EventInfo view
163 mCursor.moveToPosition(mDayAdapter.getCursorPosition(position));
164 long eventId = mCursor.getLong(INDEX_EVENT_ID);
165 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
166 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
167 intent.putExtra(Calendar.EVENT_BEGIN_TIME, mCursor.getLong(INDEX_BEGIN));
168 intent.putExtra(Calendar.EVENT_END_TIME, mCursor.getLong(INDEX_END));
169 startActivity(intent);
175 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
177 public void onReceive(Context context, Intent intent) {
178 String action = intent.getAction();
179 if (action.equals(Intent.ACTION_TIME_CHANGED)
180 || action.equals(Intent.ACTION_DATE_CHANGED)
181 || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
182 clearLastQueryTime();
188 private ContentObserver mObserver = new ContentObserver(new Handler()) {
190 public boolean deliverSelfNotifications() {
195 public void onChange(boolean selfChange) {
196 clearLastQueryTime();
202 protected void onCreate(Bundle icicle) {
203 super.onCreate(icicle);
205 // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
206 // TODO: We should restore the old heap size once the activity reaches the idle state
207 long oldHeapSize = VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
209 setContentView(R.layout.agenda_activity);
211 mContentResolver = getContentResolver();
212 mQueryHandler = new QueryHandler(mContentResolver);
214 // Preserve the same month and event selection if this activity is
215 // being restored due to an orientation change
217 if (icicle != null) {
218 mTime.set(icicle.getLong(BUNDLE_KEY_RESTORE_TIME));
220 mTime.set(Utils.timeFromIntent(getIntent()));
222 setTitle(R.string.agenda_view);
224 mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
225 mViewSwitcher.setFactory(this);
227 // Record Agenda View as the (new) default detailed view.
228 String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.AGENDA_VIEW_ID];
229 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
230 SharedPreferences.Editor editor = prefs.edit();
231 editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString);
233 // Record Agenda View as the (new) start view
234 editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
237 mDeleteEventHelper = new DeleteEventHelper(this, false /* don't exit when done */);
241 protected void onResume() {
244 clearLastQueryTime();
247 // Register for Intent broadcasts
248 IntentFilter filter = new IntentFilter();
249 filter.addAction(Intent.ACTION_TIME_CHANGED);
250 filter.addAction(Intent.ACTION_DATE_CHANGED);
251 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
252 registerReceiver(mIntentReceiver, filter);
254 mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
258 protected void onSaveInstanceState(Bundle outState) {
259 super.onSaveInstanceState(outState);
261 outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTime());
265 protected void onPause() {
268 mContentResolver.unregisterContentObserver(mObserver);
269 unregisterReceiver(mIntentReceiver);
273 public boolean onPrepareOptionsMenu(Menu menu) {
274 MenuHelper.onPrepareOptionsMenu(this, menu);
275 return super.onPrepareOptionsMenu(menu);
279 public boolean onCreateOptionsMenu(Menu menu) {
280 MenuHelper.onCreateOptionsMenu(menu);
281 return super.onCreateOptionsMenu(menu);
285 public boolean onOptionsItemSelected(MenuItem item) {
286 MenuHelper.onOptionsItemSelected(this, item, this);
287 return super.onOptionsItemSelected(item);
291 public boolean onKeyDown(int keyCode, KeyEvent event) {
293 case KeyEvent.KEYCODE_DEL: {
294 // Delete the currently selected event (if any)
295 AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
296 Cursor cursor = current.getCursor();
297 if (cursor != null) {
298 int position = current.getSelectedItemPosition();
299 position = current.getDayAdapter().getCursorPosition(position);
301 cursor.moveToPosition(position);
302 long begin = cursor.getLong(INDEX_BEGIN);
303 long end = cursor.getLong(INDEX_END);
304 long eventId = cursor.getLong(INDEX_EVENT_ID);
305 mDeleteEventHelper.delete(begin, end, eventId, -1);
311 case KeyEvent.KEYCODE_BACK:
315 return super.onKeyDown(keyCode, event);
319 * Clears the cached value for the last query time so that renewCursor()
320 * will force a requery of the Calendar events.
322 private void clearLastQueryTime() {
323 mLastQueryTime.year = 0;
324 mLastQueryTime.month = 0;
327 private void renewCursor() {
328 // Avoid batching up repeated queries for the same month. This can
329 // happen if the user scrolls with the trackball too fast.
330 if (mLastQueryTime.month == mTime.month && mLastQueryTime.year == mTime.year) {
334 // Query all instances for the current month
335 Time time = new Time();
336 time.year = mTime.year;
337 time.month = mTime.month;
338 long start = time.normalize(true);
341 long end = time.normalize(true);
343 StringBuilder path = new StringBuilder();
348 // Respect the preference to show/hide declined events
349 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
350 boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED,
353 Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, path.toString());
357 selection = Calendars.SELECTED + "=1 AND " +
358 Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
360 selection = Calendars.SELECTED + "=1";
363 // Cancel any previous queries that haven't started yet. This
364 // isn't likely to happen since we already avoid sending
365 // a duplicate query for the same month as the previous query.
366 // But if the user quickly wiggles the trackball back and forth,
367 // he could generate a stream of queries.
368 mQueryHandler.cancelOperation(0);
370 mLastQueryTime.month = mTime.month;
371 mLastQueryTime.year = mTime.year;
372 mQueryHandler.startQuery(0, null, uri, PROJECTION, selection, null,
376 private void selectTime() {
377 // Selects the first event of the day
378 AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
379 if (current.getCursor() == null) {
383 int position = current.getDayAdapter().findDayPositionNearestTime(mTime);
384 current.setSelection(position);
387 /* ViewSwitcher.ViewFactory interface methods */
388 public View makeView() {
389 AgendaListView agendaListView = new AgendaListView(this);
390 return agendaListView;
393 /* Navigator interface methods */
394 public void goToToday() {
395 Time now = new Time();
396 now.set(System.currentTimeMillis());
400 public void goTo(Time time) {
401 if (mTime.year == time.year && mTime.month == time.month) {
409 public long getSelectedTime() {
410 // Update the current time based on the selected event
411 AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
412 int position = current.getSelectedItemPosition();
413 position = current.getDayAdapter().getCursorPosition(position);
414 Cursor cursor = current.getCursor();
415 if (position >= 0 && position < cursor.getCount()) {
416 cursor.moveToPosition(position);
417 mTime.set(cursor.getLong(INDEX_BEGIN));
420 return mTime.toMillis(true);
423 public boolean getAllDay() {