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