auto import from //depot/cupcake/@136594
[android/platform/packages/apps/Calendar.git] / src / com / android / calendar / AgendaActivity.java
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 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;
49
50 public class AgendaActivity extends Activity implements ViewSwitcher.ViewFactory, Navigator {
51
52     protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
53
54     static final String[] PROJECTION = new String[] {
55         Instances._ID,                  // 0
56         Instances.TITLE,                // 1
57         Instances.EVENT_LOCATION,       // 2
58         Instances.ALL_DAY,              // 3
59         Instances.HAS_ALARM,            // 4
60         Instances.COLOR,                // 5
61         Instances.RRULE,                // 6
62         Instances.BEGIN,                // 7
63         Instances.END,                  // 8
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
68     };
69
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;
82
83     public static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC";
84
85     private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
86
87     private ContentResolver mContentResolver;
88
89     private ViewSwitcher mViewSwitcher;
90
91     private QueryHandler mQueryHandler;
92     private DeleteEventHelper mDeleteEventHelper;
93     private Time mTime;
94
95     /**
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.
98      */
99     private Time mLastQueryTime = new Time();
100
101     private class QueryHandler extends AsyncQueryHandler {
102         public QueryHandler(ContentResolver cr) {
103             super(cr);
104         }
105
106         @Override
107         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
108
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();
114                 selectTime();
115             } else {
116                 cursor.close();
117             }
118         }
119     }
120
121     private class AgendaListView extends ListView {
122         private Cursor mCursor;
123         private AgendaByDayAdapter mDayAdapter;
124         private AgendaAdapter mAdapter;
125
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);
132         }
133
134         public void setCursor(Cursor cursor) {
135             if (mCursor != null) {
136                 mCursor.close();
137             }
138             mCursor = cursor;
139             mDayAdapter.calculateDays(cursor);
140             mAdapter.changeCursor(cursor);
141             setAdapter(mDayAdapter);
142         }
143
144         public Cursor getCursor() {
145             return mCursor;
146         }
147
148         public AgendaByDayAdapter getDayAdapter() {
149             return mDayAdapter;
150         }
151
152         @Override protected void onDetachedFromWindow() {
153             super.onDetachedFromWindow();
154             if (mCursor != null) {
155                 mCursor.close();
156             }
157         }
158
159         private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
160             public void onItemClick(AdapterView a, View v, int position, long id) {
161                 if (id != -1) {
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);
170                 }
171             }
172         };
173     }
174
175     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
176         @Override
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();
183                 renewCursor();
184             }
185         }
186     };
187
188     private ContentObserver mObserver = new ContentObserver(new Handler()) {
189         @Override
190         public boolean deliverSelfNotifications() {
191             return true;
192         }
193
194         @Override
195         public void onChange(boolean selfChange) {
196             clearLastQueryTime();
197             renewCursor();
198         }
199     };
200
201     @Override
202     protected void onCreate(Bundle icicle) {
203         super.onCreate(icicle);
204
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);
208
209         setContentView(R.layout.agenda_activity);
210
211         mContentResolver = getContentResolver();
212         mQueryHandler = new QueryHandler(mContentResolver);
213
214         // Preserve the same month and event selection if this activity is
215         // being restored due to an orientation change
216         mTime = new Time();
217         if (icicle != null) {
218             mTime.set(icicle.getLong(BUNDLE_KEY_RESTORE_TIME));
219         } else {
220             mTime.set(Utils.timeFromIntent(getIntent()));
221         }
222         setTitle(R.string.agenda_view);
223
224         mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
225         mViewSwitcher.setFactory(this);
226
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);
232
233         // Record Agenda View as the (new) start view
234         editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
235         editor.commit();
236
237         mDeleteEventHelper = new DeleteEventHelper(this, false /* don't exit when done */);
238     }
239
240     @Override
241     protected void onResume() {
242         super.onResume();
243
244         clearLastQueryTime();
245         renewCursor();
246
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);
253
254         mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
255     }
256
257     @Override
258     protected void onSaveInstanceState(Bundle outState) {
259         super.onSaveInstanceState(outState);
260
261         outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTime());
262     }
263
264     @Override
265     protected void onPause() {
266         super.onPause();
267
268         mContentResolver.unregisterContentObserver(mObserver);
269         unregisterReceiver(mIntentReceiver);
270     }
271
272     @Override
273     public boolean onPrepareOptionsMenu(Menu menu) {
274         MenuHelper.onPrepareOptionsMenu(this, menu);
275         return super.onPrepareOptionsMenu(menu);
276     }
277
278     @Override
279     public boolean onCreateOptionsMenu(Menu menu) {
280         MenuHelper.onCreateOptionsMenu(menu);
281         return super.onCreateOptionsMenu(menu);
282     }
283
284     @Override
285     public boolean onOptionsItemSelected(MenuItem item) {
286         MenuHelper.onOptionsItemSelected(this, item, this);
287         return super.onOptionsItemSelected(item);
288     }
289
290     @Override
291     public boolean onKeyDown(int keyCode, KeyEvent event) {
292         switch (keyCode) {
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);
300                     if (position >= 0) {
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);
306                     }
307                 }
308             }
309                 break;
310
311             case KeyEvent.KEYCODE_BACK:
312                 finish();
313                 return true;
314         }
315         return super.onKeyDown(keyCode, event);
316     }
317
318     /**
319      * Clears the cached value for the last query time so that renewCursor()
320      * will force a requery of the Calendar events.
321      */
322     private void clearLastQueryTime() {
323         mLastQueryTime.year = 0;
324         mLastQueryTime.month = 0;
325     }
326
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) {
331             return;
332         }
333
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);
339
340         time.month++;
341         long end = time.normalize(true);
342
343         StringBuilder path = new StringBuilder();
344         path.append(start);
345         path.append('/');
346         path.append(end);
347
348         // Respect the preference to show/hide declined events
349         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
350         boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED,
351                 false);
352
353         Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, path.toString());
354
355         String selection;
356         if (hideDeclined) {
357             selection = Calendars.SELECTED + "=1 AND " +
358                     Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
359         } else {
360             selection = Calendars.SELECTED + "=1";
361         }
362
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);
369
370         mLastQueryTime.month = mTime.month;
371         mLastQueryTime.year = mTime.year;
372         mQueryHandler.startQuery(0, null, uri, PROJECTION, selection, null,
373                 AGENDA_SORT_ORDER);
374     }
375
376     private void selectTime() {
377         // Selects the first event of the day
378         AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
379         if (current.getCursor() == null) {
380             return;
381         }
382
383         int position = current.getDayAdapter().findDayPositionNearestTime(mTime);
384         current.setSelection(position);
385     }
386
387     /* ViewSwitcher.ViewFactory interface methods */
388     public View makeView() {
389         AgendaListView agendaListView = new AgendaListView(this);
390         return agendaListView;
391     }
392
393     /* Navigator interface methods */
394     public void goToToday() {
395         Time now = new Time();
396         now.set(System.currentTimeMillis());
397         goTo(now);
398     }
399
400     public void goTo(Time time) {
401         if (mTime.year == time.year && mTime.month == time.month) {
402             mTime = time;
403             selectTime();
404         } else {
405             mTime = time;
406         }
407     }
408
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));
418         }
419
420         return mTime.toMillis(true);
421     }
422
423     public boolean getAllDay() {
424         return false;
425     }
426 }
427