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