EventLoader.java revision f143b0165d54c8be505ed4e7ff937b1fb4d6b77c
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.Calendar.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
89                //Set all the days with events to true
90                while (cursor.moveToNext()) {
91                    int day = cursor.getInt(startDayColumnIndex);
92                    int dayIndex = day - startDay;
93                    eventDays[dayIndex] = true;
94                }
95            } finally {
96                if (cursor != null) {
97                    cursor.close();
98                }
99            }
100            handler.post(uiCallback);
101        }
102
103        public void skipRequest(EventLoader eventLoader) {
104        }
105    }
106
107    private static class LoadEventsRequest implements LoadRequest {
108
109        public int id;
110        public long startMillis;
111        public int numDays;
112        public ArrayList<Event> events;
113        public Runnable successCallback;
114        public Runnable cancelCallback;
115
116        public LoadEventsRequest(int id, long startMillis, int numDays, ArrayList<Event> events,
117                final Runnable successCallback, final Runnable cancelCallback) {
118            this.id = id;
119            this.startMillis = startMillis;
120            this.numDays = numDays;
121            this.events = events;
122            this.successCallback = successCallback;
123            this.cancelCallback = cancelCallback;
124        }
125
126        public void processRequest(EventLoader eventLoader) {
127            Event.loadEvents(eventLoader.mContext, events, startMillis,
128                    numDays, id, eventLoader.mSequenceNumber);
129
130            // Check if we are still the most recent request.
131            if (id == eventLoader.mSequenceNumber.get()) {
132                eventLoader.mHandler.post(successCallback);
133            } else {
134                eventLoader.mHandler.post(cancelCallback);
135            }
136        }
137
138        public void skipRequest(EventLoader eventLoader) {
139            eventLoader.mHandler.post(cancelCallback);
140        }
141    }
142
143    private static class LoaderThread extends Thread {
144        LinkedBlockingQueue<LoadRequest> mQueue;
145        EventLoader mEventLoader;
146
147        public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) {
148            mQueue = queue;
149            mEventLoader = eventLoader;
150        }
151
152        public void shutdown() {
153            try {
154                mQueue.put(new ShutdownRequest());
155            } catch (InterruptedException ex) {
156                // The put() method fails with InterruptedException if the
157                // queue is full. This should never happen because the queue
158                // has no limit.
159                Log.e("Cal", "LoaderThread.shutdown() interrupted!");
160            }
161        }
162
163        @Override
164        public void run() {
165            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
166            while (true) {
167                try {
168                    // Wait for the next request
169                    LoadRequest request = mQueue.take();
170
171                    // If there are a bunch of requests already waiting, then
172                    // skip all but the most recent request.
173                    while (!mQueue.isEmpty()) {
174                        // Let the request know that it was skipped
175                        request.skipRequest(mEventLoader);
176
177                        // Skip to the next request
178                        request = mQueue.take();
179                    }
180
181                    if (request instanceof ShutdownRequest) {
182                        return;
183                    }
184                    request.processRequest(mEventLoader);
185                } catch (InterruptedException ex) {
186                    Log.e("Cal", "background LoaderThread interrupted!");
187                }
188            }
189        }
190    }
191
192    public EventLoader(Context context) {
193        mContext = context;
194        mLoaderQueue = new LinkedBlockingQueue<LoadRequest>();
195        mResolver = context.getContentResolver();
196    }
197
198    /**
199     * Call this from the activity's onResume()
200     */
201    public void startBackgroundThread() {
202        mLoaderThread = new LoaderThread(mLoaderQueue, this);
203        mLoaderThread.start();
204    }
205
206    /**
207     * Call this from the activity's onPause()
208     */
209    public void stopBackgroundThread() {
210        mLoaderThread.shutdown();
211    }
212
213    /**
214     * Loads "numDays" days worth of events, starting at start, into events.
215     * Posts uiCallback to the {@link Handler} for this view, which will run in the UI thread.
216     * Reuses an existing background thread, if events were already being loaded in the background.
217     * NOTE: events and uiCallback are not used if an existing background thread gets reused --
218     * the ones that were passed in on the call that results in the background thread getting
219     * created are used, and the most recent call's worth of data is loaded into events and posted
220     * via the uiCallback.
221     */
222    void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
223            long start, final Runnable successCallback, final Runnable cancelCallback) {
224
225        // Increment the sequence number for requests.  We don't care if the
226        // sequence numbers wrap around because we test for equality with the
227        // latest one.
228        int id = mSequenceNumber.incrementAndGet();
229
230        // Send the load request to the background thread
231        LoadEventsRequest request = new LoadEventsRequest(id, start, numDays,
232                events, successCallback, cancelCallback);
233
234        try {
235            mLoaderQueue.put(request);
236        } catch (InterruptedException ex) {
237            // The put() method fails with InterruptedException if the
238            // queue is full. This should never happen because the queue
239            // has no limit.
240            Log.e("Cal", "loadEventsInBackground() interrupted!");
241        }
242    }
243
244    /**
245     * Sends a request for the days with events to be marked. Loads "numDays"
246     * worth of days, starting at start, and fills in eventDays to express which
247     * days have events.
248     *
249     * @param startDay First day to check for events
250     * @param numDays Days following the start day to check
251     * @param eventDay Whether or not an event exists on that day
252     * @param uiCallback What to do when done (log data, redraw screen)
253     */
254    void loadEventDaysInBackground(int startDay, int numDays, boolean[] eventDays,
255        final Runnable uiCallback)
256    {
257        // Send load request to the background thread
258        LoadEventDaysRequest request = new LoadEventDaysRequest(startDay, numDays,
259                eventDays, uiCallback);
260        try {
261            mLoaderQueue.put(request);
262        } catch (InterruptedException ex) {
263            // The put() method fails with InterruptedException if the
264            // queue is full. This should never happen because the queue
265            // has no limit.
266            Log.e("Cal", "loadEventDaysInBackground() interrupted!");
267        }
268    }
269}
270