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