• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onDraftChanged(long threadId, boolean hasDraft)49         void onDraftChanged(long threadId, boolean hasDraft);
50     }
51 
DraftCache(Context context)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      */
refresh()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      */
rebuildCache()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      */
setDraftState(long threadId, boolean hasDraft)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      */
hasDraft(long threadId)177     public synchronized boolean hasDraft(long threadId) {
178         return mDraftSet.contains(threadId);
179     }
180 
addOnDraftChangedListener(OnDraftChangedListener l)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 
removeOnDraftChangedListener(OnDraftChangedListener l)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 
setSavingDraft(final boolean savingDraft)195     public synchronized void setSavingDraft(final boolean savingDraft) {
196         mSavingDraft = savingDraft;
197     }
198 
getSavingDraft()199     public synchronized boolean getSavingDraft() {
200         return mSavingDraft;
201     }
202 
203     /**
204      * Initialize the global instance. Should call only once.
205      */
init(Context context)206     public static void init(Context context) {
207         sInstance = new DraftCache(context);
208     }
209 
210     /**
211      * Get the global instance.
212      */
getInstance()213     public static DraftCache getInstance() {
214         return sInstance;
215     }
216 
dump()217     public void dump() {
218         Log.i(TAG, "dump:");
219         for (Long threadId : mDraftSet) {
220             Log.i(TAG, "  tid: " + threadId);
221         }
222     }
223 
log(String format, Object... args)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