| /* |
| * 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.provider.CalendarContract.Attendees; |
| 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.Utils; |
| |
| import java.util.Date; |
| |
| 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 final Time mTime; |
| private String mTimeZone; |
| private final 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; |
| private boolean mForceReplace = true; |
| private long mLastShownEventId = -1; |
| |
| |
| |
| // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action |
| // bar. |
| int mJulianDayOnTop = -1; |
| |
| private final 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(); |
| mLastHandledEventTime = new Time(); |
| |
| if (mInitialTimeMillis == 0) { |
| mTime.setToNow(); |
| } else { |
| mTime.set(mInitialTimeMillis); |
| } |
| mLastHandledEventTime.set(mTime); |
| 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, true); |
| 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) { |
| |
| |
| int screenWidth = mActivity.getResources().getDisplayMetrics().widthPixels; |
| 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); |
| } |
| } |
| |
| View eventView = v.findViewById(R.id.agenda_event_info); |
| if (!mShowEventDetailsWithAgenda) { |
| eventView.setVisibility(View.GONE); |
| } |
| |
| View topListView; |
| // 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); |
| topListView = lv; |
| } else { |
| topListView = mAgendaListView; |
| } |
| |
| // Since using weight for sizing the two panes of the agenda fragment causes the whole |
| // fragment to re-measure when the sticky header is replaced, calculate the weighted |
| // size of each pane here and set it |
| |
| if (!mShowEventDetailsWithAgenda) { |
| ViewGroup.LayoutParams params = topListView.getLayoutParams(); |
| params.width = screenWidth; |
| topListView.setLayoutParams(params); |
| } else { |
| ViewGroup.LayoutParams listParams = topListView.getLayoutParams(); |
| listParams.width = screenWidth * 4 / 10; |
| topListView.setLayoutParams(listParams); |
| ViewGroup.LayoutParams detailsParams = eventView.getLayoutParams(); |
| detailsParams.width = screenWidth - listParams.width; |
| eventView.setLayoutParams(detailsParams); |
| } |
| 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; |
| } |
| if (mShowEventDetailsWithAgenda) { |
| long timeToSave; |
| if (mLastHandledEventTime != null) { |
| timeToSave = mLastHandledEventTime.toMillis(true); |
| mTime.set(mLastHandledEventTime); |
| } else { |
| timeToSave = System.currentTimeMillis(); |
| mTime.set(timeToSave); |
| } |
| outState.putLong(BUNDLE_KEY_RESTORE_TIME, timeToSave); |
| mController.setTime(timeToSave); |
| } else { |
| AgendaWindowAdapter.AgendaItem item = mAgendaListView.getFirstVisibleAgendaItem(); |
| if (item != null) { |
| long firstVisibleTime = mAgendaListView.getFirstVisibleTime(item); |
| if (firstVisibleTime > 0) { |
| mTime.set(firstVisibleTime); |
| mController.setTime(firstVisibleTime); |
| outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); |
| } |
| // Tell AllInOne the event id of the first visible event in the list. The id will be |
| // used in the GOTO when AllInOne is restored so that Agenda Fragment can select a |
| // specific event and not just the time. |
| mLastShownEventId = item.id; |
| } |
| } |
| 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) { |
| 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 (event.selectedTime != null) { |
| mTime.set(event.selectedTime); |
| } else if (event.startTime != null) { |
| mTime.set(event.startTime); |
| } |
| if (mAgendaListView == null) { |
| // The view hasn't been set yet. Just save the time and use it |
| // later. |
| return; |
| } |
| mAgendaListView.goTo(mTime, event.id, mQuery, false, |
| ((event.extraLong & CalendarController.EXTRA_GOTO_TODAY) != 0 && |
| mShowEventDetailsWithAgenda) ? true : false); |
| AgendaAdapter.ViewHolder vh = mAgendaListView.getSelectedViewHolder(); |
| // Make sure that on the first time the event info is shown to recreate it |
| Log.d(TAG, "selected viewholder is null: " + (vh == null)); |
| showEventInfo(event, vh != null ? vh.allDay : false, mForceReplace); |
| mForceReplace = 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.selectedTime != null) ? event.selectedTime : event.startTime; |
| goTo(event, true); |
| } else if (event.eventType == EventType.SEARCH) { |
| search(event.query, event.startTime); |
| } else if (event.eventType == EventType.EVENTS_CHANGED) { |
| eventsChanged(); |
| } |
| } |
| |
| public long getLastShowEventId() { |
| return mLastShownEventId; |
| } |
| |
| // Shows the selected event in the Agenda view |
| private void showEventInfo(EventInfo event, boolean allDay, boolean replaceFragment) { |
| |
| // Ignore unknown events |
| if (event.id == -1) { |
| Log.e(TAG, "showEventInfo, event ID = " + event.id); |
| return; |
| } |
| |
| mLastShownEventId = event.id; |
| |
| // 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(); |
| |
| if (allDay) { |
| event.startTime.timezone = Time.TIMEZONE_UTC; |
| event.endTime.timezone = Time.TIMEZONE_UTC; |
| } |
| |
| if (DEBUG) { |
| Log.d(TAG, "***"); |
| Log.d(TAG, "showEventInfo: start: " + new Date(event.startTime.toMillis(true))); |
| Log.d(TAG, "showEventInfo: end: " + new Date(event.endTime.toMillis(true))); |
| Log.d(TAG, "showEventInfo: all day: " + allDay); |
| Log.d(TAG, "***"); |
| } |
| |
| long startMillis = event.startTime.toMillis(true); |
| long endMillis = event.endTime.toMillis(true); |
| EventInfoFragment fOld = |
| (EventInfoFragment)fragmentManager.findFragmentById(R.id.agenda_event_info); |
| if (fOld == null || replaceFragment || fOld.getStartMillis() != startMillis || |
| fOld.getEndMillis() != endMillis || fOld.getEventId() != event.id) { |
| mEventFragment = new EventInfoFragment(mActivity, event.id, |
| startMillis, endMillis, |
| Attendees.ATTENDEE_STATUS_NONE, false, |
| EventInfoFragment.DIALOG_WINDOW_STYLE, null); |
| ft.replace(R.id.agenda_event_info, mEventFragment); |
| ft.commit(); |
| } else { |
| fOld.reloadEvents(); |
| } |
| } |
| } |
| |
| // 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); |
| } |
| }); |
| } |
| } |
| } |
| } |