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.HeaderIndexer; 45 import com.android.calendar.Utils; 46 47 48 public class AgendaFragment extends Fragment implements CalendarController.EventHandler, 49 OnScrollListener { 50 51 private static final String TAG = AgendaFragment.class.getSimpleName(); 52 private static boolean DEBUG = false; 53 54 protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time"; 55 protected static final String BUNDLE_KEY_RESTORE_INSTANCE_ID = "key_restore_instance_id"; 56 57 private AgendaListView mAgendaListView; 58 private Activity mActivity; 59 private Time mTime; 60 private String mTimeZone; 61 private long mInitialTimeMillis; 62 private boolean mShowEventDetailsWithAgenda; 63 private CalendarController mController; 64 private EventInfoFragment mEventFragment; 65 private String mQuery; 66 private boolean mUsedForSearch = false; 67 private boolean mIsTabletConfig; 68 private EventInfo mOnAttachedInfo = null; 69 private boolean mOnAttachAllDay = false; 70 71 // Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action 72 // bar. 73 int mJulianDayOnTop = -1; 74 75 private Runnable mTZUpdater = new Runnable() { 76 @Override 77 public void run() { 78 mTimeZone = Utils.getTimeZone(getActivity(), this); 79 mTime.switchTimezone(mTimeZone); 80 } 81 }; 82 AgendaFragment()83 public AgendaFragment() { 84 this(0, false); 85 } 86 87 88 // timeMillis - time of first event to show 89 // usedForSearch - indicates if this fragment is used in the search fragment AgendaFragment(long timeMillis, boolean usedForSearch)90 public AgendaFragment(long timeMillis, boolean usedForSearch) { 91 mInitialTimeMillis = timeMillis; 92 mTime = new Time(); 93 if (mInitialTimeMillis == 0) { 94 mTime.setToNow(); 95 } else { 96 mTime.set(mInitialTimeMillis); 97 } 98 mUsedForSearch = usedForSearch; 99 } 100 101 @Override onAttach(Activity activity)102 public void onAttach(Activity activity) { 103 super.onAttach(activity); 104 mTimeZone = Utils.getTimeZone(activity, mTZUpdater); 105 mTime.switchTimezone(mTimeZone); 106 mActivity = activity; 107 if (mOnAttachedInfo != null) { 108 showEventInfo(mOnAttachedInfo, mOnAttachAllDay); 109 mOnAttachedInfo = null; 110 } 111 } 112 113 @Override onCreate(Bundle icicle)114 public void onCreate(Bundle icicle) { 115 super.onCreate(icicle); 116 mController = CalendarController.getInstance(mActivity); 117 mShowEventDetailsWithAgenda = 118 Utils.getConfigBool(mActivity, R.bool.show_event_details_with_agenda); 119 mIsTabletConfig = 120 Utils.getConfigBool(mActivity, R.bool.tablet_config); 121 if (icicle != null) { 122 long prevTime = icicle.getLong(BUNDLE_KEY_RESTORE_TIME, -1); 123 if (prevTime != -1) { 124 mTime.set(prevTime); 125 if (DEBUG) { 126 Log.d(TAG, "Restoring time to " + mTime.toString()); 127 } 128 } 129 } 130 } 131 132 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)133 public View onCreateView(LayoutInflater inflater, ViewGroup container, 134 Bundle savedInstanceState) { 135 136 137 View v = inflater.inflate(R.layout.agenda_fragment, null); 138 139 mAgendaListView = (AgendaListView)v.findViewById(R.id.agenda_events_list); 140 mAgendaListView.setClickable(true); 141 142 if (savedInstanceState != null) { 143 long instanceId = savedInstanceState.getLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, -1); 144 if (instanceId != -1) { 145 mAgendaListView.setSelectedInstanceId(instanceId); 146 } 147 } 148 149 if (!mShowEventDetailsWithAgenda) { 150 v.findViewById(R.id.agenda_event_info).setVisibility(View.GONE); 151 } 152 153 // Set adapter & HeaderIndexer for StickyHeaderListView 154 StickyHeaderListView lv = 155 (StickyHeaderListView)v.findViewById(R.id.agenda_sticky_header_list); 156 if (lv != null) { 157 Adapter a = mAgendaListView.getAdapter(); 158 lv.setAdapter(a); 159 if (a instanceof HeaderViewListAdapter) { 160 lv.setIndexer((HeaderIndexer) ((HeaderViewListAdapter)a).getWrappedAdapter()); 161 } else if (a instanceof AgendaWindowAdapter) { 162 lv.setIndexer((HeaderIndexer) a); 163 } else { 164 Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView"); 165 } 166 167 // Set scroll listener so that the date on the ActionBar can be set while 168 // the user scrolls the view 169 lv.setOnScrollListener(this); 170 } 171 return v; 172 } 173 174 @Override onResume()175 public void onResume() { 176 super.onResume(); 177 if (DEBUG) { 178 Log.v(TAG, "OnResume to " + mTime.toString()); 179 } 180 181 SharedPreferences prefs = GeneralPreferences.getSharedPreferences( 182 getActivity()); 183 boolean hideDeclined = prefs.getBoolean( 184 GeneralPreferences.KEY_HIDE_DECLINED, false); 185 186 mAgendaListView.setHideDeclinedEvents(hideDeclined); 187 mAgendaListView.goTo(mTime, -1, mQuery, true); 188 mAgendaListView.onResume(); 189 190 // // Register for Intent broadcasts 191 // IntentFilter filter = new IntentFilter(); 192 // filter.addAction(Intent.ACTION_TIME_CHANGED); 193 // filter.addAction(Intent.ACTION_DATE_CHANGED); 194 // filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 195 // registerReceiver(mIntentReceiver, filter); 196 // 197 // mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver); 198 } 199 200 @Override onSaveInstanceState(Bundle outState)201 public void onSaveInstanceState(Bundle outState) { 202 super.onSaveInstanceState(outState); 203 if (mAgendaListView == null) { 204 return; 205 } 206 long firstVisibleTime = mController.getTime(); 207 if (firstVisibleTime > 0) { 208 mTime.set(firstVisibleTime); 209 outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime); 210 if (DEBUG) { 211 Log.v(TAG, "onSaveInstanceState " + mTime.toString()); 212 } 213 } 214 215 long selectedInstance = mAgendaListView.getSelectedInstanceId(); 216 if (selectedInstance >= 0) { 217 outState.putLong(BUNDLE_KEY_RESTORE_INSTANCE_ID, selectedInstance); 218 } 219 } 220 221 /** 222 * This cleans up the event info fragment since the FragmentManager doesn't 223 * handle nested fragments. Without this, the action bar buttons added by 224 * the info fragment can come back on a rotation. 225 * 226 * @param fragmentManager 227 */ removeFragments(FragmentManager fragmentManager)228 public void removeFragments(FragmentManager fragmentManager) { 229 mController.deregisterEventHandler(R.id.agenda_event_info); 230 if (getActivity().isFinishing()) { 231 return; 232 } 233 FragmentTransaction ft = fragmentManager.beginTransaction(); 234 Fragment f = fragmentManager.findFragmentById(R.id.agenda_event_info); 235 if (f != null) { 236 ft.remove(f); 237 } 238 ft.commit(); 239 } 240 241 @Override onPause()242 public void onPause() { 243 super.onPause(); 244 245 mAgendaListView.onPause(); 246 // mContentResolver.unregisterContentObserver(mObserver); 247 // unregisterReceiver(mIntentReceiver); 248 249 // Record Agenda View as the (new) default detailed view. 250 // Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID); 251 } 252 goTo(EventInfo event, boolean animate)253 private void goTo(EventInfo event, boolean animate) { 254 if (mAgendaListView == null) { 255 // The view hasn't been set yet. Just save the time and use it 256 // later. 257 mTime.set(event.startTime); 258 return; 259 } 260 // Set mTime if we have a start time and we aren't in the range of the 261 // goto 262 if (event.startTime != null 263 && (mTime.before(event.startTime) || event.endTime == null || mTime 264 .after(event.endTime))) { 265 mTime.set(event.startTime); 266 } 267 mAgendaListView.goTo(mTime, event.id, mQuery, false); 268 AgendaAdapter.ViewHolder vh = mAgendaListView.getSelectedViewHolder(); 269 showEventInfo(event, vh != null ? vh.allDay : false); 270 } 271 search(String query, Time time)272 private void search(String query, Time time) { 273 mQuery = query; 274 if (time != null) { 275 mTime.set(time); 276 } 277 if (mAgendaListView == null) { 278 // The view hasn't been set yet. Just return. 279 return; 280 } 281 mAgendaListView.goTo(time, -1, mQuery, true); 282 } 283 284 @Override eventsChanged()285 public void eventsChanged() { 286 if (mAgendaListView != null) { 287 mAgendaListView.refresh(true); 288 } 289 } 290 291 @Override getSupportedEventTypes()292 public long getSupportedEventTypes() { 293 return EventType.GO_TO | EventType.EVENTS_CHANGED | ((mUsedForSearch)?EventType.SEARCH:0); 294 } 295 296 @Override handleEvent(EventInfo event)297 public void handleEvent(EventInfo event) { 298 if (event.eventType == EventType.GO_TO) { 299 // TODO support a range of time 300 // TODO support event_id 301 // TODO figure out the animate bit 302 goTo(event, true); 303 } else if (event.eventType == EventType.SEARCH) { 304 search(event.query, event.startTime); 305 } else if (event.eventType == EventType.EVENTS_CHANGED) { 306 eventsChanged(); 307 } 308 } 309 310 311 // Shows the selected event in the Agenda view showEventInfo(EventInfo event, boolean allDay)312 private void showEventInfo(EventInfo event, boolean allDay) { 313 314 // Ignore unknown events 315 if (event.id == -1) { 316 Log.e(TAG, "showEventInfo, event ID = " + event.id); 317 return; 318 } 319 320 // Create a fragment to show the event to the side of the agenda list 321 if (mShowEventDetailsWithAgenda) { 322 FragmentManager fragmentManager = getFragmentManager(); 323 if (fragmentManager == null) { 324 // Got a goto event before the fragment finished attaching, 325 // stash the event and handle it later. 326 mOnAttachedInfo = event; 327 mOnAttachAllDay = allDay; 328 return; 329 } 330 FragmentTransaction ft = fragmentManager.beginTransaction(); 331 int response = CalendarController.ATTENDEE_NO_RESPONSE; 332 if (event.eventType == EventType.VIEW_EVENT 333 || event.eventType == EventType.EDIT_EVENT) { 334 response = (int) event.extraLong; 335 } 336 337 if (allDay) { 338 event.startTime.timezone = Time.TIMEZONE_UTC; 339 event.endTime.timezone = Time.TIMEZONE_UTC; 340 } 341 342 mEventFragment = new EventInfoFragment(mActivity, event.id, 343 event.startTime.toMillis(true), event.endTime.toMillis(true), 344 response, false); 345 ft.replace(R.id.agenda_event_info, mEventFragment); 346 mController.registerEventHandler(R.id.agenda_event_info, 347 mEventFragment); 348 ft.commit(); 349 } 350 // else { 351 // Intent intent = new Intent(Intent.ACTION_VIEW); 352 // Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id); 353 // intent.setData(eventUri); 354 // intent.setClass(getActivity(), AllInOneActivity.class); 355 // intent.putExtra(EVENT_BEGIN_TIME, event.startTime != null ? event.startTime 356 // .toMillis(true) : -1); 357 // intent.putExtra(EVENT_END_TIME, event.endTime != null ? event.endTime.toMillis(true) 358 // : -1); 359 // startActivity(intent); 360 // } 361 } 362 363 // OnScrollListener implementation to update the date on the pull-down menu of the app 364 onScrollStateChanged(AbsListView view, int scrollState)365 public void onScrollStateChanged(AbsListView view, int scrollState) { 366 // Do nothing 367 } 368 369 // Gets the time of the first visible view. If it is a new time, send a message to update 370 // the time on the ActionBar 371 @Override onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)372 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 373 int totalItemCount) { 374 int julianDay = mAgendaListView.getJulianDayFromPosition(firstVisibleItem 375 - mAgendaListView.getHeaderViewsCount()); 376 // On error - leave the old view 377 if (julianDay == 0) { 378 return; 379 } 380 // If the day changed, update the ActionBar 381 if (mJulianDayOnTop != julianDay) { 382 mJulianDayOnTop = julianDay; 383 Time t = new Time(mTimeZone); 384 t.setJulianDay(mJulianDayOnTop); 385 mController.setTime(t.toMillis(true)); 386 // Cannot sent a message that eventually may change the layout of the views 387 // so instead post a runnable that will run when the layout is done 388 if (!mIsTabletConfig) { 389 view.post(new Runnable() { 390 @Override 391 public void run() { 392 Time t = new Time(mTimeZone); 393 t.setJulianDay(mJulianDayOnTop); 394 mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, null, -1, 395 ViewType.CURRENT, 0, null, null); 396 } 397 }); 398 } 399 } 400 } 401 } 402