EventLoader.java revision a7c0390d9c5dd4ff730de505682687fae5f5ced0
1/*
2 * Copyright (C) 2008 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;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.Cursor;
22import android.os.Handler;
23import android.os.Process;
24import android.provider.CalendarContract.EventDays;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.concurrent.LinkedBlockingQueue;
30import java.util.concurrent.atomic.AtomicInteger;
31
32public class EventLoader {
33
34    private Context mContext;
35    private Handler mHandler = new Handler();
36    private AtomicInteger mSequenceNumber = new AtomicInteger();
37
38    private LinkedBlockingQueue<LoadRequest> mLoaderQueue;
39    private LoaderThread mLoaderThread;
40    private ContentResolver mResolver;
41
42    private static interface LoadRequest {
43        public void processRequest(EventLoader eventLoader);
44        public void skipRequest(EventLoader eventLoader);
45    }
46
47    private static class ShutdownRequest implements LoadRequest {
48        public void processRequest(EventLoader eventLoader) {
49        }
50
51        public void skipRequest(EventLoader eventLoader) {
52        }
53    }
54
55    /**
56     *
57     * Code for handling requests to get whether days have an event or not
58     * and filling in the eventDays array.
59     *
60     */
61    private static class LoadEventDaysRequest implements LoadRequest {
62        public int startDay;
63        public int numDays;
64        public boolean[] eventDays;
65        public Runnable uiCallback;
66
67        public LoadEventDaysRequest(int startDay, int numDays, boolean[] eventDays,
68                final Runnable uiCallback)
69        {
70            this.startDay = startDay;
71            this.numDays = numDays;
72            this.eventDays = eventDays;
73            this.uiCallback = uiCallback;
74        }
75
76        public void processRequest(EventLoader eventLoader)
77        {
78            final Handler handler = eventLoader.mHandler;
79            ContentResolver cr = eventLoader.mResolver;
80
81            // Clear the event days
82            Arrays.fill(eventDays, false);
83
84            //query which days have events
85            Cursor cursor = EventDays.query(cr, startDay, numDays);
86            try {
87                int startDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.STARTDAY);
88                int endDayColumnIndex = cursor.getColumnIndexOrThrow(EventDays.ENDDAY);
89
90                //Set all the days with events to true
91                while (cursor.moveToNext()) {
92                    int firstDay = cursor.getInt(startDayColumnIndex);
93                    int lastDay = cursor.getInt(endDayColumnIndex);
94                    //we want the entire range the event occurs, but only within the month
95                    int firstIndex = Math.max(firstDay - startDay, 0);
96                    int lastIndex = Math.min(lastDay - startDay, 30);
97
98                    for(int i = firstIndex; i <= lastIndex; i++) {
99                        eventDays[i] = true;
100                    }
101                }
102            } finally {
103                if (cursor != null) {
104                    cursor.close();
105                }
106            }
107            handler.post(uiCallback);
108        }
109
110        public void skipRequest(EventLoader eventLoader) {
111        }
112    }
113
114    private static class LoadEventsRequest implements LoadRequest {
115
116        public int id;
117        public long startMillis;
118        public int numDays;
119        public ArrayList<Event> events;
120        public Runnable successCallback;
121        public Runnable cancelCallback;
122
123        public LoadEventsRequest(int id, long startMillis, int numDays, ArrayList<Event> events,
124                final Runnable successCallback, final Runnable cancelCallback) {
125            this.id = id;
126            this.startMillis = startMillis;
127            this.numDays = numDays;
128            this.events = events;
129            this.successCallback = successCallback;
130            this.cancelCallback = cancelCallback;
131        }
132
133        public void processRequest(EventLoader eventLoader) {
134            Event.loadEvents(eventLoader.mContext, events, startMillis,
135                    numDays, id, eventLoader.mSequenceNumber);
136
137            // Check if we are still the most recent request.
138            if (id == eventLoader.mSequenceNumber.get()) {
139                eventLoader.mHandler.post(successCallback);
140            } else {
141                eventLoader.mHandler.post(cancelCallback);
142            }
143        }
144
145        public void skipRequest(EventLoader eventLoader) {
146            eventLoader.mHandler.post(cancelCallback);
147        }
148    }
149
150    private static class LoaderThread extends Thread {
151        LinkedBlockingQueue<LoadRequest> mQueue;
152        EventLoader mEventLoader;
153
154        public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) {
155            mQueue = queue;
156            mEventLoader = eventLoader;
157        }
158
159        public void shutdown() {
160            try {
161                mQueue.put(new ShutdownRequest());
162            } catch (InterruptedException ex) {
163                // The put() method fails with InterruptedException if the
164                // queue is full. This should never happen because the queue
165                // has no limit.
166                Log.e("Cal", "LoaderThread.shutdown() interrupted!");
167            }
168        }
169
170        @Override
171        public void run() {
172            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
173            while (true) {
174                try {
175                    // Wait for the next request
176                    LoadRequest request = mQueue.take();
177
178                    // If there are a bunch of requests already waiting, then
179                    // skip all but the most recent request.
180                    while (!mQueue.isEmpty()) {
181                        // Let the request know that it was skipped
182                        request.skipRequest(mEventLoader);
183
184                        // Skip to the next request
185                        request = mQueue.take();
186                    }
187
188                    if (request instanceof ShutdownRequest) {
189                        return;
190                    }
191                    request.processRequest(mEventLoader);
192                } catch (InterruptedException ex) {
193                    Log.e("Cal", "background LoaderThread interrupted!");
194                }
195            }
196        }
197    }
198
199    public EventLoader(Context context) {
200        mContext = context;
201        mLoaderQueue = new LinkedBlockingQueue<LoadRequest>();
202        mResolver = context.getContentResolver();
203    }
204
205    /**
206     * Call this from the activity's onResume()
207     */
208    public void startBackgroundThread() {
209        mLoaderThread = new LoaderThread(mLoaderQueue, this);
210        mLoaderThread.start();
211    }
212
213    /**
214     * Call this from the activity's onPause()
215     */
216    public void stopBackgroundThread() {
217        mLoaderThread.shutdown();
218    }
219
220    /**
221     * Loads "numDays" days worth of events, starting at start, into events.
222     * Posts uiCallback to the {@link Handler} for this view, which will run in the UI thread.
223     * Reuses an existing background thread, if events were already being loaded in the background.
224     * NOTE: events and uiCallback are not used if an existing background thread gets reused --
225     * the ones that were passed in on the call that results in the background thread getting
226     * created are used, and the most recent call's worth of data is loaded into events and posted
227     * via the uiCallback.
228     */
229    public void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
230            long start, final Runnable successCallback, final Runnable cancelCallback) {
231
232        // Increment the sequence number for requests.  We don't care if the
233        // sequence numbers wrap around because we test for equality with the
234        // latest one.
235        int id = mSequenceNumber.incrementAndGet();
236
237        // Send the load request to the background thread
238        LoadEventsRequest request = new LoadEventsRequest(id, start, numDays,
239                events, successCallback, cancelCallback);
240
241        try {
242            mLoaderQueue.put(request);
243        } catch (InterruptedException ex) {
244            // The put() method fails with InterruptedException if the
245            // queue is full. This should never happen because the queue
246            // has no limit.
247            Log.e("Cal", "loadEventsInBackground() interrupted!");
248        }
249    }
250
251    /**
252     * Sends a request for the days with events to be marked. Loads "numDays"
253     * worth of days, starting at start, and fills in eventDays to express which
254     * days have events.
255     *
256     * @param startDay First day to check for events
257     * @param numDays Days following the start day to check
258     * @param eventDay Whether or not an event exists on that day
259     * @param uiCallback What to do when done (log data, redraw screen)
260     */
261    void loadEventDaysInBackground(int startDay, int numDays, boolean[] eventDays,
262        final Runnable uiCallback)
263    {
264        // Send load request to the background thread
265        LoadEventDaysRequest request = new LoadEventDaysRequest(startDay, numDays,
266                eventDays, uiCallback);
267        try {
268            mLoaderQueue.put(request);
269        } catch (InterruptedException ex) {
270            // The put() method fails with InterruptedException if the
271            // queue is full. This should never happen because the queue
272            // has no limit.
273            Log.e("Cal", "loadEventDaysInBackground() interrupted!");
274        }
275    }
276}
277