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