blob: bf36cd8351385e1bba63565438657dd3bb97a891 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mms.util;
import java.util.HashSet;
import java.util.Set;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SqliteWrapper;
import android.provider.Telephony.MmsSms;
import android.provider.Telephony.Sms.Conversations;
import android.util.Log;
import com.android.mms.LogTag;
/**
* Cache for information about draft messages on conversations.
*/
public class DraftCache {
private static final String TAG = "Mms/draft";
private static DraftCache sInstance;
private final Context mContext;
private boolean mSavingDraft; // true when we're in the process of saving a draft. Check this
// before deleting any empty threads from the db.
private final Object mSavingDraftLock = new Object();
private HashSet<Long> mDraftSet = new HashSet<Long>(4);
private final Object mDraftSetLock = new Object();
private final HashSet<OnDraftChangedListener> mChangeListeners
= new HashSet<OnDraftChangedListener>(1);
private final Object mChangeListenersLock = new Object();
public interface OnDraftChangedListener {
void onDraftChanged(long threadId, boolean hasDraft);
}
private DraftCache(Context context) {
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("DraftCache.constructor");
}
mContext = context;
refresh();
}
static final String[] DRAFT_PROJECTION = new String[] {
Conversations.THREAD_ID // 0
};
static final int COLUMN_DRAFT_THREAD_ID = 0;
/** To be called whenever the draft state might have changed.
* Dispatches work to a thread and returns immediately.
*/
public void refresh() {
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("refresh");
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
rebuildCache();
}
}, "DraftCache.refresh");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
/** Does the actual work of rebuilding the draft cache.
*/
private void rebuildCache() {
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("rebuildCache");
}
HashSet<Long> newDraftSet = new HashSet<Long>();
Cursor cursor = SqliteWrapper.query(
mContext,
mContext.getContentResolver(),
MmsSms.CONTENT_DRAFT_URI,
DRAFT_PROJECTION, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
for (; !cursor.isAfterLast(); cursor.moveToNext()) {
long threadId = cursor.getLong(COLUMN_DRAFT_THREAD_ID);
newDraftSet.add(threadId);
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("rebuildCache: add tid=" + threadId);
}
}
}
} finally {
cursor.close();
}
}
Set<Long> added;
Set<Long> removed;
synchronized (mDraftSetLock) {
HashSet<Long> oldDraftSet = mDraftSet;
mDraftSet = newDraftSet;
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
dump();
}
// If nobody's interested in finding out about changes,
// just bail out early.
synchronized (mChangeListenersLock) {
if (mChangeListeners.size() < 1) {
return;
}
}
// Find out which drafts were removed and added and notify
// listeners.
added = new HashSet<Long>(newDraftSet);
added.removeAll(oldDraftSet);
removed = new HashSet<Long>(oldDraftSet);
removed.removeAll(newDraftSet);
}
synchronized (mChangeListenersLock) {
for (OnDraftChangedListener l : mChangeListeners) {
for (long threadId : added) {
l.onDraftChanged(threadId, true);
}
for (long threadId : removed) {
l.onDraftChanged(threadId, false);
}
}
}
}
/** Updates the has-draft status of a particular thread on
* a piecemeal basis, to be called when a draft has appeared
* or disappeared.
*/
public void setDraftState(long threadId, boolean hasDraft) {
if (threadId <= 0) {
return;
}
boolean changed;
synchronized (mDraftSetLock) {
if (hasDraft) {
changed = mDraftSet.add(threadId);
} else {
changed = mDraftSet.remove(threadId);
}
}
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("setDraftState: tid=" + threadId + ", value=" + hasDraft + ", changed=" + changed);
}
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
dump();
}
// Notify listeners if there was a change.
if (changed) {
synchronized (mChangeListenersLock) {
for (OnDraftChangedListener l : mChangeListeners) {
l.onDraftChanged(threadId, hasDraft);
}
}
}
}
/** Returns true if the given thread ID has a draft associated
* with it, false if not.
*/
public boolean hasDraft(long threadId) {
synchronized (mDraftSetLock) {
return mDraftSet.contains(threadId);
}
}
public void addOnDraftChangedListener(OnDraftChangedListener l) {
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("addOnDraftChangedListener " + l);
}
synchronized (mChangeListenersLock) {
mChangeListeners.add(l);
}
}
public void removeOnDraftChangedListener(OnDraftChangedListener l) {
if (Log.isLoggable(LogTag.APP, Log.DEBUG)) {
log("removeOnDraftChangedListener " + l);
}
synchronized (mChangeListenersLock) {
mChangeListeners.remove(l);
}
}
public void setSavingDraft(final boolean savingDraft) {
synchronized (mSavingDraftLock) {
mSavingDraft = savingDraft;
}
}
public boolean getSavingDraft() {
synchronized (mSavingDraftLock) {
return mSavingDraft;
}
}
/**
* Initialize the global instance. Should call only once.
*/
public static void init(Context context) {
sInstance = new DraftCache(context);
}
/**
* Get the global instance.
*/
public static DraftCache getInstance() {
return sInstance;
}
public void dump() {
Log.i(TAG, "dump:");
for (Long threadId : mDraftSet) {
Log.i(TAG, " tid: " + threadId);
}
}
private void log(String format, Object... args) {
String s = String.format(format, args);
Log.d(TAG, "[DraftCache/" + Thread.currentThread().getId() + "] " + s);
}
}