package cn.edu.tsinghua.hpc.tmms.syncaction;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.http.client.ClientProtocolException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.AbstractCursor;
import android.database.CursorIndexOutOfBoundsException;
import android.net.Uri;
import android.os.Handler;
import android.provider.Telephony.Threads;
import android.util.Log;
import cn.edu.tsinghua.hpc.syncbroker.ElementNotFound;
import cn.edu.tsinghua.hpc.syncbroker.SMSRecord;
import cn.edu.tsinghua.hpc.syncbroker.SMSType;
import cn.edu.tsinghua.hpc.syncbroker.SyncRecord;
import cn.edu.tsinghua.hpc.tmms.util.TTelephony.TSms;
import cn.edu.tsinghua.hpc.tmms.util.TTelephony.TThreads;
/**
* A mutable cursor implementation backed by an array of {@code Object}s. Use
* {@link #newRow()} to add rows. Automatically expands internal capacity as
* needed.
*/
public class TThreadsCursor extends AbstractCursor {
private String[] columnNames;
private static List<Entry<Long, SMSRecord>> data = new ArrayList<Entry<Long, SMSRecord>>();;
private static int mPageNo = 1;
private static int mCountPerPage = 5;
private Context mContext;
// faked _ID's offset
public static long BIG_OFFSET = 100000L;
public static int CACHE_WINDOW_SIZE = 20;
/**
* Constructs a new cursor with the given initial capacity.
*
* @param columnNames
* names of the columns, the ordering of which determines column
* ordering elsewhere in this cursor
* @param initialCapacity
* in rows
*/
public TThreadsCursor(Context ctx, String[] columnNames) {
this.mContext = ctx;
this.columnNames = columnNames;
}
public boolean deleteRow() {
try {
data.remove(mPos);
return true;
} catch (Exception ex) {
return false;
}
}
@Override
public boolean requery() {
// XXX not supported now
// do nothing now
// throw new UnsupportedOperationException();
return true;
}
private SMSRecord getThreadInfoByIndex(int index) {
return data.get(index).getValue();
}
private List<SyncRecord> currentCachedThread = new ArrayList<SyncRecord>();
/**
* temporily insert the Mms-SMS back to database
*/
public void cacheSomeThreads(List<Long> ids, ContentResolver resolver) {
Log.d("Mms", "cacheSomeThreads");
while (currentCachedThread.size() + ids.size() > CACHE_WINDOW_SIZE) {
SyncRecord s = currentCachedThread.get(0);
resolver.delete(TSms.CONTENT_URI, "_ID is " + s._id
+ " AND guid IS " + s.getGuid(), null);
s._id = SyncRecord.ID_IN_MEMORY;
currentCachedThread.remove(0);
}
// add useful people
for (long id : ids) {
Log.d("Mms", "cache id " + id);
SMSRecord cs = getThreadInfoByIndex((int) (id - BIG_OFFSET));
if (cs._id == SyncRecord.ID_IN_MEMORY) {
Uri uri = MmsUtils.insertOneMessageInto(mContext, cs,
SyncState.SYNC_STATE_TMP);
cs._id = (int) ContentUris.parseId(uri);
currentCachedThread.add(cs);
}
}
}
/**
* temporily insert the Mms-SMS back to database
*/
public void cacheSomeThreadsByRecord(List<SyncRecord> records,
ContentResolver resolver) {
Log.d("Mms", "cacheSomeThreadsByRecord");
while (currentCachedThread.size() + records.size() > CACHE_WINDOW_SIZE) {
SyncRecord s = currentCachedThread.get(0);
resolver.delete(TThreads.CONTENT_URI, "_ID is " + s._id
+ " AND cached IS 0", null);
s._id = SyncRecord.ID_IN_MEMORY;
currentCachedThread.remove(0);
}
// add useful people
for (SyncRecord sr : records) {
Log.d("Mms", "cache id " + sr._id);
SMSRecord cs = (SMSRecord) sr;
if (cs._id == SyncRecord.ID_IN_MEMORY) {
Uri uri = MmsUtils.tempInsertOneThreadInto(mContext, cs);
cs._id = (int) ContentUris.parseId(uri);
currentCachedThread.add(cs);
}
}
}
/**
* Gets value at the given column for the current row.
*/
private Object get(int column) {
if (column < 0 || column >= columnNames.length) {
throw new CursorIndexOutOfBoundsException("Requested column: "
+ column + ", # of columns: " + columnNames.length);
}
if (mPos < 0) {
throw new CursorIndexOutOfBoundsException("Before first row.");
}
if (mPos >= data.size()) {
throw new CursorIndexOutOfBoundsException("After last row.");
}
SMSRecord cs = getThreadInfoByIndex(mPos);
if (cs == null) {
return null;
}
String c = this.columnNames[column];
if (Threads._ID.equals(c)) {
if (cs._id == SMSRecord.ID_IN_MEMORY) {
return mPos + BIG_OFFSET;
} else {
return cs._id;
}
} else if (Threads.MESSAGE_COUNT.equals(c)) {
return 1;
} else if (Threads.RECIPIENT_IDS.equals(c)) {
if(cs.getType()== SMSType.RECEIVE)
{
return cs.getFrom();
}else
{
return cs.getTo();
}
} else if (Threads.DATE.equals(c)) {
return cs.getDate().getTime();
} else if (Threads.READ.equals(c)) {
return 1;
} else if (Threads.SNIPPET.equals(c)) {
return cs.getBody();
} else if (Threads.SNIPPET_CHARSET.equals(c)) {
return 0;
} else if (Threads.ERROR.equals(c)) {
// XXX
return 0;
} else if (Threads.HAS_ATTACHMENT.equals(c)) {
// XXX
return 0;
}
return null;
}
static final String[] PROJECTION = new String[] { Threads._ID, // 0
Threads.MESSAGE_COUNT, // 1
Threads.RECIPIENT_IDS, // 2
Threads.DATE, // 3
Threads.READ, // 4
Threads.SNIPPET, // 5
Threads.SNIPPET_CHARSET, // 6
Threads.ERROR, // 7
Threads.HAS_ATTACHMENT // 8
};
// AbstractCursor implementation.
@Override
public int getCount() {
return data.size();
}
@Override
public String[] getColumnNames() {
return columnNames;
}
public void setCoulumnNames(String[] columns) {
this.columnNames = columns;
}
@Override
public String getString(int column) {
Object value = get(column);
if (value == null)
return null;
return value.toString();
}
@Override
public short getShort(int column) {
Object value = get(column);
if (value == null)
return 0;
if (value instanceof Number)
return ((Number) value).shortValue();
return Short.parseShort(value.toString());
}
@Override
public int getInt(int column) {
Object value = get(column);
if (value == null)
return 0;
if (value instanceof Number)
return ((Number) value).intValue();
return Integer.parseInt(value.toString());
}
@Override
public long getLong(int column) {
Object value = get(column);
if (value == null)
return 0;
if (value instanceof Number)
return ((Number) value).longValue();
return Long.parseLong(value.toString());
}
@Override
public float getFloat(int column) {
Object value = get(column);
if (value == null)
return 0.0f;
if (value instanceof Number)
return ((Number) value).floatValue();
return Float.parseFloat(value.toString());
}
@Override
public double getDouble(int column) {
Object value = get(column);
if (value == null)
return 0.0d;
if (value instanceof Number)
return ((Number) value).doubleValue();
return Double.parseDouble(value.toString());
}
@Override
public boolean isNull(int column) {
return get(column) == null;
}
public void retrieveArchivedThreads(Handler handler) {
new Timer().schedule(new RetriveAchivedSMSTimerTask(handler), 0);
}
private class RetriveAchivedSMSTimerTask extends TimerTask {
int count;
Handler handler;
public RetriveAchivedSMSTimerTask(Handler handler) {
count = mCountPerPage;
this.handler = handler;
}
@Override
public void run() {
Log.d("Mms", "retrieveArchivedSMS task began");
try {
List<SyncRecord> result = SyncAction.retriveAchivedThread(
mContext, mPageNo, count);
for (SyncRecord s : result) {
data.add(new MapEntry(new Long(s.getGuid()), s));
}
if (result.size() == 0) {
} else if (result.size() < count) {
mPageNo++;
} else {
mPageNo++;
}
if (this.handler != null) {
handler.sendEmptyMessage(0);
}
cacheSomeThreadsByRecord(result, mContext.getContentResolver());
} catch (ClientProtocolException e) {
Log.d("Mms", "ClientProtocolException");
} catch (ElementNotFound e) {
Log.d("Mms", "ElementNotFound");
} catch (IOException e) {
Log.d("Mms", "IOException");
this.cancel();
}
}
}
/**
* this class is copied from java.util.MapEntry, for its visibility is
* internal
*/
class MapEntry<K, V> implements Map.Entry<K, V>, Cloneable {
K key;
V value;
MapEntry(K theKey) {
key = theKey;
}
MapEntry(K theKey, V theValue) {
key = theKey;
value = theValue;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof Map.Entry) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
return (key == null ? entry.getKey() == null : key.equals(entry
.getKey()))
&& (value == null ? entry.getValue() == null : value
.equals(entry.getValue()));
}
return false;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
public V setValue(V object) {
V result = value;
value = object;
return result;
}
@Override
public String toString() {
return key + "=" + value;
}
}
}