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.Calendar.EventDays; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.concurrent.LinkedBlockingQueue; 30 import java.util.concurrent.atomic.AtomicInteger; 31 32 public 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 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