| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calendar; |
| |
| import android.app.Activity; |
| import android.content.AsyncQueryHandler; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.preference.PreferenceManager; |
| import android.provider.Calendar; |
| import android.provider.Calendar.Attendees; |
| import android.provider.Calendar.Calendars; |
| import android.provider.Calendar.Events; |
| import android.provider.Calendar.Instances; |
| import android.text.format.Time; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.Window; |
| import android.widget.AdapterView; |
| import android.widget.ListView; |
| import android.widget.ViewSwitcher; |
| import dalvik.system.VMRuntime; |
| |
| public class AgendaActivity extends Activity implements ViewSwitcher.ViewFactory, Navigator { |
| |
| protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; |
| |
| static final String[] PROJECTION = new String[] { |
| Instances._ID, // 0 |
| Instances.TITLE, // 1 |
| Instances.EVENT_LOCATION, // 2 |
| Instances.ALL_DAY, // 3 |
| Instances.HAS_ALARM, // 4 |
| Instances.COLOR, // 5 |
| Instances.RRULE, // 6 |
| Instances.BEGIN, // 7 |
| Instances.END, // 8 |
| Instances.EVENT_ID, // 9 |
| Instances.START_DAY, // 10 Julian start day |
| Instances.END_DAY, // 11 Julian end day |
| Instances.SELF_ATTENDEE_STATUS, // 12 |
| }; |
| |
| public static final int INDEX_TITLE = 1; |
| public static final int INDEX_EVENT_LOCATION = 2; |
| public static final int INDEX_ALL_DAY = 3; |
| public static final int INDEX_HAS_ALARM = 4; |
| public static final int INDEX_COLOR = 5; |
| public static final int INDEX_RRULE = 6; |
| public static final int INDEX_BEGIN = 7; |
| public static final int INDEX_END = 8; |
| public static final int INDEX_EVENT_ID = 9; |
| public static final int INDEX_START_DAY = 10; |
| public static final int INDEX_END_DAY = 11; |
| public static final int INDEX_SELF_ATTENDEE_STATUS = 12; |
| |
| public static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC"; |
| |
| private static final long INITIAL_HEAP_SIZE = 4*1024*1024; |
| |
| private ContentResolver mContentResolver; |
| |
| private ViewSwitcher mViewSwitcher; |
| |
| private QueryHandler mQueryHandler; |
| private DeleteEventHelper mDeleteEventHelper; |
| private Time mTime; |
| |
| /** |
| * This records the start time parameter for the last query sent to the |
| * AsyncQueryHandler so that we don't send it duplicate query requests. |
| */ |
| private Time mLastQueryTime = new Time(); |
| |
| private class QueryHandler extends AsyncQueryHandler { |
| public QueryHandler(ContentResolver cr) { |
| super(cr); |
| } |
| |
| @Override |
| protected void onQueryComplete(int token, Object cookie, Cursor cursor) { |
| |
| // Only set mCursor if the Activity is not finishing. Otherwise close the cursor. |
| if (!isFinishing()) { |
| AgendaListView next = (AgendaListView) mViewSwitcher.getNextView(); |
| next.setCursor(cursor); |
| mViewSwitcher.showNext(); |
| selectTime(); |
| } else { |
| cursor.close(); |
| } |
| } |
| } |
| |
| private class AgendaListView extends ListView { |
| private Cursor mCursor; |
| private AgendaByDayAdapter mDayAdapter; |
| private AgendaAdapter mAdapter; |
| |
| public AgendaListView(Context context) { |
| super(context, null); |
| setOnItemClickListener(mOnItemClickListener); |
| setChoiceMode(ListView.CHOICE_MODE_SINGLE); |
| mAdapter = new AgendaAdapter(AgendaActivity.this, R.layout.agenda_item); |
| mDayAdapter = new AgendaByDayAdapter(AgendaActivity.this, mAdapter); |
| } |
| |
| public void setCursor(Cursor cursor) { |
| if (mCursor != null) { |
| mCursor.close(); |
| } |
| mCursor = cursor; |
| mDayAdapter.calculateDays(cursor); |
| mAdapter.changeCursor(cursor); |
| setAdapter(mDayAdapter); |
| } |
| |
| public Cursor getCursor() { |
| return mCursor; |
| } |
| |
| public AgendaByDayAdapter getDayAdapter() { |
| return mDayAdapter; |
| } |
| |
| @Override protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| if (mCursor != null) { |
| mCursor.close(); |
| } |
| } |
| |
| private OnItemClickListener mOnItemClickListener = new OnItemClickListener() { |
| public void onItemClick(AdapterView a, View v, int position, long id) { |
| if (id != -1) { |
| // Switch to the EventInfo view |
| mCursor.moveToPosition(mDayAdapter.getCursorPosition(position)); |
| long eventId = mCursor.getLong(INDEX_EVENT_ID); |
| Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); |
| Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
| intent.putExtra(Calendar.EVENT_BEGIN_TIME, mCursor.getLong(INDEX_BEGIN)); |
| intent.putExtra(Calendar.EVENT_END_TIME, mCursor.getLong(INDEX_END)); |
| startActivity(intent); |
| } |
| } |
| }; |
| } |
| |
| private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_TIME_CHANGED) |
| || action.equals(Intent.ACTION_DATE_CHANGED) |
| || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { |
| clearLastQueryTime(); |
| renewCursor(); |
| } |
| } |
| }; |
| |
| private ContentObserver mObserver = new ContentObserver(new Handler()) { |
| @Override |
| public boolean deliverSelfNotifications() { |
| return true; |
| } |
| |
| @Override |
| public void onChange(boolean selfChange) { |
| clearLastQueryTime(); |
| renewCursor(); |
| } |
| }; |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| // Eliminate extra GCs during startup by setting the initial heap size to 4MB. |
| // TODO: We should restore the old heap size once the activity reaches the idle state |
| long oldHeapSize = VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); |
| |
| setContentView(R.layout.agenda_activity); |
| |
| mContentResolver = getContentResolver(); |
| mQueryHandler = new QueryHandler(mContentResolver); |
| |
| // Preserve the same month and event selection if this activity is |
| // being restored due to an orientation change |
| mTime = new Time(); |
| if (icicle != null) { |
| mTime.set(icicle.getLong(BUNDLE_KEY_RESTORE_TIME)); |
| } else { |
| mTime.set(Utils.timeFromIntent(getIntent())); |
| } |
| setTitle(R.string.agenda_view); |
| |
| mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher); |
| mViewSwitcher.setFactory(this); |
| |
| // Record Agenda View as the (new) default detailed view. |
| String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.AGENDA_VIEW_ID]; |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString); |
| |
| // Record Agenda View as the (new) start view |
| editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString); |
| editor.commit(); |
| |
| mDeleteEventHelper = new DeleteEventHelper(this, false /* don't exit when done */); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| |
| clearLastQueryTime(); |
| renewCursor(); |
| |
| // Register for Intent broadcasts |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_TIME_CHANGED); |
| filter.addAction(Intent.ACTION_DATE_CHANGED); |
| filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); |
| registerReceiver(mIntentReceiver, filter); |
| |
| mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| |
| outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTime()); |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| |
| mContentResolver.unregisterContentObserver(mObserver); |
| unregisterReceiver(mIntentReceiver); |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| MenuHelper.onPrepareOptionsMenu(this, menu); |
| return super.onPrepareOptionsMenu(menu); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| MenuHelper.onCreateOptionsMenu(menu); |
| return super.onCreateOptionsMenu(menu); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| MenuHelper.onOptionsItemSelected(this, item, this); |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DEL: { |
| // Delete the currently selected event (if any) |
| AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView(); |
| Cursor cursor = current.getCursor(); |
| if (cursor != null) { |
| int position = current.getSelectedItemPosition(); |
| position = current.getDayAdapter().getCursorPosition(position); |
| if (position >= 0) { |
| cursor.moveToPosition(position); |
| long begin = cursor.getLong(INDEX_BEGIN); |
| long end = cursor.getLong(INDEX_END); |
| long eventId = cursor.getLong(INDEX_EVENT_ID); |
| mDeleteEventHelper.delete(begin, end, eventId, -1); |
| } |
| } |
| } |
| break; |
| |
| case KeyEvent.KEYCODE_BACK: |
| finish(); |
| return true; |
| } |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| /** |
| * Clears the cached value for the last query time so that renewCursor() |
| * will force a requery of the Calendar events. |
| */ |
| private void clearLastQueryTime() { |
| mLastQueryTime.year = 0; |
| mLastQueryTime.month = 0; |
| } |
| |
| private void renewCursor() { |
| // Avoid batching up repeated queries for the same month. This can |
| // happen if the user scrolls with the trackball too fast. |
| if (mLastQueryTime.month == mTime.month && mLastQueryTime.year == mTime.year) { |
| return; |
| } |
| |
| // Query all instances for the current month |
| Time time = new Time(); |
| time.year = mTime.year; |
| time.month = mTime.month; |
| long start = time.normalize(true); |
| |
| time.month++; |
| long end = time.normalize(true); |
| |
| StringBuilder path = new StringBuilder(); |
| path.append(start); |
| path.append('/'); |
| path.append(end); |
| |
| // Respect the preference to show/hide declined events |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
| boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED, |
| false); |
| |
| Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, path.toString()); |
| |
| String selection; |
| if (hideDeclined) { |
| selection = Calendars.SELECTED + "=1 AND " + |
| Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; |
| } else { |
| selection = Calendars.SELECTED + "=1"; |
| } |
| |
| // Cancel any previous queries that haven't started yet. This |
| // isn't likely to happen since we already avoid sending |
| // a duplicate query for the same month as the previous query. |
| // But if the user quickly wiggles the trackball back and forth, |
| // he could generate a stream of queries. |
| mQueryHandler.cancelOperation(0); |
| |
| mLastQueryTime.month = mTime.month; |
| mLastQueryTime.year = mTime.year; |
| mQueryHandler.startQuery(0, null, uri, PROJECTION, selection, null, |
| AGENDA_SORT_ORDER); |
| } |
| |
| private void selectTime() { |
| // Selects the first event of the day |
| AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView(); |
| if (current.getCursor() == null) { |
| return; |
| } |
| |
| int position = current.getDayAdapter().findDayPositionNearestTime(mTime); |
| current.setSelection(position); |
| } |
| |
| /* ViewSwitcher.ViewFactory interface methods */ |
| public View makeView() { |
| AgendaListView agendaListView = new AgendaListView(this); |
| return agendaListView; |
| } |
| |
| /* Navigator interface methods */ |
| public void goToToday() { |
| Time now = new Time(); |
| now.set(System.currentTimeMillis()); |
| goTo(now); |
| } |
| |
| public void goTo(Time time) { |
| if (mTime.year == time.year && mTime.month == time.month) { |
| mTime = time; |
| selectTime(); |
| } else { |
| mTime = time; |
| renewCursor(); |
| } |
| } |
| |
| public long getSelectedTime() { |
| // Update the current time based on the selected event |
| AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView(); |
| int position = current.getSelectedItemPosition(); |
| position = current.getDayAdapter().getCursorPosition(position); |
| Cursor cursor = current.getCursor(); |
| if (position >= 0 && position < cursor.getCount()) { |
| cursor.moveToPosition(position); |
| mTime.set(cursor.getLong(INDEX_BEGIN)); |
| } |
| |
| return mTime.toMillis(true); |
| } |
| |
| public boolean getAllDay() { |
| return false; |
| } |
| } |
| |