Home | History | Annotate | Download | only in util
      1  /*
      2  * Copyright (C) 2009 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.mms.util;
     18 
     19 import android.database.sqlite.SqliteWrapper;
     20 import com.android.mms.LogTag;
     21 
     22 import java.util.HashSet;
     23 import java.util.Set;
     24 
     25 import android.content.Context;
     26 import android.database.Cursor;
     27 import android.provider.Telephony.MmsSms;
     28 import android.provider.Telephony.Sms.Conversations;
     29 import android.util.Log;
     30 
     31 /**
     32  * Cache for information about draft messages on conversations.
     33  */
     34 public class DraftCache {
     35     private static final String TAG = "Mms/draft";
     36 
     37     private static DraftCache sInstance;
     38 
     39     private final Context mContext;
     40 
     41     private HashSet<Long> mDraftSet = new HashSet<Long>(4);
     42     private final HashSet<OnDraftChangedListener> mChangeListeners
     43             = new HashSet<OnDraftChangedListener>(1);
     44 
     45     public interface OnDraftChangedListener {
     46         void onDraftChanged(long threadId, boolean hasDraft);
     47     }
     48 
     49     private DraftCache(Context context) {
     50         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
     51             log("DraftCache.constructor");
     52         }
     53 
     54         mContext = context;
     55         refresh();
     56     }
     57 
     58     static final String[] DRAFT_PROJECTION = new String[] {
     59         Conversations.THREAD_ID           // 0
     60     };
     61 
     62     static final int COLUMN_DRAFT_THREAD_ID = 0;
     63 
     64     /** To be called whenever the draft state might have changed.
     65      *  Dispatches work to a thread and returns immediately.
     66      */
     67     public void refresh() {
     68         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
     69             log("refresh");
     70         }
     71 
     72         new Thread(new Runnable() {
     73             public void run() {
     74                 rebuildCache();
     75             }
     76         }).start();
     77     }
     78 
     79     /** Does the actual work of rebuilding the draft cache.
     80      */
     81     private synchronized void rebuildCache() {
     82         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
     83             log("rebuildCache");
     84         }
     85 
     86         HashSet<Long> oldDraftSet = mDraftSet;
     87         HashSet<Long> newDraftSet = new HashSet<Long>(oldDraftSet.size());
     88 
     89         Cursor cursor = SqliteWrapper.query(
     90                 mContext,
     91                 mContext.getContentResolver(),
     92                 MmsSms.CONTENT_DRAFT_URI,
     93                 DRAFT_PROJECTION, null, null, null);
     94 
     95         try {
     96             if (cursor.moveToFirst()) {
     97                 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
     98                     long threadId = cursor.getLong(COLUMN_DRAFT_THREAD_ID);
     99                     newDraftSet.add(threadId);
    100                     if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
    101                         log("rebuildCache: add tid=" + threadId);
    102                     }
    103                 }
    104             }
    105         } finally {
    106             cursor.close();
    107         }
    108 
    109         mDraftSet = newDraftSet;
    110 
    111         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    112             dump();
    113         }
    114 
    115         // If nobody's interested in finding out about changes,
    116         // just bail out early.
    117         if (mChangeListeners.size() < 1) {
    118             return;
    119         }
    120 
    121         // Find out which drafts were removed and added and notify
    122         // listeners.
    123         Set<Long> added = new HashSet<Long>(newDraftSet);
    124         added.removeAll(oldDraftSet);
    125         Set<Long> removed = new HashSet<Long>(oldDraftSet);
    126         removed.removeAll(newDraftSet);
    127 
    128         for (OnDraftChangedListener l : mChangeListeners) {
    129             for (long threadId : added) {
    130                 l.onDraftChanged(threadId, true);
    131             }
    132             for (long threadId : removed) {
    133                 l.onDraftChanged(threadId, false);
    134             }
    135         }
    136     }
    137 
    138     /** Updates the has-draft status of a particular thread on
    139      *  a piecemeal basis, to be called when a draft has appeared
    140      *  or disappeared.
    141      */
    142     public synchronized void setDraftState(long threadId, boolean hasDraft) {
    143         if (threadId <= 0) {
    144             return;
    145         }
    146 
    147         boolean changed;
    148         if (hasDraft) {
    149             changed = mDraftSet.add(threadId);
    150         } else {
    151             changed = mDraftSet.remove(threadId);
    152         }
    153 
    154         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
    155             log("setDraftState: tid=" + threadId + ", value=" + hasDraft + ", changed=" + changed);
    156         }
    157 
    158         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    159             dump();
    160         }
    161 
    162         // Notify listeners if there was a change.
    163         if (changed) {
    164             for (OnDraftChangedListener l : mChangeListeners) {
    165                 l.onDraftChanged(threadId, hasDraft);
    166             }
    167         }
    168     }
    169 
    170     /** Returns true if the given thread ID has a draft associated
    171      *  with it, false if not.
    172      */
    173     public synchronized boolean hasDraft(long threadId) {
    174         return mDraftSet.contains(threadId);
    175     }
    176 
    177     public synchronized void addOnDraftChangedListener(OnDraftChangedListener l) {
    178         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
    179             log("addOnDraftChangedListener " + l);
    180         }
    181         mChangeListeners.add(l);
    182     }
    183 
    184     public synchronized void removeOnDraftChangedListener(OnDraftChangedListener l) {
    185         if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
    186             log("removeOnDraftChangedListener " + l);
    187         }
    188         mChangeListeners.remove(l);
    189     }
    190 
    191     /**
    192      * Initialize the global instance. Should call only once.
    193      */
    194     public static void init(Context context) {
    195         sInstance = new DraftCache(context);
    196     }
    197 
    198     /**
    199      * Get the global instance.
    200      */
    201     public static DraftCache getInstance() {
    202         return sInstance;
    203     }
    204 
    205     public void dump() {
    206         Log.i(TAG, "dump:");
    207         for (Long threadId : mDraftSet) {
    208             Log.i(TAG, "  tid: " + threadId);
    209         }
    210     }
    211 
    212     private void log(String format, Object... args) {
    213         String s = String.format(format, args);
    214         Log.d(TAG, "[DraftCache/" + Thread.currentThread().getId() + "] " + s);
    215     }
    216 }
    217