Home | History | Annotate | Download | only in calendar
      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