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            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        MenuItem menuItem = menu.findItem(R.id.action_today);
254        if (Utils.isJellybeanOrLater()) {
255            LayerDrawable icon = (LayerDrawable) menuItem.getIcon();
256            Utils.setTodayIcon(
257                    icon, this, Utils.getTimeZone(SearchActivity.this, mTimeChangesUpdater));
258        } else {
259            menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light);
260        }
261
262        MenuItem item = menu.findItem(R.id.action_search);
263        item.expandActionView();
264        item.setOnActionExpandListener(this);
265        mSearchView = (SearchView) item.getActionView();
266        Utils.setUpSearchView(mSearchView, this);
267        mSearchView.setQuery(mQuery, false);
268        mSearchView.clearFocus();
269
270        return true;
271    }
272
273    @Override
274    public boolean onOptionsItemSelected(MenuItem item) {
275        Time t = null;
276        final int itemId = item.getItemId();
277        if (itemId == R.id.action_today) {
278            t = new Time();
279            t.setToNow();
280            mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT);
281            return true;
282        } else if (itemId == R.id.action_search) {
283            return false;
284        } else if (itemId == R.id.action_settings) {
285            mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0);
286            return true;
287        } else if (itemId == android.R.id.home) {
288            Utils.returnToCalendarHome(this);
289            return true;
290        } else {
291            return false;
292        }
293    }
294
295    @Override
296    protected void onNewIntent(Intent intent) {
297        // From the Android Dev Guide: "It's important to note that when
298        // onNewIntent(Intent) is called, the Activity has not been restarted,
299        // so the getIntent() method will still return the Intent that was first
300        // received with onCreate(). This is why setIntent(Intent) is called
301        // inside onNewIntent(Intent) (just in case you call getIntent() at a
302        // later time)."
303        setIntent(intent);
304        handleIntent(intent);
305    }
306
307    private void handleIntent(Intent intent) {
308        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
309            String query = intent.getStringExtra(SearchManager.QUERY);
310            search(query, null);
311        }
312    }
313
314    @Override
315    public void onSaveInstanceState(Bundle outState) {
316        super.onSaveInstanceState(outState);
317        outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime());
318        outState.putString(BUNDLE_KEY_RESTORE_SEARCH_QUERY, mQuery);
319    }
320
321    @Override
322    protected void onResume() {
323        super.onResume();
324
325        Utils.setMidnightUpdater(
326                mHandler, mTimeChangesUpdater, Utils.getTimeZone(this, mTimeChangesUpdater));
327        // Make sure the today icon is up to date
328        invalidateOptionsMenu();
329        mTimeChangesReceiver = Utils.setTimeChangesReceiver(this, mTimeChangesUpdater);
330        mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
331        // We call this in case the user changed the time zone
332        eventsChanged();
333    }
334
335    @Override
336    protected void onPause() {
337        super.onPause();
338        Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater);
339        Utils.clearTimeChangesReceiver(this, mTimeChangesReceiver);
340        mContentResolver.unregisterContentObserver(mObserver);
341    }
342
343    @Override
344    public void eventsChanged() {
345        mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
346    }
347
348    @Override
349    public long getSupportedEventTypes() {
350        return EventType.VIEW_EVENT | EventType.DELETE_EVENT;
351    }
352
353    @Override
354    public void handleEvent(EventInfo event) {
355        long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false);
356        if (event.eventType == EventType.VIEW_EVENT) {
357            showEventInfo(event);
358        } else if (event.eventType == EventType.DELETE_EVENT) {
359            deleteEvent(event.id, event.startTime.toMillis(false), endTime);
360        }
361    }
362
363    @Override
364    public boolean onQueryTextChange(String newText) {
365        return false;
366    }
367
368    @Override
369    public boolean onQueryTextSubmit(String query) {
370        mQuery = query;
371        mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, 0, query,
372                getComponentName());
373        return false;
374    }
375
376    @Override
377    public boolean onMenuItemActionExpand(MenuItem item) {
378        return true;
379    }
380
381    @Override
382    public boolean onMenuItemActionCollapse(MenuItem item) {
383        Utils.returnToCalendarHome(this);
384        return false;
385    }
386}
387