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