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.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