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