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.text.format.Time;
     27 import android.util.Log;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.widget.AbsListView;
     32 import android.widget.AbsListView.OnScrollListener;
     33 import android.widget.Adapter;
     34 import android.widget.HeaderViewListAdapter;
     35 
     36 import com.android.calendar.CalendarController;
     37 import com.android.calendar.CalendarController.EventInfo;
     38 import com.android.calendar.CalendarController.EventType;
     39 import com.android.calendar.CalendarController.ViewType;
     40 import com.android.calendar.EventInfoFragment;
     41 import com.android.calendar.GeneralPreferences;
     42 import com.android.calendar.R;
     43 import com.android.calendar.StickyHeaderListView;
     44 import com.android.calendar.StickyHeaderListView.HeaderHeightListener;
     45 import com.android.calendar.StickyHeaderListView.HeaderIndexer;
     46 import com.android.calendar.Utils;
     47 
     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 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