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