blob: fd43331c57a682b913adf043b9207312ad31f7b8 [file] [log] [blame]
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 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.ui;
import android.content.Context;
import android.database.Cursor;
import android.os.Handler;
import android.provider.BaseColumns;
import android.provider.Telephony.Mms;
import android.provider.Telephony.MmsSms;
import android.provider.Telephony.MmsSms.PendingMessages;
import android.provider.Telephony.Sms;
import android.provider.Telephony.Sms.Conversations;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.CursorAdapter;
import android.widget.ListView;
import com.android.mms.R;
import com.google.android.mms.MmsException;
import java.util.regex.Pattern;
/**
* The back-end data adapter of a message list.
*/
public class MessageListAdapter extends CursorAdapter {
private static final String TAG = "MessageListAdapter";
private static final boolean LOCAL_LOGV = false;
static final String[] PROJECTION = new String[] {
// TODO: should move this symbol into com.android.mms.telephony.Telephony.
MmsSms.TYPE_DISCRIMINATOR_COLUMN,
BaseColumns._ID,
Conversations.THREAD_ID,
// For SMS
Sms.ADDRESS,
Sms.BODY,
Sms.DATE,
Sms.DATE_SENT,
Sms.READ,
Sms.TYPE,
Sms.STATUS,
Sms.LOCKED,
Sms.ERROR_CODE,
// For MMS
Mms.SUBJECT,
Mms.SUBJECT_CHARSET,
Mms.DATE,
Mms.DATE_SENT,
Mms.READ,
Mms.MESSAGE_TYPE,
Mms.MESSAGE_BOX,
Mms.DELIVERY_REPORT,
Mms.READ_REPORT,
PendingMessages.ERROR_TYPE,
Mms.LOCKED
};
// The indexes of the default columns which must be consistent
// with above PROJECTION.
static final int COLUMN_MSG_TYPE = 0;
static final int COLUMN_ID = 1;
static final int COLUMN_THREAD_ID = 2;
static final int COLUMN_SMS_ADDRESS = 3;
static final int COLUMN_SMS_BODY = 4;
static final int COLUMN_SMS_DATE = 5;
static final int COLUMN_SMS_DATE_SENT = 6;
static final int COLUMN_SMS_READ = 7;
static final int COLUMN_SMS_TYPE = 8;
static final int COLUMN_SMS_STATUS = 9;
static final int COLUMN_SMS_LOCKED = 10;
static final int COLUMN_SMS_ERROR_CODE = 11;
static final int COLUMN_MMS_SUBJECT = 12;
static final int COLUMN_MMS_SUBJECT_CHARSET = 13;
static final int COLUMN_MMS_DATE = 14;
static final int COLUMN_MMS_DATE_SENT = 15;
static final int COLUMN_MMS_READ = 16;
static final int COLUMN_MMS_MESSAGE_TYPE = 17;
static final int COLUMN_MMS_MESSAGE_BOX = 18;
static final int COLUMN_MMS_DELIVERY_REPORT = 19;
static final int COLUMN_MMS_READ_REPORT = 20;
static final int COLUMN_MMS_ERROR_TYPE = 21;
static final int COLUMN_MMS_LOCKED = 22;
private static final int CACHE_SIZE = 50;
public static final int INCOMING_ITEM_TYPE = 0;
public static final int OUTGOING_ITEM_TYPE = 1;
protected LayoutInflater mInflater;
private final LruCache<Long, MessageItem> mMessageItemCache;
private final ColumnsMap mColumnsMap;
private OnDataSetChangedListener mOnDataSetChangedListener;
private Handler mMsgListItemHandler;
private Pattern mHighlight;
private Context mContext;
public MessageListAdapter(
Context context, Cursor c, ListView listView,
boolean useDefaultColumnsMap, Pattern highlight) {
super(context, c, FLAG_REGISTER_CONTENT_OBSERVER);
mContext = context;
mHighlight = highlight;
mInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mMessageItemCache = new LruCache<Long, MessageItem>(CACHE_SIZE);
if (useDefaultColumnsMap) {
mColumnsMap = new ColumnsMap();
} else {
mColumnsMap = new ColumnsMap(c);
}
listView.setRecyclerListener(new AbsListView.RecyclerListener() {
@Override
public void onMovedToScrapHeap(View view) {
if (view instanceof MessageListItem) {
MessageListItem mli = (MessageListItem) view;
// Clear references to resources
mli.unbind();
}
}
});
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof MessageListItem) {
String type = cursor.getString(mColumnsMap.mColumnMsgType);
long msgId = cursor.getLong(mColumnsMap.mColumnMsgId);
MessageItem msgItem = getCachedMessageItem(type, msgId, cursor);
if (msgItem != null) {
MessageListItem mli = (MessageListItem) view;
mli.bind(msgItem, cursor.getPosition() == cursor.getCount() - 1);
mli.setMsgListItemHandler(mMsgListItemHandler);
}
}
}
public interface OnDataSetChangedListener {
void onDataSetChanged(MessageListAdapter adapter);
void onContentChanged(MessageListAdapter adapter);
}
public void setOnDataSetChangedListener(OnDataSetChangedListener l) {
mOnDataSetChangedListener = l;
}
public void setMsgListItemHandler(Handler handler) {
mMsgListItemHandler = handler;
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
if (LOCAL_LOGV) {
Log.v(TAG, "MessageListAdapter.notifyDataSetChanged().");
}
mMessageItemCache.evictAll();
if (mOnDataSetChangedListener != null) {
mOnDataSetChangedListener.onDataSetChanged(this);
}
}
@Override
protected void onContentChanged() {
if (getCursor() != null && !getCursor().isClosed()) {
if (mOnDataSetChangedListener != null) {
mOnDataSetChangedListener.onContentChanged(this);
}
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(getItemViewType(cursor) == INCOMING_ITEM_TYPE ?
R.layout.message_list_item_recv : R.layout.message_list_item_send,
parent, false);
}
public MessageItem getCachedMessageItem(String type, long msgId, Cursor c) {
MessageItem item = mMessageItemCache.get(getKey(type, msgId));
if (item == null && c != null && isCursorValid(c)) {
try {
item = new MessageItem(mContext, type, c, mColumnsMap, mHighlight);
mMessageItemCache.put(getKey(item.mType, item.mMsgId), item);
} catch (MmsException e) {
Log.e(TAG, "getCachedMessageItem: ", e);
}
}
return item;
}
private boolean isCursorValid(Cursor cursor) {
// Check whether the cursor is valid or not.
if (cursor.isClosed() || cursor.isBeforeFirst() || cursor.isAfterLast()) {
return false;
}
return true;
}
private static long getKey(String type, long id) {
if (type.equals("mms")) {
return -id;
} else {
return id;
}
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
/* MessageListAdapter says that it contains two types of views. Really, it just contains
* a single type, a MessageListItem. Depending upon whether the message is an incoming or
* outgoing message, the avatar and text and other items are laid out either left or right
* justified. That works fine for everything but the message text. When views are recycled,
* there's a greater than zero chance that the right-justified text on outgoing messages
* will remain left-justified. The best solution at this point is to tell the adapter we've
* got two different types of views. That way we won't recycle views between the two types.
* @see android.widget.BaseAdapter#getViewTypeCount()
*/
@Override
public int getViewTypeCount() {
return 2; // Incoming and outgoing messages
}
@Override
public int getItemViewType(int position) {
Cursor cursor = (Cursor)getItem(position);
return getItemViewType(cursor);
}
private int getItemViewType(Cursor cursor) {
String type = cursor.getString(mColumnsMap.mColumnMsgType);
int boxId;
if ("sms".equals(type)) {
boxId = cursor.getInt(mColumnsMap.mColumnSmsType);
} else {
boxId = cursor.getInt(mColumnsMap.mColumnMmsMessageBox);
}
return boxId == Mms.MESSAGE_BOX_INBOX ? INCOMING_ITEM_TYPE : OUTGOING_ITEM_TYPE;
}
public static class ColumnsMap {
public int mColumnMsgType;
public int mColumnMsgId;
public int mColumnSmsAddress;
public int mColumnSmsBody;
public int mColumnSmsDate;
public int mColumnSmsDateSent;
public int mColumnSmsRead;
public int mColumnSmsType;
public int mColumnSmsStatus;
public int mColumnSmsLocked;
public int mColumnSmsErrorCode;
public int mColumnMmsSubject;
public int mColumnMmsSubjectCharset;
public int mColumnMmsDate;
public int mColumnMmsDateSent;
public int mColumnMmsRead;
public int mColumnMmsMessageType;
public int mColumnMmsMessageBox;
public int mColumnMmsDeliveryReport;
public int mColumnMmsReadReport;
public int mColumnMmsErrorType;
public int mColumnMmsLocked;
public ColumnsMap() {
mColumnMsgType = COLUMN_MSG_TYPE;
mColumnMsgId = COLUMN_ID;
mColumnSmsAddress = COLUMN_SMS_ADDRESS;
mColumnSmsBody = COLUMN_SMS_BODY;
mColumnSmsDate = COLUMN_SMS_DATE;
mColumnSmsDateSent = COLUMN_SMS_DATE_SENT;
mColumnSmsType = COLUMN_SMS_TYPE;
mColumnSmsStatus = COLUMN_SMS_STATUS;
mColumnSmsLocked = COLUMN_SMS_LOCKED;
mColumnSmsErrorCode = COLUMN_SMS_ERROR_CODE;
mColumnMmsSubject = COLUMN_MMS_SUBJECT;
mColumnMmsSubjectCharset = COLUMN_MMS_SUBJECT_CHARSET;
mColumnMmsMessageType = COLUMN_MMS_MESSAGE_TYPE;
mColumnMmsMessageBox = COLUMN_MMS_MESSAGE_BOX;
mColumnMmsDeliveryReport = COLUMN_MMS_DELIVERY_REPORT;
mColumnMmsReadReport = COLUMN_MMS_READ_REPORT;
mColumnMmsErrorType = COLUMN_MMS_ERROR_TYPE;
mColumnMmsLocked = COLUMN_MMS_LOCKED;
}
public ColumnsMap(Cursor cursor) {
// Ignore all 'not found' exceptions since the custom columns
// may be just a subset of the default columns.
try {
mColumnMsgType = cursor.getColumnIndexOrThrow(
MmsSms.TYPE_DISCRIMINATOR_COLUMN);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMsgId = cursor.getColumnIndexOrThrow(BaseColumns._ID);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsAddress = cursor.getColumnIndexOrThrow(Sms.ADDRESS);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsBody = cursor.getColumnIndexOrThrow(Sms.BODY);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsDate = cursor.getColumnIndexOrThrow(Sms.DATE);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsDateSent = cursor.getColumnIndexOrThrow(Sms.DATE_SENT);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsType = cursor.getColumnIndexOrThrow(Sms.TYPE);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsStatus = cursor.getColumnIndexOrThrow(Sms.STATUS);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsLocked = cursor.getColumnIndexOrThrow(Sms.LOCKED);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnSmsErrorCode = cursor.getColumnIndexOrThrow(Sms.ERROR_CODE);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsSubject = cursor.getColumnIndexOrThrow(Mms.SUBJECT);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsSubjectCharset = cursor.getColumnIndexOrThrow(Mms.SUBJECT_CHARSET);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsMessageType = cursor.getColumnIndexOrThrow(Mms.MESSAGE_TYPE);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsMessageBox = cursor.getColumnIndexOrThrow(Mms.MESSAGE_BOX);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsDeliveryReport = cursor.getColumnIndexOrThrow(Mms.DELIVERY_REPORT);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsReadReport = cursor.getColumnIndexOrThrow(Mms.READ_REPORT);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsErrorType = cursor.getColumnIndexOrThrow(PendingMessages.ERROR_TYPE);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
try {
mColumnMmsLocked = cursor.getColumnIndexOrThrow(Mms.LOCKED);
} catch (IllegalArgumentException e) {
Log.w("colsMap", e.getMessage());
}
}
}
}