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