diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/calendar/AgendaListView.java | 20 | ||||
-rw-r--r-- | src/com/android/calendar/AgendaWindowAdapter.java | 104 | ||||
-rw-r--r-- | src/com/android/calendar/SearchActivity.java | 251 | ||||
-rw-r--r-- | src/com/android/calendar/Utils.java | 10 |
4 files changed, 354 insertions, 31 deletions
diff --git a/src/com/android/calendar/AgendaListView.java b/src/com/android/calendar/AgendaListView.java index b9e9ea7a..d8184196 100644 --- a/src/com/android/calendar/AgendaListView.java +++ b/src/com/android/calendar/AgendaListView.java @@ -20,6 +20,7 @@ import com.android.calendar.AgendaAdapter.ViewHolder; import com.android.calendar.AgendaWindowAdapter.EventInfo; import com.android.calendar.CalendarController.EventType; +import android.content.Context; import android.graphics.Rect; import android.text.format.Time; import android.util.Log; @@ -35,19 +36,18 @@ public class AgendaListView extends ListView implements OnItemClickListener { private static final boolean DEBUG = false; private AgendaWindowAdapter mWindowAdapter; - private DeleteEventHelper mDeleteEventHelper; - public AgendaListView(AgendaActivity agendaActivity) { - super(agendaActivity, null); + public AgendaListView(Context context) { + super(context, null); setOnItemClickListener(this); setChoiceMode(ListView.CHOICE_MODE_SINGLE); setVerticalScrollBarEnabled(false); - mWindowAdapter = new AgendaWindowAdapter(agendaActivity, this); + mWindowAdapter = new AgendaWindowAdapter(context, this); setAdapter(mWindowAdapter); mDeleteEventHelper = - new DeleteEventHelper(agendaActivity, null, false /* don't exit when done */); + new DeleteEventHelper(context, null, false /* don't exit when done */); } @Override protected void onDetachedFromWindow() { @@ -71,6 +71,16 @@ public class AgendaListView extends ListView implements OnItemClickListener { mWindowAdapter.refresh(time, forced); } + public void search(String searchQuery, boolean forced) { + Time time = new Time(); + long goToTime = getFirstVisibleTime(); + if (goToTime <= 0) { + goToTime = System.currentTimeMillis(); + } + time.set(goToTime); + mWindowAdapter.refresh(time, searchQuery, forced); + } + public void refresh(boolean forced) { Time time = new Time(); long goToTime = getFirstVisibleTime(); diff --git a/src/com/android/calendar/AgendaWindowAdapter.java b/src/com/android/calendar/AgendaWindowAdapter.java index 5d43c8ff..e3720d46 100644 --- a/src/com/android/calendar/AgendaWindowAdapter.java +++ b/src/com/android/calendar/AgendaWindowAdapter.java @@ -18,9 +18,11 @@ package com.android.calendar; import android.content.AsyncQueryHandler; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.provider.Calendar; import android.provider.Calendar.Attendees; import android.provider.Calendar.Calendars; import android.provider.Calendar.Instances; @@ -64,7 +66,11 @@ public class AgendaWindowAdapter extends BaseAdapter { static final boolean DEBUGLOG = false; private static String TAG = "AgendaWindowAdapter"; - private static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC"; + private static final String AGENDA_SORT_ORDER = + Calendar.Instances.START_DAY + " ASC, " + + Calendar.Instances.BEGIN + " ASC, " + + Calendar.Events.TITLE + " ASC"; + public static final int INDEX_TITLE = 1; public static final int INDEX_EVENT_LOCATION = 2; public static final int INDEX_ALL_DAY = 3; @@ -108,8 +114,8 @@ public class AgendaWindowAdapter extends BaseAdapter { private static final int PREFETCH_BOUNDARY = 1; - // Times to auto-expand/retry query after getting no data - private static final int RETRIES_ON_NO_DATA = 0; + /** Times to auto-expand/retry query after getting no data */ + private static final int RETRIES_ON_NO_DATA = 1; private Context mContext; @@ -117,11 +123,14 @@ public class AgendaWindowAdapter extends BaseAdapter { private AgendaListView mAgendaListView; - private int mRowCount; // The sum of the rows in all the adapters + /** The sum of the rows in all the adapters */ + private int mRowCount; + /** The number of times we have queried and gotten no results back */ private int mEmptyCursorCount; - private DayAdapterInfo mLastUsedInfo; // Cached value of the last used adapter. + /** Cached value of the last used adapter */ + private DayAdapterInfo mLastUsedInfo; private LinkedList<DayAdapterInfo> mAdapterInfos = new LinkedList<DayAdapterInfo>(); @@ -133,24 +142,24 @@ public class AgendaWindowAdapter extends BaseAdapter { private boolean mDoneSettingUpHeaderFooter = false; - /* + /** * When the user scrolled to the top, a query will be made for older events * and this will be incremented. Don't make more requests if * mOlderRequests > mOlderRequestsProcessed. */ private int mOlderRequests; - // Number of "older" query that has been processed. + /** Number of "older" query that has been processed. */ private int mOlderRequestsProcessed; - /* + /** * When the user scrolled to the bottom, a query will be made for newer * events and this will be incremented. Don't make more requests if * mNewerRequests > mNewerRequestsProcessed. */ private int mNewerRequests; - // Number of "newer" query that has been processed. + /** Number of "newer" query that has been processed. */ private int mNewerRequestsProcessed; // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. @@ -160,6 +169,9 @@ public class AgendaWindowAdapter extends BaseAdapter { private boolean mShuttingDown; private boolean mHideDeclined; + /** The current search query, or null if none */ + private String mSearchQuery; + // Types of Query private static final int QUERY_TYPE_OLDER = 0; // Query for older events private static final int QUERY_TYPE_NEWER = 1; // Query for newer events @@ -174,6 +186,8 @@ public class AgendaWindowAdapter extends BaseAdapter { int end; + String searchQuery; + int queryType; public QuerySpec(int queryType) { @@ -188,6 +202,7 @@ public class AgendaWindowAdapter extends BaseAdapter { result = prime * result + (int) (queryStartMillis ^ (queryStartMillis >>> 32)); result = prime * result + queryType; result = prime * result + start; + result = prime * result + searchQuery.hashCode(); if (goToTime != null) { long goToTimeMillis = goToTime.toMillis(false); result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32)); @@ -202,9 +217,11 @@ public class AgendaWindowAdapter extends BaseAdapter { if (getClass() != obj.getClass()) return false; QuerySpec other = (QuerySpec) obj; if (end != other.end || queryStartMillis != other.queryStartMillis - || queryType != other.queryType || start != other.start) { + || queryType != other.queryType || start != other.start + || Utils.equals(searchQuery, other.searchQuery)) { return false; } + if (goToTime != null) { if (goToTime.toMillis(false) != other.goToTime.toMillis(false)) { return false; @@ -259,16 +276,18 @@ public class AgendaWindowAdapter extends BaseAdapter { } } - public AgendaWindowAdapter(AgendaActivity agendaActivity, + public AgendaWindowAdapter(Context context, AgendaListView agendaListView) { - mContext = agendaActivity; + mContext = context; mAgendaListView = agendaListView; - mQueryHandler = new QueryHandler(agendaActivity.getContentResolver()); + mQueryHandler = new QueryHandler(context.getContentResolver()); mStringBuilder = new StringBuilder(50); mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - LayoutInflater inflater = (LayoutInflater) agendaActivity + mSearchQuery = null; + + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); mHeaderView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); mFooterView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null); @@ -469,6 +488,15 @@ public class AgendaWindowAdapter extends BaseAdapter { } public void refresh(Time goToTime, boolean forced) { + refresh(goToTime, mSearchQuery, forced); + } + + public void refresh(Time goToTime, String searchQuery, boolean forced) { + if (!Utils.equals(searchQuery, mSearchQuery)) { + // When we change search terms, clean up any old state, start over + resetInstanceFields(); + } + mSearchQuery = searchQuery; if (DEBUGLOG) { Log.e(TAG, "refresh " + goToTime.toString() + (forced ? " forced" : " not forced")); } @@ -484,7 +512,7 @@ public class AgendaWindowAdapter extends BaseAdapter { // Query for a total of MIN_QUERY_DURATION days int endDay = startDay + MIN_QUERY_DURATION; - queueQuery(startDay, endDay, goToTime, QUERY_TYPE_CLEAN); + queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN); } public void close() { @@ -539,6 +567,20 @@ public class AgendaWindowAdapter extends BaseAdapter { } } + /** + * Resets any transient state in this instance and puts it back into a state + * where it can be treated as a newly instantiated adapter + * + * TODO are these all of the fields that need to be reset? + */ + private void resetInstanceFields() { + mEmptyCursorCount = 0; + mNewerRequests = 0; + mNewerRequestsProcessed = 0; + mOlderRequests = 0; + mOlderRequestsProcessed = 0; + } + private String buildQuerySelection() { // Respect the preference to show/hide declined events @@ -551,13 +593,17 @@ public class AgendaWindowAdapter extends BaseAdapter { } } - private Uri buildQueryUri(int start, int end) { - StringBuilder path = new StringBuilder(); - path.append(start); - path.append('/'); - path.append(end); - Uri uri = Uri.withAppendedPath(Instances.CONTENT_BY_DAY_URI, path.toString()); - return uri; + private Uri buildQueryUri(int start, int end, String searchQuery) { + Uri rootUri = searchQuery == null ? + Instances.CONTENT_BY_DAY_URI : + Instances.CONTENT_SEARCH_BY_DAY_URI; + Uri.Builder builder = rootUri.buildUpon(); + ContentUris.appendId(builder, start); + ContentUris.appendId(builder, end); + if (searchQuery != null) { + builder.appendPath(searchQuery); + } + return builder.build(); } private boolean isInRange(int start, int end) { @@ -584,15 +630,18 @@ public class AgendaWindowAdapter extends BaseAdapter { return queryDuration; } - private boolean queueQuery(int start, int end, Time goToTime, int queryType) { + private boolean queueQuery(int start, int end, Time goToTime, + String searchQuery, int queryType) { QuerySpec queryData = new QuerySpec(queryType); queryData.goToTime = goToTime; queryData.start = start; queryData.end = end; + queryData.searchQuery = searchQuery; return queueQuery(queryData); } private boolean queueQuery(QuerySpec queryData) { + queryData.searchQuery = mSearchQuery; Boolean queuedQuery; synchronized (mQueryQueue) { queuedQuery = false; @@ -634,9 +683,12 @@ public class AgendaWindowAdapter extends BaseAdapter { mQueryHandler.cancelOperation(0); if (BASICLOG) queryData.queryStartMillis = System.nanoTime(); - mQueryHandler.startQuery(0, queryData, buildQueryUri( - queryData.start, queryData.end), PROJECTION, - buildQuerySelection(), null, AGENDA_SORT_ORDER); + + Uri queryUri = buildQueryUri( + queryData.start, queryData.end, queryData.searchQuery); + mQueryHandler.startQuery(0, queryData, queryUri, + PROJECTION, buildQuerySelection(), null, + AGENDA_SORT_ORDER); } private String formatDateString(int julianDay) { diff --git a/src/com/android/calendar/SearchActivity.java b/src/com/android/calendar/SearchActivity.java new file mode 100644 index 00000000..d5b56315 --- /dev/null +++ b/src/com/android/calendar/SearchActivity.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2010 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 static android.provider.Calendar.EVENT_BEGIN_TIME; +import dalvik.system.VMRuntime; +import android.app.Activity; +import android.app.SearchManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Calendar.Events; +import android.text.format.Time; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; + +/** + */ +public class SearchActivity extends Activity implements Navigator { + + private static final String TAG = SearchActivity.class.getSimpleName(); + + private static boolean DEBUG = false; + + private static final long INITIAL_HEAP_SIZE = 4*1024*1024; + + protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; + + protected static final String BUNDLE_KEY_RESTORE_SEARCH_QUERY = + "key_restore_search_query"; + + private ContentResolver mContentResolver; + + private AgendaListView mAgendaListView; + + private Time mTime; + + 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)) { + mAgendaListView.refresh(true); + } + } + }; + + private ContentObserver mObserver = new ContentObserver(new Handler()) { + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + mAgendaListView.refresh(true); + } + }; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + // 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 + VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE); + + mAgendaListView = new AgendaListView(this); + setContentView(mAgendaListView); + + mContentResolver = getContentResolver(); + + setTitle(R.string.search); + + long millis = 0; + mTime = new Time(); + if (icicle != null) { + // Returns 0 if key not found + millis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME); + if (DEBUG) { + Log.v(TAG, "Restore value from icicle: " + millis); + } + } + if (millis == 0) { + // Didn't find a time in the bundle, look in intent or current time + millis = Utils.timeFromIntentInMillis(getIntent()); + } + Intent intent = getIntent(); + mTime.set(millis); + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + String query = intent.getStringExtra(SearchManager.QUERY); + search(query); + } + } + + @Override + protected void onNewIntent(Intent intent) { + // From the Android Dev Guide: "It's important to note that when + // onNewIntent(Intent) is called, the Activity has not been restarted, + // so the getIntent() method will still return the Intent that was first + // received with onCreate(). This is why setIntent(Intent) is called + // inside onNewIntent(Intent) (just in case you call getIntent() at a + // later time)." + setIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + String query = intent.getStringExtra(SearchManager.QUERY); + search(query); + } else { + long time = Utils.timeFromIntentInMillis(intent); + if (time > 0) { + mTime.set(time); + goTo(mTime, false); + } + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + long firstVisibleTime = mAgendaListView.getFirstVisibleTime(); + if (firstVisibleTime > 0) { + mTime.set(firstVisibleTime); + outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); + if (DEBUG) { + Log.v(TAG, "onSaveInstanceState " + mTime.toString()); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + if (DEBUG) { + Log.v(TAG, "OnResume to " + mTime.toString()); + } + + SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences( + getApplicationContext()); + boolean hideDeclined = prefs.getBoolean( + CalendarPreferenceActivity.KEY_HIDE_DECLINED, false); + + mAgendaListView.setHideDeclinedEvents(hideDeclined); + mAgendaListView.goTo(mTime, true); + mAgendaListView.onResume(); + + // 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 onPause() { + super.onPause(); + + mAgendaListView.onPause(); + mContentResolver.unregisterContentObserver(mObserver); + unregisterReceiver(mIntentReceiver); + + // Record Agenda View as the (new) default detailed view. + Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); + } + + @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) + mAgendaListView.deleteSelectedEvent(); + break; + } + return super.onKeyDown(keyCode, event); + } + + private void search(String searchQuery) { + mAgendaListView.search(searchQuery, true); + } + + + /* Navigator interface methods */ + public void goToToday() { + Time now = new Time(); + now.setToNow(); + mAgendaListView.goTo(now, true); // Force refresh + } + + public void goTo(Time time, boolean animate) { + mAgendaListView.goTo(time, false); + } + + public long getSelectedTime() { + return mAgendaListView.getSelectedTime(); + } + + public boolean getAllDay() { + return false; + } + +} diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java index e928c709..b3f9e1dc 100644 --- a/src/com/android/calendar/Utils.java +++ b/src/com/android/calendar/Utils.java @@ -307,4 +307,14 @@ public class Utils { } } } + + /** + * Null-safe object comparison + * @param s1 + * @param s2 + * @return + */ + public static boolean equals(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } } |