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