AgendaListView.java revision 6bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1
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.EventInfo; 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 AgendaAdapter.ViewHolder) { 144 // meeting view - check if event in the past or started already and not grayed yet 145 // All day meetings for a day are grayed out 146 AgendaAdapter.ViewHolder holder = (AgendaAdapter.ViewHolder) o; 147 if (!holder.grayed && ((!holder.allDay && holder.startTimeMilli <= now) || 148 (holder.allDay && holder.julianDay <= todayJulianDay))) { 149 needUpdate = true; 150 break; 151 } 152 } 153 } 154 return needUpdate; 155 } 156 157 @Override 158 protected void onDetachedFromWindow() { 159 super.onDetachedFromWindow(); 160 mWindowAdapter.close(); 161 } 162 163 // Implementation of the interface OnItemClickListener 164 @Override 165 public void onItemClick(AdapterView<?> a, View v, int position, long id) { 166 if (id != -1) { 167 // Switch to the EventInfo view 168 EventInfo event = mWindowAdapter.getEventByPosition(position); 169 long oldInstanceId = mWindowAdapter.getSelectedInstanceId(); 170 mWindowAdapter.setSelectedView(v); 171 172 // If events are shown to the side of the agenda list , do nothing 173 // when the same 174 // event is selected , otherwise show the selected event. 175 176 if (event != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() || 177 !mShowEventDetailsWithAgenda)) { 178 long startTime = event.begin; 179 long endTime = event.end; 180 if (event.allDay) { 181 startTime = Utils.convertAlldayLocalToUTC(mTime, startTime, mTimeZone); 182 endTime = Utils.convertAlldayLocalToUTC(mTime, endTime, mTimeZone); 183 } 184 mTime.set(startTime); 185 CalendarController controller = CalendarController.getInstance(mContext); 186 controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, event.id, 187 startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong( 188 Attendees.ATTENDEE_STATUS_NONE, event.allDay), 189 controller.getTime()); 190 } 191 } 192 } 193 194 public void goTo(Time time, long id, String searchQuery, boolean forced, 195 boolean refreshEventInfo) { 196 if (time == null) { 197 time = mTime; 198 long goToTime = getFirstVisibleTime(); 199 if (goToTime <= 0) { 200 goToTime = System.currentTimeMillis(); 201 } 202 time.set(goToTime); 203 } 204 mTime.set(time); 205 mTime.switchTimezone(mTimeZone); 206 mTime.normalize(true); 207 if (DEBUG) { 208 Log.d(TAG, "Goto with time " + mTime.toString()); 209 } 210 mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo); 211 } 212 213 public void refresh(boolean forced) { 214 mWindowAdapter.refresh(mTime, -1, null, forced, false); 215 } 216 217 public void deleteSelectedEvent() { 218 int position = getSelectedItemPosition(); 219 EventInfo event = mWindowAdapter.getEventByPosition(position); 220 if (event != null) { 221 mDeleteEventHelper.delete(event.begin, event.end, event.id, -1); 222 } 223 } 224 225 public View getFirstVisibleView() { 226 Rect r = new Rect(); 227 int childCount = getChildCount(); 228 for (int i = 0; i < childCount; ++i) { 229 View listItem = getChildAt(i); 230 listItem.getLocalVisibleRect(r); 231 if (r.top >= 0) { // if visible 232 return listItem; 233 } 234 } 235 return null; 236 } 237 238 public long getSelectedTime() { 239 int position = getSelectedItemPosition(); 240 if (position >= 0) { 241 EventInfo event = mWindowAdapter.getEventByPosition(position); 242 if (event != null) { 243 return event.begin; 244 } 245 } 246 return getFirstVisibleTime(); 247 } 248 249 public AgendaAdapter.ViewHolder getSelectedViewHolder() { 250 return mWindowAdapter.getSelectedViewHolder(); 251 } 252 253 public long getFirstVisibleTime() { 254 int position = getFirstVisiblePosition(); 255 if (DEBUG) { 256 Log.v(TAG, "getFirstVisiblePosition = " + position); 257 } 258 259 // mShowEventDetailsWithAgenda == true implies we have a sticky header. In that case 260 // we may need to take the second visible position, since the first one maybe the one 261 // under the sticky header. 262 if (mShowEventDetailsWithAgenda) { 263 View v = getFirstVisibleView (); 264 if (v != null) { 265 Rect r = new Rect (); 266 v.getLocalVisibleRect(r); 267 if (r.bottom - r.top <= mWindowAdapter.getStickyHeaderHeight()) { 268 position ++; 269 } 270 } 271 } 272 273 EventInfo event = mWindowAdapter.getEventByPosition(position, 274 false /* startDay = date separator date instead of actual event startday */); 275 if (event != null) { 276 Time t = new Time(mTimeZone); 277 t.set(event.begin); 278 // Save and restore the time since setJulianDay sets the time to 00:00:00 279 int hour = t.hour; 280 int minute = t.minute; 281 int second = t.second; 282 t.setJulianDay(event.startDay); 283 t.hour = hour; 284 t.minute = minute; 285 t.second = second; 286 if (DEBUG) { 287 t.normalize(true); 288 Log.d(TAG, "position " + position + " had time " + t.toString()); 289 } 290 return t.normalize(false); 291 } 292 return 0; 293 } 294 295 public int getJulianDayFromPosition(int position) { 296 DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position); 297 if (info != null) { 298 return info.dayAdapter.findJulianDayFromPosition(position - info.offset); 299 } 300 return 0; 301 } 302 303 // Finds is a specific event (defined by start time and id) is visible 304 public boolean isEventVisible(Time startTime, long id) { 305 306 if (id == -1 || startTime == null) { 307 return false; 308 } 309 310 View child = getChildAt(0); 311 // View not set yet, so not child - return 312 if (child == null) { 313 return false; 314 } 315 int start = getPositionForView(child); 316 long milliTime = startTime.toMillis(true); 317 int childCount = getChildCount(); 318 int eventsInAdapter = mWindowAdapter.getCount(); 319 320 for (int i = 0; i < childCount; i++) { 321 if (i + start >= eventsInAdapter) { 322 break; 323 } 324 EventInfo event = mWindowAdapter.getEventByPosition(i + start); 325 if (event == null) { 326 continue; 327 } 328 if (event.id == id && event.begin == milliTime) { 329 View listItem = getChildAt(i); 330 if (listItem.getTop() <= getHeight() && 331 listItem.getTop() >= mWindowAdapter.getStickyHeaderHeight()) { 332 return true; 333 } 334 } 335 } 336 return false; 337 } 338 339 public long getSelectedInstanceId() { 340 return mWindowAdapter.getSelectedInstanceId(); 341 } 342 343 public void setSelectedInstanceId(long id) { 344 mWindowAdapter.setSelectedInstanceId(id); 345 } 346 347 // Move the currently selected or visible focus down by offset amount. 348 // offset could be negative. 349 public void shiftSelection(int offset) { 350 shiftPosition(offset); 351 int position = getSelectedItemPosition(); 352 if (position != INVALID_POSITION) { 353 setSelectionFromTop(position + offset, 0); 354 } 355 } 356 357 private void shiftPosition(int offset) { 358 if (DEBUG) { 359 Log.v(TAG, "Shifting position " + offset); 360 } 361 362 View firstVisibleItem = getFirstVisibleView(); 363 364 if (firstVisibleItem != null) { 365 Rect r = new Rect(); 366 firstVisibleItem.getLocalVisibleRect(r); 367 // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is 368 // returning an item above the first visible item. 369 int position = getPositionForView(firstVisibleItem); 370 setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); 371 if (DEBUG) { 372 if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { 373 ViewHolder viewHolder = (AgendaAdapter.ViewHolder) firstVisibleItem.getTag(); 374 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " 375 + viewHolder.title.getText()); 376 } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { 377 AgendaByDayAdapter.ViewHolder viewHolder = 378 (AgendaByDayAdapter.ViewHolder) firstVisibleItem.getTag(); 379 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " 380 + viewHolder.dateView.getText()); 381 } else if (firstVisibleItem instanceof TextView) { 382 Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); 383 } 384 } 385 } else if (getSelectedItemPosition() >= 0) { 386 if (DEBUG) { 387 Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + 388 " by " + offset); 389 } 390 setSelection(getSelectedItemPosition() + offset); 391 } 392 } 393 394 public void setHideDeclinedEvents(boolean hideDeclined) { 395 mWindowAdapter.setHideDeclinedEvents(hideDeclined); 396 } 397 398 public void onResume() { 399 mTZUpdater.run(); 400 Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone); 401 setPastEventsUpdater(); 402 mWindowAdapter.onResume(); 403 } 404 405 public void onPause() { 406 Utils.resetMidnightUpdater(mHandler, mMidnightUpdater); 407 resetPastEventsUpdater(); 408 } 409} 410