/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.calendar.agenda; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.SharedPreferences; import android.os.Bundle; import android.text.format.Time; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.Adapter; import android.widget.HeaderViewListAdapter; import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarController.ViewType; import com.android.calendar.EventInfoFragment; import com.android.calendar.GeneralPreferences; import com.android.calendar.R; import com.android.calendar.StickyHeaderListView; import com.android.calendar.StickyHeaderListView.HeaderHeightListener; import com.android.calendar.StickyHeaderListView.HeaderIndexer; import com.android.calendar.Utils; public class AgendaFragment extends Fragment implements CalendarController.EventHandler, OnScrollListener { private static final String TAG = AgendaFragment.class.getSimpleName(); private static boolean DEBUG = false; protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; protected static final String BUNDLE_KEY_RESTORE_INSTANCE_ID = "key_restore_instance_id"; private AgendaListView mAgendaListView; private Activity mActivity; private Time mTime; private String mTimeZone; private long mInitialTimeMillis; private boolean mShowEventDetailsWithAgenda; private CalendarController mController; private EventInfoFragment mEventFragment; private String mQuery; private boolean mUsedForSearch = false; private boolean mIsTabletConfig; private EventInfo mOnAttachedInfo = null; private boolean mOnAttachAllDay = false; private AgendaWindowAdapter mAdapter = null; // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action // bar. int mJulianDayOnTop = -1; private Runnable mTZUpdater = new Runnable() { @Override public void run() { mTimeZone = Utils.getTimeZone(getActivity(), this); mTime.switchTimezone(mTimeZone); } }; public AgendaFragment() { this(0, false); } // timeMillis - time of first event to show // usedForSearch - indicates if this fragment is used in the search fragment public AgendaFragment(long timeMillis, boolean usedForSearch) { mInitialTimeMillis = timeMillis; mTime = new Time(); if (mInitialTimeMillis == 0) { mTime.setToNow(); } else { mTime.set(mInitialTimeMillis); } mUsedForSearch = usedForSearch; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mTimeZone = Utils.getTimeZone(activity, mTZUpdater); mTime.switchTimezone(mTimeZone); mActivity = activity; if (mOnAttachedInfo != null) { showEventInfo(mOnAttachedInfo, mOnAttachAllDay); mOnAttachedInfo = null; } } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mController = CalendarController.getInstance(mActivity); mShowEventDetailsWithAgenda = Utils.getConfigBool(mActivity, R.bool.show_event_details_with_agenda); mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config); if (icicle != null) { long prevTime = icicle.getLong(BUNDLE_KEY_RESTORE_TIME, -1); if (prevTime != -1) { mTime.set(prevTime); if (DEBUG) { Log.d(TAG, "Restoring time to " + mTime.toString()); } } } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.agenda_fragment, null); mAgendaListView = (AgendaListView)v.findViewById(R.id.agenda_events_list); mAgendaListView.setClickable(true); if (savedInstanceState != null) { long instanceId = savedInstanceState.getLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, -1); if (instanceId != -1) { mAgendaListView.setSelectedInstanceId(instanceId); } } if (!mShowEventDetailsWithAgenda) { v.findViewById(R.id.agenda_event_info).setVisibility(View.GONE); } // Set adapter & HeaderIndexer for StickyHeaderListView StickyHeaderListView lv = (StickyHeaderListView)v.findViewById(R.id.agenda_sticky_header_list); if (lv != null) { Adapter a = mAgendaListView.getAdapter(); lv.setAdapter(a); if (a instanceof HeaderViewListAdapter) { mAdapter = (AgendaWindowAdapter) ((HeaderViewListAdapter)a).getWrappedAdapter(); lv.setIndexer(mAdapter); lv.setHeaderHeightListener(mAdapter); } else if (a instanceof AgendaWindowAdapter) { mAdapter = (AgendaWindowAdapter)a; lv.setIndexer(mAdapter); lv.setHeaderHeightListener(mAdapter); } else { Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView"); } // Set scroll listener so that the date on the ActionBar can be set while // the user scrolls the view lv.setOnScrollListener(this); lv.setHeaderSeparator(getResources().getColor(R.color.agenda_list_separator_color), 1); } return v; } @Override public void onResume() { super.onResume(); if (DEBUG) { Log.v(TAG, "OnResume to " + mTime.toString()); } SharedPreferences prefs = GeneralPreferences.getSharedPreferences( getActivity()); boolean hideDeclined = prefs.getBoolean( GeneralPreferences.KEY_HIDE_DECLINED, false); mAgendaListView.setHideDeclinedEvents(hideDeclined); if (mLastHandledEventId != -1) { mAgendaListView.goTo(mLastHandledEventTime, mLastHandledEventId, mQuery, true, false); mLastHandledEventTime = null; mLastHandledEventId = -1; } else { mAgendaListView.goTo(mTime, -1, mQuery, true, false); } mAgendaListView.onResume(); // // Register for Intent broadcasts // IntentFilter filter = new IntentFilter(); // filter.addAction(Intent.ACTION_TIME_CHANGED); // filter.addAction(Intent.ACTION_DATE_CHANGED); // filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); // registerReceiver(mIntentReceiver, filter); // // mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mAgendaListView == null) { return; } long firstVisibleTime = mAgendaListView.getFirstVisibleTime(); if (firstVisibleTime > 0) { mTime.set(firstVisibleTime); mController.setTime(firstVisibleTime); outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); if (DEBUG) { Log.v(TAG, "onSaveInstanceState " + mTime.toString()); } } long selectedInstance = mAgendaListView.getSelectedInstanceId(); if (selectedInstance >= 0) { outState.putLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, selectedInstance); } } /** * This cleans up the event info fragment since the FragmentManager doesn't * handle nested fragments. Without this, the action bar buttons added by * the info fragment can come back on a rotation. * * @param fragmentManager */ public void removeFragments(FragmentManager fragmentManager) { mController.deregisterEventHandler(R.id.agenda_event_info); if (getActivity().isFinishing()) { return; } FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment f = fragmentManager.findFragmentById(R.id.agenda_event_info); if (f != null) { ft.remove(f); } ft.commit(); } @Override public void onPause() { super.onPause(); mAgendaListView.onPause(); // mContentResolver.unregisterContentObserver(mObserver); // unregisterReceiver(mIntentReceiver); // Record Agenda View as the (new) default detailed view. // Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); } private void goTo(EventInfo event, boolean animate) { if (mAgendaListView == null) { // The view hasn't been set yet. Just save the time and use it // later. mTime.set(event.startTime); return; } // Set mTime if we have a start time and we aren't in the range of the // goto if (event.startTime != null && (mTime.before(event.startTime) || event.endTime == null || mTime .after(event.endTime))) { mTime.set(event.startTime); } mAgendaListView.goTo(mTime, event.id, mQuery, false, ((event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0 && mShowEventDetailsWithAgenda) ? true : false); AgendaAdapter.ViewHolder vh = mAgendaListView.getSelectedViewHolder(); showEventInfo(event, vh != null ? vh.allDay : false); } private void search(String query, Time time) { mQuery = query; if (time != null) { mTime.set(time); } if (mAgendaListView == null) { // The view hasn't been set yet. Just return. return; } mAgendaListView.goTo(time, -1, mQuery, true, false); } @Override public void eventsChanged() { if (mAgendaListView != null) { mAgendaListView.refresh(true); } } @Override public long getSupportedEventTypes() { return EventType.GO_TO | EventType.EVENTS_CHANGED | ((mUsedForSearch)?EventType.SEARCH:0); } private long mLastHandledEventId = -1; private Time mLastHandledEventTime = null; @Override public void handleEvent(EventInfo event) { if (event.eventType == EventType.GO_TO) { // TODO support a range of time // TODO support event_id // TODO figure out the animate bit mLastHandledEventId = event.id; mLastHandledEventTime = event.startTime; goTo(event, true); } else if (event.eventType == EventType.SEARCH) { search(event.query, event.startTime); } else if (event.eventType == EventType.EVENTS_CHANGED) { eventsChanged(); } } // Shows the selected event in the Agenda view private void showEventInfo(EventInfo event, boolean allDay) { // Ignore unknown events if (event.id == -1) { Log.e(TAG, "showEventInfo, event ID = " + event.id); return; } // Create a fragment to show the event to the side of the agenda list if (mShowEventDetailsWithAgenda) { FragmentManager fragmentManager = getFragmentManager(); if (fragmentManager == null) { // Got a goto event before the fragment finished attaching, // stash the event and handle it later. mOnAttachedInfo = event; mOnAttachAllDay = allDay; return; } FragmentTransaction ft = fragmentManager.beginTransaction(); int response = CalendarController.ATTENDEE_NO_RESPONSE; if (event.eventType == EventType.VIEW_EVENT || event.eventType == EventType.EDIT_EVENT) { response = (int) event.extraLong; } if (allDay) { event.startTime.timezone = Time.TIMEZONE_UTC; event.endTime.timezone = Time.TIMEZONE_UTC; } mEventFragment = new EventInfoFragment(mActivity, event.id, event.startTime.toMillis(true), event.endTime.toMillis(true), response, false, EventInfoFragment.DIALOG_WINDOW_STYLE); ft.replace(R.id.agenda_event_info, mEventFragment); mController.registerEventHandler(R.id.agenda_event_info, mEventFragment); ft.commit(); } // else { // Intent intent = new Intent(Intent.ACTION_VIEW); // Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id); // intent.setData(eventUri); // intent.setClass(getActivity(), AllInOneActivity.class); // intent.putExtra(EVENT_BEGIN_TIME, event.startTime != null ? event.startTime // .toMillis(true) : -1); // intent.putExtra(EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true) // : -1); // startActivity(intent); // } } // OnScrollListener implementation to update the date on the pull-down menu of the app @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // Save scroll state so that the adapter can stop the scroll when the // agenda list is fling state and it needs to set the agenda list to a new position if (mAdapter != null) { mAdapter.setScrollState(scrollState); } } // Gets the time of the first visible view. If it is a new time, send a message to update // the time on the ActionBar @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int julianDay = mAgendaListView.getJulianDayFromPosition(firstVisibleItem - mAgendaListView.getHeaderViewsCount()); // On error - leave the old view if (julianDay == 0) { return; } // If the day changed, update the ActionBar if (mJulianDayOnTop != julianDay) { mJulianDayOnTop = julianDay; Time t = new Time(mTimeZone); t.setJulianDay(mJulianDayOnTop); mController.setTime(t.toMillis(true)); // Cannot sent a message that eventually may change the layout of the views // so instead post a runnable that will run when the layout is done if (!mIsTabletConfig) { view.post(new Runnable() { @Override public void run() { Time t = new Time(mTimeZone); t.setJulianDay(mJulianDayOnTop); mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, null, -1, ViewType.CURRENT, 0, null, null); } }); } } } }