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