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