• 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 com.google.android.mms.util.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 {
onDraftChanged(long threadId, boolean hasDraft)46         void onDraftChanged(long threadId, boolean hasDraft);
47     }
48 
DraftCache(Context context)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      */
refresh()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      */
rebuildCache()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      */
setDraftState(long threadId, boolean hasDraft)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      */
hasDraft(long threadId)173     public synchronized boolean hasDraft(long threadId) {
174         return mDraftSet.contains(threadId);
175     }
176 
addOnDraftChangedListener(OnDraftChangedListener l)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 
removeOnDraftChangedListener(OnDraftChangedListener l)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      */
init(Context context)194     public static void init(Context context) {
195         sInstance = new DraftCache(context);
196     }
197 
198     /**
199      * Get the global instance.
200      */
getInstance()201     public static DraftCache getInstance() {
202         return sInstance;
203     }
204 
dump()205     public void dump() {
206         Log.i(TAG, "dump:");
207         for (Long threadId : mDraftSet) {
208             Log.i(TAG, "  tid: " + threadId);
209         }
210     }
211 
log(String format, Object... args)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