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 */ 16 package com.android.calendar; 17 18 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; 19 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; 20 21 import android.app.ActionBar; 22 import android.app.Activity; 23 import android.app.FragmentManager; 24 import android.app.FragmentTransaction; 25 import android.app.SearchManager; 26 import android.content.BroadcastReceiver; 27 import android.content.ContentResolver; 28 import android.content.ContentUris; 29 import android.content.Intent; 30 import android.database.ContentObserver; 31 import android.graphics.drawable.LayerDrawable; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.provider.CalendarContract.Events; 36 import android.provider.SearchRecentSuggestions; 37 import android.text.format.Time; 38 import android.util.Log; 39 import android.view.Menu; 40 import android.view.MenuItem; 41 import android.view.MenuItem.OnActionExpandListener; 42 import android.widget.SearchView; 43 44 import com.android.calendar.CalendarController.EventInfo; 45 import com.android.calendar.CalendarController.EventType; 46 import com.android.calendar.CalendarController.ViewType; 47 import com.android.calendar.agenda.AgendaFragment; 48 49 public 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 LayerDrawable icon = (LayerDrawable)menu.findItem(R.id.action_today).getIcon(); 254 Utils.setTodayIcon(icon, this, Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); 255 256 MenuItem item = menu.findItem(R.id.action_search); 257 item.expandActionView(); 258 item.setOnActionExpandListener(this); 259 mSearchView = (SearchView) item.getActionView(); 260 Utils.setUpSearchView(mSearchView, this); 261 mSearchView.setQuery(mQuery, false); 262 mSearchView.clearFocus(); 263 264 return true; 265 } 266 267 @Override 268 public boolean onOptionsItemSelected(MenuItem item) { 269 Time t = null; 270 switch (item.getItemId()) { 271 case R.id.action_today: 272 t = new Time(); 273 t.setToNow(); 274 mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT); 275 return true; 276 case R.id.action_search: 277 return false; 278 case R.id.action_settings: 279 mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0); 280 return true; 281 case android.R.id.home: 282 Utils.returnToCalendarHome(this); 283 return true; 284 default: 285 return false; 286 } 287 } 288 289 @Override 290 protected void onNewIntent(Intent intent) { 291 // From the Android Dev Guide: "It's important to note that when 292 // onNewIntent(Intent) is called, the Activity has not been restarted, 293 // so the getIntent() method will still return the Intent that was first 294 // received with onCreate(). This is why setIntent(Intent) is called 295 // inside onNewIntent(Intent) (just in case you call getIntent() at a 296 // later time)." 297 setIntent(intent); 298 handleIntent(intent); 299 } 300 301 private void handleIntent(Intent intent) { 302 if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 303 String query = intent.getStringExtra(SearchManager.QUERY); 304 search(query, null); 305 } 306 } 307 308 @Override 309 public void onSaveInstanceState(Bundle outState) { 310 super.onSaveInstanceState(outState); 311 outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime()); 312 outState.putString(BUNDLE_KEY_RESTORE_SEARCH_QUERY, mQuery); 313 } 314 315 @Override 316 protected void onResume() { 317 super.onResume(); 318 319 Utils.setMidnightUpdater( 320 mHandler, mTimeChangesUpdater, Utils.getTimeZone(this, mTimeChangesUpdater)); 321 // Make sure the today icon is up to date 322 invalidateOptionsMenu(); 323 mTimeChangesReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater); 324 mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); 325 // We call this in case the user changed the time zone 326 eventsChanged(); 327 } 328 329 @Override 330 protected void onPause() { 331 super.onPause(); 332 Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater); 333 Utils.clearTimeChangesReceiver(this, mTimeChangesReceiver); 334 mContentResolver.unregisterContentObserver(mObserver); 335 } 336 337 @Override 338 public void eventsChanged() { 339 mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); 340 } 341 342 @Override 343 public long getSupportedEventTypes() { 344 return EventType.VIEW_EVENT | EventType.DELETE_EVENT; 345 } 346 347 @Override 348 public void handleEvent(EventInfo event) { 349 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); 350 if (event.eventType == EventType.VIEW_EVENT) { 351 showEventInfo(event); 352 } else if (event.eventType == EventType.DELETE_EVENT) { 353 deleteEvent(event.id, event.startTime.toMillis(false), endTime); 354 } 355 } 356 357 @Override 358 public boolean onQueryTextChange(String newText) { 359 return false; 360 } 361 362 @Override 363 public boolean onQueryTextSubmit(String query) { 364 mQuery = query; 365 mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query, 366 getComponentName()); 367 return false; 368 } 369 370 @Override 371 public boolean onMenuItemActionExpand(MenuItem item) { 372 return true; 373 } 374 375 @Override 376 public boolean onMenuItemActionCollapse(MenuItem item) { 377 Utils.returnToCalendarHome(this); 378 return false; 379 } 380 } 381