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