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