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