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