AgendaFragment.java revision 96d6163c524b338307b01d25916ccffd495e2f29
1/*
2 * Copyright (C) 2007 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
17package com.android.calendar.agenda;
18
19
20import android.app.Activity;
21import android.app.Fragment;
22import android.app.FragmentManager;
23import android.app.FragmentTransaction;
24import android.content.SharedPreferences;
25import android.os.Bundle;
26import android.text.format.Time;
27import android.util.Log;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.AbsListView;
32import android.widget.AbsListView.OnScrollListener;
33import android.widget.Adapter;
34import android.widget.HeaderViewListAdapter;
35
36import com.android.calendar.CalendarController;
37import com.android.calendar.CalendarController.EventInfo;
38import com.android.calendar.CalendarController.EventType;
39import com.android.calendar.CalendarController.ViewType;
40import com.android.calendar.EventInfoFragment;
41import com.android.calendar.GeneralPreferences;
42import com.android.calendar.R;
43import com.android.calendar.StickyHeaderListView;
44import com.android.calendar.StickyHeaderListView.HeaderIndexer;
45import com.android.calendar.Utils;
46
47
48public class AgendaFragment extends Fragment implements CalendarController.EventHandler,
49        OnScrollListener {
50
51    private static final String TAG = AgendaFragment.class.getSimpleName();
52    private static boolean DEBUG = false;
53
54    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
55    protected static final String BUNDLE_KEY_RESTORE_INSTANCE_ID = "key_restore_instance_id";
56
57    private AgendaListView mAgendaListView;
58    private Activity mActivity;
59    private Time mTime;
60    private String mTimeZone;
61    private long mInitialTimeMillis;
62    private boolean mShowEventDetailsWithAgenda;
63    private CalendarController mController;
64    private EventInfoFragment mEventFragment;
65    private String mQuery;
66    private boolean mUsedForSearch = false;
67    private boolean mIsTabletConfig;
68    private EventInfo mOnAttachedInfo = null;
69
70    // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action
71    // bar.
72    int  mJulianDayOnTop = -1;
73
74    private Runnable mTZUpdater = new Runnable() {
75        @Override
76        public void run() {
77            mTimeZone = Utils.getTimeZone(getActivity(), this);
78            mTime.switchTimezone(mTimeZone);
79        }
80    };
81
82    public AgendaFragment() {
83        this(0, false);
84    }
85
86
87    // timeMillis - time of first event to show
88    // usedForSearch - indicates if this fragment is used in the search fragment
89    public AgendaFragment(long timeMillis, boolean usedForSearch) {
90        mInitialTimeMillis = timeMillis;
91        mTime = new Time();
92        if (mInitialTimeMillis == 0) {
93            mTime.setToNow();
94        } else {
95            mTime.set(mInitialTimeMillis);
96        }
97        mUsedForSearch = usedForSearch;
98    }
99
100    @Override
101    public void onAttach(Activity activity) {
102        super.onAttach(activity);
103        mTimeZone = Utils.getTimeZone(activity, mTZUpdater);
104        mTime.switchTimezone(mTimeZone);
105        mActivity = activity;
106        if (mOnAttachedInfo != null) {
107            showEventInfo(mOnAttachedInfo);
108            mOnAttachedInfo = null;
109        }
110    }
111
112    @Override
113    public void onCreate(Bundle icicle) {
114        super.onCreate(icicle);
115        mController = CalendarController.getInstance(mActivity);
116        mShowEventDetailsWithAgenda =
117            Utils.getConfigBool(mActivity, R.bool.show_event_details_with_agenda);
118        mIsTabletConfig =
119            Utils.getConfigBool(mActivity, R.bool.tablet_config);
120        if (icicle != null) {
121            long prevTime = icicle.getLong(BUNDLE_KEY_RESTORE_TIME, -1);
122            if (prevTime != -1) {
123                mTime.set(prevTime);
124                if (DEBUG) {
125                    Log.d(TAG, "Restoring time to " + mTime.toString());
126                }
127            }
128        }
129    }
130
131    @Override
132    public View onCreateView(LayoutInflater inflater, ViewGroup container,
133            Bundle savedInstanceState) {
134
135
136        View v = inflater.inflate(R.layout.agenda_fragment, null);
137
138        mAgendaListView = (AgendaListView)v.findViewById(R.id.agenda_events_list);
139        mAgendaListView.setClickable(true);
140        mAgendaListView.goTo(mTime, -1, mQuery, true);
141
142        if (savedInstanceState != null) {
143            long instanceId = savedInstanceState.getLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, -1);
144            if (instanceId != -1) {
145                mAgendaListView.setSelectedInstanceId(instanceId);
146            }
147        }
148
149        if (!mShowEventDetailsWithAgenda) {
150            v.findViewById(R.id.agenda_event_info).setVisibility(View.GONE);
151        }
152
153        // Set adapter & HeaderIndexer for StickyHeaderListView
154        StickyHeaderListView lv =
155            (StickyHeaderListView)v.findViewById(R.id.agenda_sticky_header_list);
156        if (lv != null) {
157            Adapter a = mAgendaListView.getAdapter();
158            lv.setAdapter(a);
159            if (a instanceof HeaderViewListAdapter) {
160                lv.setIndexer((HeaderIndexer) ((HeaderViewListAdapter)a).getWrappedAdapter());
161            } else if (a instanceof AgendaWindowAdapter) {
162                lv.setIndexer((HeaderIndexer) a);
163            } else {
164                Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView");
165            }
166
167            // Set scroll listener so that the date on the ActionBar can be set while
168            // the user scrolls the view
169            lv.setOnScrollListener(this);
170        }
171        return v;
172    }
173
174    @Override
175    public void onResume() {
176        super.onResume();
177        if (DEBUG) {
178            Log.v(TAG, "OnResume to " + mTime.toString());
179        }
180
181        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(
182                getActivity());
183        boolean hideDeclined = prefs.getBoolean(
184                GeneralPreferences.KEY_HIDE_DECLINED, false);
185
186        mAgendaListView.setHideDeclinedEvents(hideDeclined);
187        mAgendaListView.goTo(mTime, -1, mQuery, true);
188        mAgendaListView.onResume();
189
190//        // Register for Intent broadcasts
191//        IntentFilter filter = new IntentFilter();
192//        filter.addAction(Intent.ACTION_TIME_CHANGED);
193//        filter.addAction(Intent.ACTION_DATE_CHANGED);
194//        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
195//        registerReceiver(mIntentReceiver, filter);
196//
197//        mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
198    }
199
200    @Override
201    public void onSaveInstanceState(Bundle outState) {
202        super.onSaveInstanceState(outState);
203        if (mAgendaListView == null) {
204            return;
205        }
206        long firstVisibleTime = mController.getTime();
207        if (firstVisibleTime > 0) {
208            mTime.set(firstVisibleTime);
209            outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime);
210            if (DEBUG) {
211                Log.v(TAG, "onSaveInstanceState " + mTime.toString());
212            }
213        }
214
215        long selectedInstance = mAgendaListView.getSelectedInstanceId();
216        if (selectedInstance >= 0) {
217            outState.putLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, selectedInstance);
218        }
219    }
220
221    /**
222     * This cleans up the event info fragment since the FragmentManager doesn't
223     * handle nested fragments. Without this, the action bar buttons added by
224     * the info fragment can come back on a rotation.
225     *
226     * @param fragmentManager
227     */
228    public void removeFragments(FragmentManager fragmentManager) {
229        mController.deregisterEventHandler(R.id.agenda_event_info);
230        if (getActivity().isFinishing()) {
231            return;
232        }
233        FragmentTransaction ft = fragmentManager.beginTransaction();
234        Fragment f = fragmentManager.findFragmentById(R.id.agenda_event_info);
235        if (f != null) {
236            ft.remove(f);
237        }
238        ft.commit();
239    }
240
241    @Override
242    public void onPause() {
243        super.onPause();
244
245        mAgendaListView.onPause();
246//        mContentResolver.unregisterContentObserver(mObserver);
247//        unregisterReceiver(mIntentReceiver);
248
249        // Record Agenda View as the (new) default detailed view.
250//        Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID);
251    }
252
253    private void goTo(EventInfo event, boolean animate) {
254        if (mAgendaListView == null) {
255            // The view hasn't been set yet. Just save the time and use it
256            // later.
257            mTime.set(event.startTime);
258            return;
259        }
260        // Set mTime if we have a start time and we aren't in the range of the
261        // goto
262        if (event.startTime != null
263                && (mTime.before(event.startTime) || event.endTime == null || mTime
264                        .after(event.endTime))) {
265            mTime.set(event.startTime);
266        }
267        mAgendaListView.goTo(mTime, event.id, mQuery, false);
268        showEventInfo(event);
269    }
270
271    private void search(String query, Time time) {
272        mQuery = query;
273        if (time != null) {
274            mTime.set(time);
275        }
276        if (mAgendaListView == null) {
277            // The view hasn't been set yet. Just return.
278            return;
279        }
280        mAgendaListView.goTo(time, -1, mQuery, true);
281    }
282
283    @Override
284    public void eventsChanged() {
285        if (mAgendaListView != null) {
286            mAgendaListView.refresh(true);
287        }
288    }
289
290    @Override
291    public long getSupportedEventTypes() {
292        return EventType.GO_TO | EventType.EVENTS_CHANGED | ((mUsedForSearch)?EventType.SEARCH:0);
293    }
294
295    @Override
296    public void handleEvent(EventInfo event) {
297        if (event.eventType == EventType.GO_TO) {
298            // TODO support a range of time
299            // TODO support event_id
300            // TODO figure out the animate bit
301            goTo(event, true);
302        } else if (event.eventType == EventType.SEARCH) {
303            search(event.query, event.startTime);
304        } else if (event.eventType == EventType.EVENTS_CHANGED) {
305            eventsChanged();
306        }
307    }
308
309
310    // Shows the selected event in the Agenda view
311    private void showEventInfo(EventInfo event) {
312
313        // Ignore unknown events
314        if (event.id == -1) {
315            Log.e(TAG, "showEventInfo, event ID = " + event.id);
316            return;
317        }
318
319        // Create a fragment to show the event to the side of the agenda list
320        if (mShowEventDetailsWithAgenda) {
321            FragmentManager fragmentManager = getFragmentManager();
322            if (fragmentManager == null) {
323                // Got a goto event before the fragment finished attaching,
324                // stash the event and handle it later.
325                mOnAttachedInfo = event;
326                return;
327            }
328            FragmentTransaction ft = fragmentManager.beginTransaction();
329            int response = CalendarController.ATTENDEE_NO_RESPONSE;
330            if (event.eventType == EventType.VIEW_EVENT
331                    || event.eventType == EventType.EDIT_EVENT) {
332                response = (int) event.extraLong;
333            }
334            mEventFragment = new EventInfoFragment(mActivity, event.id,
335                    event.startTime.toMillis(false), event.endTime.toMillis(false),
336                    response, false);
337            ft.replace(R.id.agenda_event_info, mEventFragment);
338            mController.registerEventHandler(R.id.agenda_event_info,
339                    mEventFragment);
340            ft.commit();
341        }
342//        else {
343//            Intent intent = new Intent(Intent.ACTION_VIEW);
344//            Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id);
345//            intent.setData(eventUri);
346//            intent.setClass(getActivity(), AllInOneActivity.class);
347//            intent.putExtra(EVENT_BEGIN_TIME, event.startTime != null ? event.startTime
348//                    .toMillis(true) : -1);
349//            intent.putExtra(EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true)
350//                    : -1);
351//            startActivity(intent);
352//        }
353    }
354
355    // OnScrollListener implementation to update the date on the pull-down menu of the app
356
357    public void onScrollStateChanged(AbsListView view, int scrollState) {
358        // Do nothing
359    }
360
361    // Gets the time of the first visible view. If it is a new time, send a message to update
362    // the time on the ActionBar
363    @Override
364    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
365            int totalItemCount) {
366        int julianDay = mAgendaListView.getJulianDayFromPosition(firstVisibleItem
367                - mAgendaListView.getHeaderViewsCount());
368        // On error - leave the old view
369        if (julianDay == 0) {
370            return;
371        }
372        // If the day changed, update the ActionBar
373        if (mJulianDayOnTop != julianDay) {
374            mJulianDayOnTop = julianDay;
375            Time t = new Time(mTimeZone);
376            t.setJulianDay(mJulianDayOnTop);
377            mController.setTime(t.toMillis(true));
378            // Cannot sent a message that eventually may change the layout of the views
379            // so instead post a runnable that will run when the layout is done
380            if (!mIsTabletConfig) {
381                view.post(new Runnable() {
382                    @Override
383                    public void run() {
384                        Time t = new Time(mTimeZone);
385                        t.setJulianDay(mJulianDayOnTop);
386                        mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, null, -1,
387                                ViewType.CURRENT, 0, null, null);
388                    }
389                });
390            }
391        }
392    }
393}
394