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 { 44 public void processRequest(EventLoader eventLoader); 45 public void skipRequest(EventLoader eventLoader); 46 } 47 48 private static class ShutdownRequest implements LoadRequest { 49 public void processRequest(EventLoader eventLoader) { 50 } 51 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 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 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 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 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 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 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 164 public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) { 165 mQueue = queue; 166 mEventLoader = eventLoader; 167 } 168 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 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 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 */ 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 */ 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 */ 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 */ 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