package vandy.mooc.provider.cache; import java.util.ArrayList; import java.util.List; import vandy.mooc.provider.AcronymContract.AcronymEntry; import vandy.mooc.retrofit.AcronymData.AcronymExpansion; import android.app.AlarmManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.os.SystemClock; /** * Timeout cache that uses Content Providers to cache data and uses * AlarmManager and Broadcast Receiver to remove expired cache entries * periodically. */ public class ContentProviderTimeoutCache implements TimeoutCache<String, List<AcronymExpansion>> { /** * Cache is cleaned up twice a day to remove expired acronyms. */ public static final long CLEANUP_SCHEDULER_TIME_INTERVAL = AlarmManager.INTERVAL_HALF_DAY; /** * Store the context to allow access to application-specific * resources and classes. */ private Context mContext; /** * Time after which the data will expire (in nanoseconds). */ private long mDefaultTimeout; /** * AlarmManager provides access to the system alarm services. * Used to schedule Cache cleanup at regular intervals to remove * expired Acronym Expansions. */ private AlarmManager mAlarmManager; /** * This constructor sets the default timeout to the designated @a * timeout parameter and initialises local variables. * * @param context */ public ContentProviderTimeoutCache(Context context) { // Store the context. mContext = context; // Set the timeout to 10 seconds in nanoseconds. mDefaultTimeout = Long.valueOf(10000000000L); // Get the AlarmManager system service. mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); // If Cache Cleanup is not scheduled, then schedule it. scheduleCacheCleanup(context); } /** * Gets the @a List of Acronym Expansions from the cache having * given @a acronym. Remove it if expired. * * @param acronym * @return List of Acronym Data */ public List<AcronymExpansion> get(final String acronym) { // Selection clause to find rows with given acronym. final String SELECTION_ACRONYM = AcronymEntry.COLUMN_ACRONYM + " = ?"; // Initializes an array to contain selection arguments. String[] selectionArgs = { acronym }; // Cursor that is returned as a result of database query which // points to one or more rows. try (Cursor cursor = mContext.getContentResolver().query (AcronymEntry.CONTENT_URI, null, SELECTION_ACRONYM, selectionArgs, null)) { // If there are not matches in the database return false. if (!cursor.moveToFirst()) return null; // Since all rows with same acronym have same expiration // time, check the expiration of first row. If its // expired, then return null and start a thread to delete // all rows having that acronym else get the Acronym Data // from first row and add it to the List. else { // TODO -- replace "0" with the expiration time of // given acronym that's obtained from the cursor. Long expirationTime = 0L; // Check if the acronym is expired. If true, then // remove it. if (System.nanoTime() > expirationTime) { // Start a thread to delete all rows having that // acronym. new Thread(new Runnable() { public void run() { remove(acronym); } }).start(); return null; } else { List<AcronymExpansion> longForms = new ArrayList<AcronymExpansion>(); // Now we're sure that the acronym has not // expired, get the List of AcronymExpansions. do longForms.add(getAcronymExpansion(cursor)); while (cursor.moveToNext()); return longForms; } } } } /** * Get the acronym expansions data from the database. * * @param cursor * @return AcronymExpansion */ private AcronymExpansion getAcronymExpansion(Cursor cursor) { // TODO -- replace "null" with the "long form" of the acronym // obtained from the cursor. String longForm = null; // TODO -- replace "0" with the "frequency" value of the acronym // obtained from the cursor. int frequency = 0; // TODO -- replace "0" with the "since" value of the acronym // obtained from the cursor. int since = 0; return new AcronymExpansion(longForm, frequency, since); } /** * Put the @a longForms into the cache at the designated @a * acronym with the default timeout. * * @param acronym * @param longForms */ public void put(String acronym, List<AcronymExpansion> longForms) { putValues(acronym, longForms, mDefaultTimeout); } /** * Put the @a longForms into the Database at the designated @a * acronym with a certain timeout after which the cached data will * expire. * * @param acronym * @param longForms * @param timeout */ public void put(String acronym, List<AcronymExpansion> longForms, int timeout) { putValues(acronym, longForms, // Represent the timeout in nanoseconds. timeout * 1000 * 1000 * 1000); } /** * Helper method that puts a @a List of Acronym Expansions into * the cache at the designated @a acronym with a certain timeout, * after which the cached data expires. * * @param acronym * @param longForms * @param timeout * @return number of rows inserted */ private int putValues(String acronym, List<AcronymExpansion> longForms, long timeout) { // Check if the List is not null or empty. if (longForms == null || longForms.isEmpty()) return -1; // Calculate the Expiration time. Long expirationTime = System.nanoTime() + timeout; // Use ContentValues to store the values in appropriate // columns, so that ContentResolver can process it. Since // more than one rows needs to be inserted, an Array of // ContentValues is needed. ContentValues[] cvArray = new ContentValues[longForms.size()]; for (int i = 0; i < longForms.size(); i++) { // TODO -- as you loop through the list of acronym // expansions create a ContentValues object that contains // their contents, and store this into the appropriate // location the cvArray. } // Use ContentResolver to bulk insert the ContentValues into // the Acronym database and return the number of rows inserted. return mContext.getContentResolver().bulkInsert(AcronymEntry.CONTENT_URI, cvArray); } /** * Removes each expansion associated with the designated @a acronym. * * @param acronym */ public void remove(String acronym) { // Selection clause to find rows with given acronym. final String SELECTION_ACRONYM = AcronymEntry.COLUMN_ACRONYM + " = ?"; // Initializes an array to contain selection arguments String[] selectionArgs = { acronym }; // TODO - delete the row(s) associated with an acronym. } /** * Return the current number of rows in Database. * * @return size */ public int size() { // Use ContentResolver to get a Cursor that points to all rows // of Acronym table. try (Cursor cursor = mContext.getContentResolver().query (AcronymEntry.CONTENT_URI, null, null, null, null)) { return cursor.getCount(); } } /** * Remove the expired Acronyms from Database. */ public void removeExpiredAcronyms() { // Selection clause to delete acronym expansions that have // expired. final String SELECTION_EXPIRATION = AcronymEntry.COLUMN_EXPIRATION_TIME + " <= ?"; String[] selectionArgs = { String.valueOf(System.nanoTime()) }; // TODO -- delete expired acronym expansions. } /** * Helper method that uses AlarmManager to schedule Cache Cleanup * at regular intervals. * * @param context */ private void scheduleCacheCleanup(Context context) { // Only schedule the Alarm if it's not already scheduled. if (!isAlarmActive(context)) { // Schedule an alarm after a certain timeout to start a // service to delete expired data from Database. mAlarmManager.setInexactRepeating (AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + CLEANUP_SCHEDULER_TIME_INTERVAL, CLEANUP_SCHEDULER_TIME_INTERVAL, DeleteCacheReceiver.makeReceiverPendingIntent(context)); } } /** * Helper method to check whether the Alarm is already active or * not. * * @param context * @return boolean, whether the Alarm is already active or not */ private boolean isAlarmActive(Context context) { return DeleteCacheReceiver.makeCheckAlarmPendingIntent (context) != null; } }