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