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