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