AgendaListView.java revision cf31ab351c9becad6785d4d77eefc934f30b92e3
1/* 2 * Copyright (C) 2009 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 17package com.android.calendar.agenda; 18 19import com.android.calendar.CalendarController; 20import com.android.calendar.CalendarController.EventType; 21import com.android.calendar.DeleteEventHelper; 22import com.android.calendar.R; 23import com.android.calendar.Utils; 24import com.android.calendar.agenda.AgendaAdapter.ViewHolder; 25import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; 26import com.android.calendar.agenda.AgendaWindowAdapter.AgendaItem; 27 28import android.content.Context; 29import android.graphics.Rect; 30import android.os.Handler; 31import android.provider.CalendarContract.Attendees; 32import android.text.format.Time; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.view.View; 36import android.widget.AdapterView; 37import android.widget.AdapterView.OnItemClickListener; 38import android.widget.ListView; 39import android.widget.TextView; 40 41public class AgendaListView extends ListView implements OnItemClickListener { 42 43 private static final String TAG = "AgendaListView"; 44 private static final boolean DEBUG = false; 45 private static final int EVENT_UPDATE_TIME = 300000; // 5 minutes 46 47 private AgendaWindowAdapter mWindowAdapter; 48 private DeleteEventHelper mDeleteEventHelper; 49 private Context mContext; 50 private String mTimeZone; 51 private Time mTime; 52 private boolean mShowEventDetailsWithAgenda; 53 private Handler mHandler = null; 54 55 private final Runnable mTZUpdater = new Runnable() { 56 @Override 57 public void run() { 58 mTimeZone = Utils.getTimeZone(mContext, this); 59 mTime.switchTimezone(mTimeZone); 60 } 61 }; 62 63 // runs every midnight and refreshes the view in order to update the past/present 64 // separator 65 private final Runnable mMidnightUpdater = new Runnable() { 66 @Override 67 public void run() { 68 refresh(true); 69 Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone); 70 } 71 }; 72 73 // Runs every EVENT_UPDATE_TIME to gray out past events 74 private final Runnable mPastEventUpdater = new Runnable() { 75 @Override 76 public void run() { 77 if (updatePastEvents() == true) { 78 refresh(true); 79 } 80 setPastEventsUpdater(); 81 } 82 }; 83 84 public AgendaListView(Context context, AttributeSet attrs) { 85 super(context, attrs); 86 initView(context); 87 } 88 89 private void initView(Context context) { 90 mContext = context; 91 mTimeZone = Utils.getTimeZone(context, mTZUpdater); 92 mTime = new Time(mTimeZone); 93 setOnItemClickListener(this); 94 setVerticalScrollBarEnabled(false); 95 mWindowAdapter = new AgendaWindowAdapter(context, this, 96 Utils.getConfigBool(context, R.bool.show_event_details_with_agenda)); 97 mWindowAdapter.setSelectedInstanceId(-1/* TODO:instanceId */); 98 setAdapter(mWindowAdapter); 99 setCacheColorHint(context.getResources().getColor(R.color.agenda_item_not_selected)); 100 mDeleteEventHelper = 101 new DeleteEventHelper(context, null, false /* don't exit when done */); 102 mShowEventDetailsWithAgenda = Utils.getConfigBool(mContext, 103 R.bool.show_event_details_with_agenda); 104 // Hide ListView dividers, they are done in the item views themselves 105 setDivider(null); 106 setDividerHeight(0); 107 108 mHandler = new Handler(); 109 } 110 111 // Sets a thread to run every EVENT_UPDATE_TIME in order to update the list 112 // with grayed out past events 113 private void setPastEventsUpdater() { 114 115 // Run the thread in the nearest rounded EVENT_UPDATE_TIME 116 long now = System.currentTimeMillis(); 117 long roundedTime = (now / EVENT_UPDATE_TIME) * EVENT_UPDATE_TIME; 118 mHandler.removeCallbacks(mPastEventUpdater); 119 mHandler.postDelayed(mPastEventUpdater, EVENT_UPDATE_TIME - (now - roundedTime)); 120 } 121 122 // Stop the past events thread 123 private void resetPastEventsUpdater() { 124 mHandler.removeCallbacks(mPastEventUpdater); 125 } 126 127 // Go over all visible views and checks if all past events are grayed out. 128 // Returns true is there is at least one event that ended and it is not 129 // grayed out. 130 private boolean updatePastEvents() { 131 132 int childCount = getChildCount(); 133 boolean needUpdate = false; 134 long now = System.currentTimeMillis(); 135 Time time = new Time(mTimeZone); 136 time.set(now); 137 int todayJulianDay = Time.getJulianDay(now, time.gmtoff); 138 139 // Go over views in list 140 for (int i = 0; i < childCount; ++i) { 141 View listItem = getChildAt(i); 142 Object o = listItem.getTag(); 143 if (o instanceof AgendaByDayAdapter.ViewHolder) { 144 // day view - check if day in the past and not grayed yet 145 AgendaByDayAdapter.ViewHolder holder = (AgendaByDayAdapter.ViewHolder) o; 146 if (holder.julianDay <= todayJulianDay && !holder.grayed) { 147 needUpdate = true; 148 break; 149 } 150 } else if (o instanceof AgendaAdapter.ViewHolder) { 151 // meeting view - check if event in the past or started already and not grayed yet 152 // All day meetings for a day are grayed out 153 AgendaAdapter.ViewHolder holder = (AgendaAdapter.ViewHolder) o; 154 if (!holder.grayed && ((!holder.allDay && holder.startTimeMilli <= now) || 155 (holder.allDay && holder.julianDay <= todayJulianDay))) { 156 needUpdate = true; 157 break; 158 } 159 } 160 } 161 return needUpdate; 162 } 163 164 @Override 165 protected void onDetachedFromWindow() { 166 super.onDetachedFromWindow(); 167 mWindowAdapter.close(); 168 } 169 170 // Implementation of the interface OnItemClickListener 171 @Override 172 public void onItemClick(AdapterView<?> a, View v, int position, long id) { 173 if (id != -1) { 174 // Switch to the EventInfo view 175 AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position); 176 long oldInstanceId = mWindowAdapter.getSelectedInstanceId(); 177 mWindowAdapter.setSelectedView(v); 178 179 // If events are shown to the side of the agenda list , do nothing 180 // when the same event is selected , otherwise show the selected event. 181 182 if (item != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() || 183 !mShowEventDetailsWithAgenda)) { 184 long startTime = item.begin; 185 long endTime = item.end; 186 // Holder in view holds the start of the specific part of a multi-day event , 187 // use it for the goto 188 long holderStartTime; 189 Object holder = v.getTag(); 190 if (holder instanceof AgendaAdapter.ViewHolder) { 191 holderStartTime = ((AgendaAdapter.ViewHolder) holder).startTimeMilli; 192 } else { 193 holderStartTime = startTime; 194 } 195 mTime.set(startTime); 196 CalendarController controller = CalendarController.getInstance(mContext); 197 controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id, 198 startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong( 199 Attendees.ATTENDEE_STATUS_NONE, item.allDay), holderStartTime); 200 } 201 } 202 } 203 204 public void goTo(Time time, long id, String searchQuery, boolean forced, 205 boolean refreshEventInfo) { 206 if (time == null) { 207 time = mTime; 208 long goToTime = getFirstVisibleTime(null); 209 if (goToTime <= 0) { 210 goToTime = System.currentTimeMillis(); 211 } 212 time.set(goToTime); 213 } 214 mTime.set(time); 215 mTime.switchTimezone(mTimeZone); 216 mTime.normalize(true); 217 if (DEBUG) { 218 Log.d(TAG, "Goto with time " + mTime.toString()); 219 } 220 mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo); 221 } 222 223 public void refresh(boolean forced) { 224 mWindowAdapter.refresh(mTime, -1, null, forced, false); 225 } 226 227 public void deleteSelectedAgendaItem() { 228 int position = getSelectedItemPosition(); 229 AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(position); 230 if (agendaItem != null) { 231 mDeleteEventHelper.delete(agendaItem.begin, agendaItem.end, agendaItem.id, -1); 232 } 233 } 234 235 public View getFirstVisibleView() { 236 Rect r = new Rect(); 237 int childCount = getChildCount(); 238 for (int i = 0; i < childCount; ++i) { 239 View listItem = getChildAt(i); 240 listItem.getLocalVisibleRect(r); 241 if (r.top >= 0) { // if visible 242 return listItem; 243 } 244 } 245 return null; 246 } 247 248 public long getSelectedTime() { 249 int position = getSelectedItemPosition(); 250 if (position >= 0) { 251 AgendaItem item = mWindowAdapter.getAgendaItemByPosition(position); 252 if (item != null) { 253 return item.begin; 254 } 255 } 256 return getFirstVisibleTime(null); 257 } 258 259 public AgendaAdapter.ViewHolder getSelectedViewHolder() { 260 return mWindowAdapter.getSelectedViewHolder(); 261 } 262 263 public long getFirstVisibleTime(AgendaItem item) { 264 AgendaItem agendaItem = item; 265 if (item == null) { 266 agendaItem = getFirstVisibleAgendaItem(); 267 } 268 if (agendaItem != null) { 269 Time t = new Time(mTimeZone); 270 t.set(agendaItem.begin); 271 // Save and restore the time since setJulianDay sets the time to 00:00:00 272 int hour = t.hour; 273 int minute = t.minute; 274 int second = t.second; 275 t.setJulianDay(agendaItem.startDay); 276 t.hour = hour; 277 t.minute = minute; 278 t.second = second; 279 if (DEBUG) { 280 t.normalize(true); 281 Log.d(TAG, "first position had time " + t.toString()); 282 } 283 return t.normalize(false); 284 } 285 return 0; 286 } 287 288 public AgendaItem getFirstVisibleAgendaItem() { 289 int position = getFirstVisiblePosition(); 290 if (DEBUG) { 291 Log.v(TAG, "getFirstVisiblePosition = " + position); 292 } 293 294 // mShowEventDetailsWithAgenda == true implies we have a sticky header. In that case 295 // we may need to take the second visible position, since the first one maybe the one 296 // under the sticky header. 297 if (mShowEventDetailsWithAgenda) { 298 View v = getFirstVisibleView (); 299 if (v != null) { 300 Rect r = new Rect (); 301 v.getLocalVisibleRect(r); 302 if (r.bottom - r.top <= mWindowAdapter.getStickyHeaderHeight()) { 303 position ++; 304 } 305 } 306 } 307 308 return mWindowAdapter.getAgendaItemByPosition(position, 309 false /* startDay = date separator date instead of actual event startday */); 310 311 } 312 313 public int getJulianDayFromPosition(int position) { 314 DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position); 315 if (info != null) { 316 return info.dayAdapter.findJulianDayFromPosition(position - info.offset); 317 } 318 return 0; 319 } 320 321 // Finds is a specific event (defined by start time and id) is visible 322 public boolean isAgendaItemVisible(Time startTime, long id) { 323 324 if (id == -1 || startTime == null) { 325 return false; 326 } 327 328 View child = getChildAt(0); 329 // View not set yet, so not child - return 330 if (child == null) { 331 return false; 332 } 333 334 int start = getPositionForView(child); 335 long milliTime = startTime.toMillis(true); 336 int childCount = getChildCount(); 337 int eventsInAdapter = mWindowAdapter.getCount(); 338 339 if (DEBUG) { 340 Log.d(TAG,"id: " + id + ", milliTime (after): " + milliTime); 341 } 342 for (int i = 0; i < childCount; i++) { 343 if (i + start >= eventsInAdapter) { 344 break; 345 } 346 AgendaItem agendaItem = mWindowAdapter.getAgendaItemByPosition(i + start); 347 if (agendaItem == null) { 348 continue; 349 } 350 351 // If all-day event, it will be in UTC, so convert it to local time for comparison. 352 long begin = agendaItem.begin; 353 if (agendaItem.allDay) { 354 begin = Utils.convertAlldayUtcToLocal(null, begin, mTimeZone); 355 } 356 357 if (DEBUG) { 358 Log.d(TAG," id: " + agendaItem.id + ", startTime: " + agendaItem.begin); 359 } 360 if (agendaItem.id == id && begin == milliTime) { 361 View listItem = getChildAt(i); 362 if (listItem.getTop() <= getHeight() && 363 listItem.getTop() >= mWindowAdapter.getStickyHeaderHeight()) { 364 if (DEBUG) { 365 Log.d(TAG, "MATCH"); 366 } 367 return true; 368 } 369 } 370 } 371 if (DEBUG) { 372 Log.d(TAG, "NO MATCH"); 373 } 374 return false; 375 } 376 377 public long getSelectedInstanceId() { 378 return mWindowAdapter.getSelectedInstanceId(); 379 } 380 381 public void setSelectedInstanceId(long id) { 382 mWindowAdapter.setSelectedInstanceId(id); 383 } 384 385 // Move the currently selected or visible focus down by offset amount. 386 // offset could be negative. 387 public void shiftSelection(int offset) { 388 shiftPosition(offset); 389 int position = getSelectedItemPosition(); 390 if (position != INVALID_POSITION) { 391 setSelectionFromTop(position + offset, 0); 392 } 393 } 394 395 private void shiftPosition(int offset) { 396 if (DEBUG) { 397 Log.v(TAG, "Shifting position " + offset); 398 } 399 400 View firstVisibleItem = getFirstVisibleView(); 401 402 if (firstVisibleItem != null) { 403 Rect r = new Rect(); 404 firstVisibleItem.getLocalVisibleRect(r); 405 // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is 406 // returning an item above the first visible item. 407 int position = getPositionForView(firstVisibleItem); 408 setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); 409 if (DEBUG) { 410 if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { 411 ViewHolder viewHolder = (AgendaAdapter.ViewHolder) firstVisibleItem.getTag(); 412 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " 413 + viewHolder.title.getText()); 414 } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { 415 AgendaByDayAdapter.ViewHolder viewHolder = 416 (AgendaByDayAdapter.ViewHolder) firstVisibleItem.getTag(); 417 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " 418 + viewHolder.dateView.getText()); 419 } else if (firstVisibleItem instanceof TextView) { 420 Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); 421 } 422 } 423 } else if (getSelectedItemPosition() >= 0) { 424 if (DEBUG) { 425 Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + 426 " by " + offset); 427 } 428 setSelection(getSelectedItemPosition() + offset); 429 } 430 } 431 432 public void setHideDeclinedEvents(boolean hideDeclined) { 433 mWindowAdapter.setHideDeclinedEvents(hideDeclined); 434 } 435 436 public void onResume() { 437 mTZUpdater.run(); 438 Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone); 439 setPastEventsUpdater(); 440 mWindowAdapter.onResume(); 441 } 442 443 public void onPause() { 444 Utils.resetMidnightUpdater(mHandler, mMidnightUpdater); 445 resetPastEventsUpdater(); 446 } 447} 448