AgendaListView.java revision 80369fe0d18311277cd0ae6472bad7073b9879a3
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 45 private AgendaWindowAdapter mWindowAdapter; 46 private DeleteEventHelper mDeleteEventHelper; 47 private Context mContext; 48 private String mTimeZone; 49 private boolean mShowEventDetailsWithAgenda; 50 // Used to update the past/present separator at midnight 51 private Handler mMidnightUpdate = null;; 52 53 private Runnable mTZUpdater = new Runnable() { 54 @Override 55 public void run() { 56 mTimeZone = Utils.getTimeZone(mContext, this); 57 } 58 }; 59 60 private Runnable mMidnightUpdater = new Runnable() { 61 @Override 62 public void run() { 63 refresh(true); 64 setMidnightUpdater(); 65 } 66 }; 67 68 69 public AgendaListView(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 initView(context); 72 } 73 74 private void initView(Context context) { 75 mContext = context; 76 mTimeZone = Utils.getTimeZone(context, mTZUpdater); 77 setOnItemClickListener(this); 78 setChoiceMode(ListView.CHOICE_MODE_SINGLE); 79 setVerticalScrollBarEnabled(false); 80 mWindowAdapter = new AgendaWindowAdapter(context, this, 81 Utils.getConfigBool(context, R.bool.show_event_details_with_agenda)); 82 mWindowAdapter.setSelectedInstanceId(-1/* TODO:instanceId */); 83 setAdapter(mWindowAdapter); 84 setCacheColorHint(context.getResources().getColor(R.color.agenda_item_not_selected)); 85 mDeleteEventHelper = 86 new DeleteEventHelper(context, null, false /* don't exit when done */); 87 mShowEventDetailsWithAgenda = Utils.getConfigBool(mContext, 88 R.bool.show_event_details_with_agenda); 89 // Hide ListView dividers, they are done in the item views themselves 90 setDivider(null); 91 setDividerHeight(0); 92 93 setMidnightUpdater(); 94 } 95 96 97 // Sets a thread to run one second after midnight and refresh the list view 98 // causing the separator between past/present to be updated. 99 private void setMidnightUpdater() { 100 101 // Create the handler or clear the existing one. 102 if (mMidnightUpdate == null) { 103 mMidnightUpdate = new Handler(); 104 } else { 105 mMidnightUpdate.removeCallbacks(mMidnightUpdater); 106 } 107 108 // Calculate the time until midnight + 1 second and set the handler to 109 // do a refresh 110 // at that time. 111 112 long now = System.currentTimeMillis(); 113 Time time = new Time(mTimeZone); 114 time.set(now); 115 long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 - 116 time.second + 1) * 1000; 117 mMidnightUpdate.postDelayed(mMidnightUpdater, runInMillis); 118 } 119 120 // Stop the midnight update thread 121 private void resetMidnightUpdater() { 122 if (mMidnightUpdate != null) { 123 mMidnightUpdate.removeCallbacks(mMidnightUpdater); 124 } 125 } 126 127 @Override 128 protected void onDetachedFromWindow() { 129 super.onDetachedFromWindow(); 130 mWindowAdapter.close(); 131 } 132 133 // Implementation of the interface OnItemClickListener 134 @Override 135 public void onItemClick(AdapterView<?> a, View v, int position, long id) { 136 if (id != -1) { 137 // Switch to the EventInfo view 138 EventInfo event = mWindowAdapter.getEventByPosition(position); 139 long oldInstanceId = mWindowAdapter.getSelectedInstanceId(); 140 mWindowAdapter.setSelectedView(v); 141 142 // If events are shown to the side of the agenda list , do nothing 143 // when the same 144 // event is selected , otherwise show the selected event. 145 146 if (event != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() || 147 !mShowEventDetailsWithAgenda)) { 148 CalendarController.getInstance(mContext).sendEventRelatedEvent(this, 149 EventType.VIEW_EVENT, event.id, event.begin, event.end, 0, 0, -1); 150 } 151 } 152 } 153 154 public void goTo(Time time, long id, String searchQuery, boolean forced) { 155 if (time == null) { 156 time = new Time(mTimeZone); 157 long goToTime = getFirstVisibleTime(); 158 if (goToTime <= 0) { 159 goToTime = System.currentTimeMillis(); 160 } 161 time.set(goToTime); 162 } 163 mWindowAdapter.refresh(time, id, searchQuery, forced); 164 } 165 166 public void refresh(boolean forced) { 167 Time time = new Time(Utils.getTimeZone(mContext, null)); 168 long goToTime = getFirstVisibleTime(); 169 if (goToTime <= 0) { 170 goToTime = System.currentTimeMillis(); 171 } 172 time.set(goToTime); 173 mWindowAdapter.refresh(time, -1, null, forced); 174 } 175 176 public void deleteSelectedEvent() { 177 int position = getSelectedItemPosition(); 178 EventInfo event = mWindowAdapter.getEventByPosition(position); 179 if (event != null) { 180 mDeleteEventHelper.delete(event.begin, event.end, event.id, -1); 181 } 182 } 183 184 @Override 185 public int getFirstVisiblePosition() { 186 // TODO File bug! 187 // getFirstVisiblePosition doesn't always return the first visible 188 // item. Sometimes, it is above the visible one. 189 // instead. I loop through the viewgroup children and find the first 190 // visible one. BTW, getFirstVisiblePosition() == getChildAt(0). I 191 // am not looping through the entire list. 192 View v = getFirstVisibleView(); 193 if (v != null) { 194 if (DEBUG) { 195 Log.v(TAG, "getFirstVisiblePosition: " + AgendaWindowAdapter.getViewTitle(v)); 196 } 197 return getPositionForView(v); 198 } 199 return -1; 200 } 201 202 public View getFirstVisibleView() { 203 Rect r = new Rect(); 204 int childCount = getChildCount(); 205 for (int i = 0; i < childCount; ++i) { 206 View listItem = getChildAt(i); 207 listItem.getLocalVisibleRect(r); 208 if (r.top >= 0) { // if visible 209 return listItem; 210 } 211 } 212 return null; 213 } 214 215 public long getSelectedTime() { 216 int position = getSelectedItemPosition(); 217 if (position >= 0) { 218 EventInfo event = mWindowAdapter.getEventByPosition(position); 219 if (event != null) { 220 return event.begin; 221 } 222 } 223 return getFirstVisibleTime(); 224 } 225 226 public long getFirstVisibleTime() { 227 int position = getFirstVisiblePosition(); 228 if (DEBUG) { 229 Log.v(TAG, "getFirstVisiblePosition = " + position); 230 } 231 232 EventInfo event = mWindowAdapter.getEventByPosition(position); 233 if (event != null) { 234 Time t = new Time(mTimeZone); 235 t.set(event.begin); 236 t.setJulianDay(event.startDay); 237 return t.normalize(false); 238 } 239 return 0; 240 } 241 242 public int getJulianDayFromPosition(int position) { 243 DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position); 244 if (info != null) { 245 return info.dayAdapter.findJulianDayFromPosition(position - info.offset); 246 } 247 return 0; 248 } 249 250 // Finds is a specific event (defined by start time and id) is visible 251 public boolean isEventVisible(Time startTime, long id) { 252 253 if (id == -1 || startTime == null) { 254 return false; 255 } 256 257 View child = getChildAt(0); 258 // View not set yet, so not child - return 259 if (child == null) { 260 return false; 261 } 262 int start = getPositionForView(child); 263 long milliTime = startTime.toMillis(true); 264 int childCount = getChildCount(); 265 int eventsInAdapter = mWindowAdapter.getCount(); 266 267 for (int i = 0; i < childCount; i++) { 268 if (i + start >= eventsInAdapter) { 269 break; 270 } 271 EventInfo event = mWindowAdapter.getEventByPosition(i + start); 272 if (event == null) { 273 continue; 274 } 275 if (event.id == id && event.begin == milliTime) { 276 View listItem = getChildAt(i); 277 if (listItem.getBottom() <= getHeight() && 278 listItem.getTop() >= 0) { 279 return true; 280 } 281 } 282 } 283 return false; 284 } 285 286 public long getSelectedInstanceId() { 287 return mWindowAdapter.getSelectedInstanceId(); 288 } 289 290 // Move the currently selected or visible focus down by offset amount. 291 // offset could be negative. 292 public void shiftSelection(int offset) { 293 shiftPosition(offset); 294 int position = getSelectedItemPosition(); 295 if (position != INVALID_POSITION) { 296 setSelectionFromTop(position + offset, 0); 297 } 298 } 299 300 private void shiftPosition(int offset) { 301 if (DEBUG) { 302 Log.v(TAG, "Shifting position " + offset); 303 } 304 305 View firstVisibleItem = getFirstVisibleView(); 306 307 if (firstVisibleItem != null) { 308 Rect r = new Rect(); 309 firstVisibleItem.getLocalVisibleRect(r); 310 // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is 311 // returning an item above the first visible item. 312 int position = getPositionForView(firstVisibleItem); 313 setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top); 314 if (DEBUG) { 315 if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) { 316 ViewHolder viewHolder = (AgendaAdapter.ViewHolder) firstVisibleItem.getTag(); 317 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title " 318 + viewHolder.title.getText()); 319 } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) { 320 AgendaByDayAdapter.ViewHolder viewHolder = 321 (AgendaByDayAdapter.ViewHolder) firstVisibleItem.getTag(); 322 Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date " 323 + viewHolder.dateView.getText()); 324 } else if (firstVisibleItem instanceof TextView) { 325 Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition()); 326 } 327 } 328 } else if (getSelectedItemPosition() >= 0) { 329 if (DEBUG) { 330 Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() + 331 " by " + offset); 332 } 333 setSelection(getSelectedItemPosition() + offset); 334 } 335 } 336 337 public void setHideDeclinedEvents(boolean hideDeclined) { 338 mWindowAdapter.setHideDeclinedEvents(hideDeclined); 339 } 340 341 public void onResume() { 342 mTZUpdater.run(); 343 setMidnightUpdater(); 344 mWindowAdapter.onResume(); 345 } 346 347 public void onPause() { 348 resetMidnightUpdater(); 349 mWindowAdapter.notifyDataSetInvalidated(); 350 } 351} 352