1/* 2 * Copyright (C) 2010 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 */ 16package com.android.calendar; 17 18import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; 19import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; 20 21import android.app.ActionBar; 22import android.app.Activity; 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.app.SearchManager; 26import android.content.BroadcastReceiver; 27import android.content.ContentResolver; 28import android.content.ContentUris; 29import android.content.Intent; 30import android.database.ContentObserver; 31import android.graphics.drawable.LayerDrawable; 32import android.net.Uri; 33import android.os.Bundle; 34import android.os.Handler; 35import android.provider.CalendarContract.Events; 36import android.provider.SearchRecentSuggestions; 37import android.text.format.Time; 38import android.util.Log; 39import android.view.Menu; 40import android.view.MenuItem; 41import android.view.MenuItem.OnActionExpandListener; 42import android.widget.SearchView; 43 44import com.android.calendar.CalendarController.EventInfo; 45import com.android.calendar.CalendarController.EventType; 46import com.android.calendar.CalendarController.ViewType; 47import com.android.calendar.agenda.AgendaFragment; 48 49public class SearchActivity extends Activity implements CalendarController.EventHandler, 50 SearchView.OnQueryTextListener, OnActionExpandListener { 51 52 private static final String TAG = SearchActivity.class.getSimpleName(); 53 54 private static final boolean DEBUG = false; 55 56 private static final int HANDLER_KEY = 0; 57 58 protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; 59 60 protected static final String BUNDLE_KEY_RESTORE_SEARCH_QUERY = 61 "key_restore_search_query"; 62 63 // display event details to the side of the event list 64 private boolean mShowEventDetailsWithAgenda; 65 private static boolean mIsMultipane; 66 67 private CalendarController mController; 68 69 private EventInfoFragment mEventInfoFragment; 70 71 private long mCurrentEventId = -1; 72 73 private String mQuery; 74 75 private SearchView mSearchView; 76 77 private DeleteEventHelper mDeleteEventHelper; 78 79 private Handler mHandler; 80 private BroadcastReceiver mTimeChangesReceiver; 81 private ContentResolver mContentResolver; 82 83 private final ContentObserver mObserver = new ContentObserver(new Handler()) { 84 @Override 85 public boolean deliverSelfNotifications() { 86 return true; 87 } 88 89 @Override 90 public void onChange(boolean selfChange) { 91 eventsChanged(); 92 } 93 }; 94 95 // runs when a timezone was changed and updates the today icon 96 private final Runnable mTimeChangesUpdater = new Runnable() { 97 @Override 98 public void run() { 99 Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, 100 Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); 101 SearchActivity.this.invalidateOptionsMenu(); 102 } 103 }; 104 105 @Override 106 protected void onCreate(Bundle icicle) { 107 super.onCreate(icicle); 108 // This needs to be created before setContentView 109 mController = CalendarController.getInstance(this); 110 mHandler = new Handler(); 111 112 mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config); 113 mShowEventDetailsWithAgenda = 114 Utils.getConfigBool(this, R.bool.show_event_details_with_agenda); 115 116 setContentView(R.layout.search); 117 118 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); 119 120 mContentResolver = getContentResolver(); 121 122 if (mIsMultipane) { 123 getActionBar().setDisplayOptions( 124 ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); 125 } else { 126 getActionBar().setDisplayOptions(0, 127 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME); 128 } 129 130 // Must be the first to register because this activity can modify the 131 // list of event handlers in it's handle method. This affects who the 132 // rest of the handlers the controller dispatches to are. 133 mController.registerEventHandler(HANDLER_KEY, this); 134 135 mDeleteEventHelper = new DeleteEventHelper(this, this, 136 false /* don't exit when done */); 137 138 long millis = 0; 139 if (icicle != null) { 140 // Returns 0 if key not found 141 millis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME); 142 if (DEBUG) { 143 Log.v(TAG, "Restore value from icicle: " + millis); 144 } 145 } 146 if (millis == 0) { 147 // Didn't find a time in the bundle, look in intent or current time 148 millis = Utils.timeFromIntentInMillis(getIntent()); 149 } 150 151 Intent intent = getIntent(); 152 if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 153 String query; 154 if (icicle != null && icicle.containsKey(BUNDLE_KEY_RESTORE_SEARCH_QUERY)) { 155 query = icicle.getString(BUNDLE_KEY_RESTORE_SEARCH_QUERY); 156 } else { 157 query = intent.getStringExtra(SearchManager.QUERY); 158 } 159 initFragments(millis, query); 160 } 161 } 162 163 @Override 164 protected void onDestroy() { 165 super.onDestroy(); 166 mController.deregisterAllEventHandlers(); 167 CalendarController.removeInstance(this); 168 } 169 170 private void initFragments(long timeMillis, String query) { 171 FragmentManager fragmentManager = getFragmentManager(); 172 FragmentTransaction ft = fragmentManager.beginTransaction(); 173 174 AgendaFragment searchResultsFragment = new AgendaFragment(timeMillis, true); 175 ft.replace(R.id.search_results, searchResultsFragment); 176 mController.registerEventHandler(R.id.search_results, searchResultsFragment); 177 178 ft.commit(); 179 Time t = new Time(); 180 t.set(timeMillis); 181 search(query, t); 182 } 183 184 private void showEventInfo(EventInfo event) { 185 if (mShowEventDetailsWithAgenda) { 186 FragmentManager fragmentManager = getFragmentManager(); 187 FragmentTransaction ft = fragmentManager.beginTransaction(); 188 189 mEventInfoFragment = new EventInfoFragment(this, event.id, 190 event.startTime.toMillis(false), event.endTime.toMillis(false), 191 event.getResponse(), false, EventInfoFragment.DIALOG_WINDOW_STYLE); 192 ft.replace(R.id.agenda_event_info, mEventInfoFragment); 193 ft.commit(); 194 mController.registerEventHandler(R.id.agenda_event_info, mEventInfoFragment); 195 } else { 196 Intent intent = new Intent(Intent.ACTION_VIEW); 197 Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id); 198 intent.setData(eventUri); 199 intent.setClass(this, EventInfoActivity.class); 200 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, 201 event.startTime != null ? event.startTime.toMillis(true) : -1); 202 intent.putExtra( 203 EXTRA_EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true) : -1); 204 startActivity(intent); 205 } 206 mCurrentEventId = event.id; 207 } 208 209 private void search(String searchQuery, Time goToTime) { 210 // save query in recent queries 211 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, 212 Utils.getSearchAuthority(this), 213 CalendarRecentSuggestionsProvider.MODE); 214 suggestions.saveRecentQuery(searchQuery, null); 215 216 217 EventInfo searchEventInfo = new EventInfo(); 218 searchEventInfo.eventType = EventType.SEARCH; 219 searchEventInfo.query = searchQuery; 220 searchEventInfo.viewType = ViewType.AGENDA; 221 if (goToTime != null) { 222 searchEventInfo.startTime = goToTime; 223 } 224 mController.sendEvent(this, searchEventInfo); 225 mQuery = searchQuery; 226 if (mSearchView != null) { 227 mSearchView.setQuery(mQuery, false); 228 mSearchView.clearFocus(); 229 } 230 } 231 232 private void deleteEvent(long eventId, long startMillis, long endMillis) { 233 mDeleteEventHelper.delete(startMillis, endMillis, eventId, -1); 234 if (mIsMultipane && mEventInfoFragment != null 235 && eventId == mCurrentEventId) { 236 FragmentManager fragmentManager = getFragmentManager(); 237 FragmentTransaction ft = fragmentManager.beginTransaction(); 238 ft.remove(mEventInfoFragment); 239 ft.commit(); 240 mEventInfoFragment = null; 241 mController.deregisterEventHandler(R.id.agenda_event_info); 242 mCurrentEventId = -1; 243 } 244 } 245 246 @Override 247 public boolean onCreateOptionsMenu(Menu menu) { 248 super.onCreateOptionsMenu(menu); 249 getMenuInflater().inflate(R.menu.search_title_bar, menu); 250 251 // replace the default top layer drawable of the today icon with a custom drawable 252 // that shows the day of the month of today 253 MenuItem menuItem = menu.findItem(R.id.action_today); 254 if (Utils.isJellybeanOrLater()) { 255 LayerDrawable icon = (LayerDrawable) menuItem.getIcon(); 256 Utils.setTodayIcon( 257 icon, this, Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); 258 } else { 259 menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light); 260 } 261 262 MenuItem item = menu.findItem(R.id.action_search); 263 item.expandActionView(); 264 item.setOnActionExpandListener(this); 265 mSearchView = (SearchView) item.getActionView(); 266 Utils.setUpSearchView(mSearchView, this); 267 mSearchView.setQuery(mQuery, false); 268 mSearchView.clearFocus(); 269 270 return true; 271 } 272 273 @Override 274 public boolean onOptionsItemSelected(MenuItem item) { 275 Time t = null; 276 final int itemId = item.getItemId(); 277 if (itemId == R.id.action_today) { 278 t = new Time(); 279 t.setToNow(); 280 mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT); 281 return true; 282 } else if (itemId == R.id.action_search) { 283 return false; 284 } else if (itemId == R.id.action_settings) { 285 mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0); 286 return true; 287 } else if (itemId == android.R.id.home) { 288 Utils.returnToCalendarHome(this); 289 return true; 290 } else { 291 return false; 292 } 293 } 294 295 @Override 296 protected void onNewIntent(Intent intent) { 297 // From the Android Dev Guide: "It's important to note that when 298 // onNewIntent(Intent) is called, the Activity has not been restarted, 299 // so the getIntent() method will still return the Intent that was first 300 // received with onCreate(). This is why setIntent(Intent) is called 301 // inside onNewIntent(Intent) (just in case you call getIntent() at a 302 // later time)." 303 setIntent(intent); 304 handleIntent(intent); 305 } 306 307 private void handleIntent(Intent intent) { 308 if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 309 String query = intent.getStringExtra(SearchManager.QUERY); 310 search(query, null); 311 } 312 } 313 314 @Override 315 public void onSaveInstanceState(Bundle outState) { 316 super.onSaveInstanceState(outState); 317 outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime()); 318 outState.putString(BUNDLE_KEY_RESTORE_SEARCH_QUERY, mQuery); 319 } 320 321 @Override 322 protected void onResume() { 323 super.onResume(); 324 325 Utils.setMidnightUpdater( 326 mHandler, mTimeChangesUpdater, Utils.getTimeZone(this, mTimeChangesUpdater)); 327 // Make sure the today icon is up to date 328 invalidateOptionsMenu(); 329 mTimeChangesReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater); 330 mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); 331 // We call this in case the user changed the time zone 332 eventsChanged(); 333 } 334 335 @Override 336 protected void onPause() { 337 super.onPause(); 338 Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater); 339 Utils.clearTimeChangesReceiver(this, mTimeChangesReceiver); 340 mContentResolver.unregisterContentObserver(mObserver); 341 } 342 343 @Override 344 public void eventsChanged() { 345 mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); 346 } 347 348 @Override 349 public long getSupportedEventTypes() { 350 return EventType.VIEW_EVENT | EventType.DELETE_EVENT; 351 } 352 353 @Override 354 public void handleEvent(EventInfo event) { 355 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); 356 if (event.eventType == EventType.VIEW_EVENT) { 357 showEventInfo(event); 358 } else if (event.eventType == EventType.DELETE_EVENT) { 359 deleteEvent(event.id, event.startTime.toMillis(false), endTime); 360 } 361 } 362 363 @Override 364 public boolean onQueryTextChange(String newText) { 365 return false; 366 } 367 368 @Override 369 public boolean onQueryTextSubmit(String query) { 370 mQuery = query; 371 mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query, 372 getComponentName()); 373 return false; 374 } 375 376 @Override 377 public boolean onMenuItemActionExpand(MenuItem item) { 378 return true; 379 } 380 381 @Override 382 public boolean onMenuItemActionCollapse(MenuItem item) { 383 Utils.returnToCalendarHome(this); 384 return false; 385 } 386} 387