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            if ("TARDIS".equalsIgnoreCase(query)) {
160                Utils.tardis();
161            }
162            initFragments(millis, query);
163        }
164    }
165
166    @Override
167    protected void onDestroy() {
168        super.onDestroy();
169        mController.deregisterAllEventHandlers();
170        CalendarController.removeInstance(this);
171    }
172
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
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
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
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
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
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
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
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
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
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
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
346    public void eventsChanged() {
347        mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
348    }
349
350    @Override
351    public long getSupportedEventTypes() {
352        return EventType.VIEW_EVENT | EventType.DELETE_EVENT;
353    }
354
355    @Override
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
366    public boolean onQueryTextChange(String newText) {
367        return false;
368    }
369
370    @Override
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
379    public boolean onMenuItemActionExpand(MenuItem item) {
380        return true;
381    }
382
383    @Override
384    public boolean onMenuItemActionCollapse(MenuItem item) {
385        Utils.returnToCalendarHome(this);
386        return false;
387    }
388}
389