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 onCreate(Bundle icicle)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 if ("TARDIS".equalsIgnoreCase(query)) { 160 Utils.tardis(); 161 } 162 initFragments(millis, query); 163 } 164 } 165 166 @Override onDestroy()167 protected void onDestroy() { 168 super.onDestroy(); 169 mController.deregisterAllEventHandlers(); 170 CalendarController.removeInstance(this); 171 } 172 initFragments(long timeMillis, String query)173 private void initFragments(long timeMillis, String query) { 174 FragmentManager fragmentManager = getFragmentManager(); 175 FragmentTransaction ft = fragmentManager.beginTransaction(); 176 177 AgendaFragment searchResultsFragment = new AgendaFragment(timeMillis, true); 178 ft.replace(R.id.search_results, searchResultsFragment); 179 mController.registerEventHandler(R.id.search_results, searchResultsFragment); 180 181 ft.commit(); 182 Time t = new Time(); 183 t.set(timeMillis); 184 search(query, t); 185 } 186 showEventInfo(EventInfo event)187 private void showEventInfo(EventInfo event) { 188 if (mShowEventDetailsWithAgenda) { 189 FragmentManager fragmentManager = getFragmentManager(); 190 FragmentTransaction ft = fragmentManager.beginTransaction(); 191 192 mEventInfoFragment = new EventInfoFragment(this, event.id, 193 event.startTime.toMillis(false), event.endTime.toMillis(false), 194 event.getResponse(), false, EventInfoFragment.DIALOG_WINDOW_STYLE, 195 null /* No reminders to explicitly pass in. */); 196 ft.replace(R.id.agenda_event_info, mEventInfoFragment); 197 ft.commit(); 198 } else { 199 Intent intent = new Intent(Intent.ACTION_VIEW); 200 Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id); 201 intent.setData(eventUri); 202 intent.setClass(this, EventInfoActivity.class); 203 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, 204 event.startTime != null ? event.startTime.toMillis(true) : -1); 205 intent.putExtra( 206 EXTRA_EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true) : -1); 207 startActivity(intent); 208 } 209 mCurrentEventId = event.id; 210 } 211 search(String searchQuery, Time goToTime)212 private void search(String searchQuery, Time goToTime) { 213 // save query in recent queries 214 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, 215 Utils.getSearchAuthority(this), 216 CalendarRecentSuggestionsProvider.MODE); 217 suggestions.saveRecentQuery(searchQuery, null); 218 219 220 EventInfo searchEventInfo = new EventInfo(); 221 searchEventInfo.eventType = EventType.SEARCH; 222 searchEventInfo.query = searchQuery; 223 searchEventInfo.viewType = ViewType.AGENDA; 224 if (goToTime != null) { 225 searchEventInfo.startTime = goToTime; 226 } 227 mController.sendEvent(this, searchEventInfo); 228 mQuery = searchQuery; 229 if (mSearchView != null) { 230 mSearchView.setQuery(mQuery, false); 231 mSearchView.clearFocus(); 232 } 233 } 234 deleteEvent(long eventId, long startMillis, long endMillis)235 private void deleteEvent(long eventId, long startMillis, long endMillis) { 236 mDeleteEventHelper.delete(startMillis, endMillis, eventId, -1); 237 if (mIsMultipane && mEventInfoFragment != null 238 && eventId == mCurrentEventId) { 239 FragmentManager fragmentManager = getFragmentManager(); 240 FragmentTransaction ft = fragmentManager.beginTransaction(); 241 ft.remove(mEventInfoFragment); 242 ft.commit(); 243 mEventInfoFragment = null; 244 mCurrentEventId = -1; 245 } 246 } 247 248 @Override onCreateOptionsMenu(Menu menu)249 public boolean onCreateOptionsMenu(Menu menu) { 250 super.onCreateOptionsMenu(menu); 251 getMenuInflater().inflate(R.menu.search_title_bar, menu); 252 253 // replace the default top layer drawable of the today icon with a custom drawable 254 // that shows the day of the month of today 255 MenuItem menuItem = menu.findItem(R.id.action_today); 256 if (Utils.isJellybeanOrLater()) { 257 LayerDrawable icon = (LayerDrawable) menuItem.getIcon(); 258 Utils.setTodayIcon( 259 icon, this, Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater)); 260 } else { 261 menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light); 262 } 263 264 MenuItem item = menu.findItem(R.id.action_search); 265 item.expandActionView(); 266 item.setOnActionExpandListener(this); 267 mSearchView = (SearchView) item.getActionView(); 268 Utils.setUpSearchView(mSearchView, this); 269 mSearchView.setQuery(mQuery, false); 270 mSearchView.clearFocus(); 271 272 return true; 273 } 274 275 @Override onOptionsItemSelected(MenuItem item)276 public boolean onOptionsItemSelected(MenuItem item) { 277 Time t = null; 278 final int itemId = item.getItemId(); 279 if (itemId == R.id.action_today) { 280 t = new Time(); 281 t.setToNow(); 282 mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT); 283 return true; 284 } else if (itemId == R.id.action_search) { 285 return false; 286 } else if (itemId == R.id.action_settings) { 287 mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0); 288 return true; 289 } else if (itemId == android.R.id.home) { 290 Utils.returnToCalendarHome(this); 291 return true; 292 } else { 293 return false; 294 } 295 } 296 297 @Override onNewIntent(Intent intent)298 protected void onNewIntent(Intent intent) { 299 // From the Android Dev Guide: "It's important to note that when 300 // onNewIntent(Intent) is called, the Activity has not been restarted, 301 // so the getIntent() method will still return the Intent that was first 302 // received with onCreate(). This is why setIntent(Intent) is called 303 // inside onNewIntent(Intent) (just in case you call getIntent() at a 304 // later time)." 305 setIntent(intent); 306 handleIntent(intent); 307 } 308 handleIntent(Intent intent)309 private void handleIntent(Intent intent) { 310 if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 311 String query = intent.getStringExtra(SearchManager.QUERY); 312 search(query, null); 313 } 314 } 315 316 @Override onSaveInstanceState(Bundle outState)317 public void onSaveInstanceState(Bundle outState) { 318 super.onSaveInstanceState(outState); 319 outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime()); 320 outState.putString(BUNDLE_KEY_RESTORE_SEARCH_QUERY, mQuery); 321 } 322 323 @Override onResume()324 protected void onResume() { 325 super.onResume(); 326 327 Utils.setMidnightUpdater( 328 mHandler, mTimeChangesUpdater, Utils.getTimeZone(this, mTimeChangesUpdater)); 329 // Make sure the today icon is up to date 330 invalidateOptionsMenu(); 331 mTimeChangesReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater); 332 mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); 333 // We call this in case the user changed the time zone 334 eventsChanged(); 335 } 336 337 @Override onPause()338 protected void onPause() { 339 super.onPause(); 340 Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater); 341 Utils.clearTimeChangesReceiver(this, mTimeChangesReceiver); 342 mContentResolver.unregisterContentObserver(mObserver); 343 } 344 345 @Override eventsChanged()346 public void eventsChanged() { 347 mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT); 348 } 349 350 @Override getSupportedEventTypes()351 public long getSupportedEventTypes() { 352 return EventType.VIEW_EVENT | EventType.DELETE_EVENT; 353 } 354 355 @Override handleEvent(EventInfo event)356 public void handleEvent(EventInfo event) { 357 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); 358 if (event.eventType == EventType.VIEW_EVENT) { 359 showEventInfo(event); 360 } else if (event.eventType == EventType.DELETE_EVENT) { 361 deleteEvent(event.id, event.startTime.toMillis(false), endTime); 362 } 363 } 364 365 @Override onQueryTextChange(String newText)366 public boolean onQueryTextChange(String newText) { 367 return false; 368 } 369 370 @Override onQueryTextSubmit(String query)371 public boolean onQueryTextSubmit(String query) { 372 mQuery = query; 373 mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query, 374 getComponentName()); 375 return false; 376 } 377 378 @Override onMenuItemActionExpand(MenuItem item)379 public boolean onMenuItemActionExpand(MenuItem item) { 380 return true; 381 } 382 383 @Override onMenuItemActionCollapse(MenuItem item)384 public boolean onMenuItemActionCollapse(MenuItem item) { 385 Utils.returnToCalendarHome(this); 386 return false; 387 } 388 } 389