Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.ui;
     19 
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.os.Handler;
     23 import android.provider.BaseColumns;
     24 import android.provider.Telephony.Mms;
     25 import android.provider.Telephony.MmsSms;
     26 import android.provider.Telephony.MmsSms.PendingMessages;
     27 import android.provider.Telephony.Sms;
     28 import android.provider.Telephony.Sms.Conversations;
     29 import android.util.Log;
     30 import android.util.LruCache;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.AbsListView;
     35 import android.widget.CursorAdapter;
     36 import android.widget.ListView;
     37 import com.android.mms.R;
     38 import com.google.android.mms.MmsException;
     39 
     40 import java.util.regex.Pattern;
     41 
     42 /**
     43  * The back-end data adapter of a message list.
     44  */
     45 public class MessageListAdapter extends CursorAdapter {
     46     private static final String TAG = "MessageListAdapter";
     47     private static final boolean LOCAL_LOGV = false;
     48 
     49     static final String[] PROJECTION = new String[] {
     50         // TODO: should move this symbol into com.android.mms.telephony.Telephony.
     51         MmsSms.TYPE_DISCRIMINATOR_COLUMN,
     52         BaseColumns._ID,
     53         Conversations.THREAD_ID,
     54         // For SMS
     55         Sms.ADDRESS,
     56         Sms.BODY,
     57         Sms.DATE,
     58         Sms.DATE_SENT,
     59         Sms.READ,
     60         Sms.TYPE,
     61         Sms.STATUS,
     62         Sms.LOCKED,
     63         Sms.ERROR_CODE,
     64         // For MMS
     65         Mms.SUBJECT,
     66         Mms.SUBJECT_CHARSET,
     67         Mms.DATE,
     68         Mms.DATE_SENT,
     69         Mms.READ,
     70         Mms.MESSAGE_TYPE,
     71         Mms.MESSAGE_BOX,
     72         Mms.DELIVERY_REPORT,
     73         Mms.READ_REPORT,
     74         PendingMessages.ERROR_TYPE,
     75         Mms.LOCKED
     76     };
     77 
     78     // The indexes of the default columns which must be consistent
     79     // with above PROJECTION.
     80     static final int COLUMN_MSG_TYPE            = 0;
     81     static final int COLUMN_ID                  = 1;
     82     static final int COLUMN_THREAD_ID           = 2;
     83     static final int COLUMN_SMS_ADDRESS         = 3;
     84     static final int COLUMN_SMS_BODY            = 4;
     85     static final int COLUMN_SMS_DATE            = 5;
     86     static final int COLUMN_SMS_DATE_SENT       = 6;
     87     static final int COLUMN_SMS_READ            = 7;
     88     static final int COLUMN_SMS_TYPE            = 8;
     89     static final int COLUMN_SMS_STATUS          = 9;
     90     static final int COLUMN_SMS_LOCKED          = 10;
     91     static final int COLUMN_SMS_ERROR_CODE      = 11;
     92     static final int COLUMN_MMS_SUBJECT         = 12;
     93     static final int COLUMN_MMS_SUBJECT_CHARSET = 13;
     94     static final int COLUMN_MMS_DATE            = 14;
     95     static final int COLUMN_MMS_DATE_SENT       = 15;
     96     static final int COLUMN_MMS_READ            = 16;
     97     static final int COLUMN_MMS_MESSAGE_TYPE    = 17;
     98     static final int COLUMN_MMS_MESSAGE_BOX     = 18;
     99     static final int COLUMN_MMS_DELIVERY_REPORT = 19;
    100     static final int COLUMN_MMS_READ_REPORT     = 20;
    101     static final int COLUMN_MMS_ERROR_TYPE      = 21;
    102     static final int COLUMN_MMS_LOCKED          = 22;
    103 
    104     private static final int CACHE_SIZE         = 50;
    105 
    106     public static final int INCOMING_ITEM_TYPE = 0;
    107     public static final int OUTGOING_ITEM_TYPE = 1;
    108 
    109     protected LayoutInflater mInflater;
    110     private final LruCache<Long, MessageItem> mMessageItemCache;
    111     private final ColumnsMap mColumnsMap;
    112     private OnDataSetChangedListener mOnDataSetChangedListener;
    113     private Handler mMsgListItemHandler;
    114     private Pattern mHighlight;
    115     private Context mContext;
    116 
    117     public MessageListAdapter(
    118             Context context, Cursor c, ListView listView,
    119             boolean useDefaultColumnsMap, Pattern highlight) {
    120         super(context, c, FLAG_REGISTER_CONTENT_OBSERVER);
    121         mContext = context;
    122         mHighlight = highlight;
    123 
    124         mInflater = (LayoutInflater) context.getSystemService(
    125                 Context.LAYOUT_INFLATER_SERVICE);
    126         mMessageItemCache = new LruCache<Long, MessageItem>(CACHE_SIZE);
    127 
    128         if (useDefaultColumnsMap) {
    129             mColumnsMap = new ColumnsMap();
    130         } else {
    131             mColumnsMap = new ColumnsMap(c);
    132         }
    133 
    134         listView.setRecyclerListener(new AbsListView.RecyclerListener() {
    135             @Override
    136             public void onMovedToScrapHeap(View view) {
    137                 if (view instanceof MessageListItem) {
    138                     MessageListItem mli = (MessageListItem) view;
    139                     // Clear references to resources
    140                     mli.unbind();
    141                 }
    142             }
    143         });
    144     }
    145 
    146     @Override
    147     public void bindView(View view, Context context, Cursor cursor) {
    148         if (view instanceof MessageListItem) {
    149             String type = cursor.getString(mColumnsMap.mColumnMsgType);
    150             long msgId = cursor.getLong(mColumnsMap.mColumnMsgId);
    151 
    152             MessageItem msgItem = getCachedMessageItem(type, msgId, cursor);
    153             if (msgItem != null) {
    154                 MessageListItem mli = (MessageListItem) view;
    155                 mli.bind(msgItem, cursor.getPosition() == cursor.getCount() - 1);
    156                 mli.setMsgListItemHandler(mMsgListItemHandler);
    157             }
    158         }
    159     }
    160 
    161     public interface OnDataSetChangedListener {
    162         void onDataSetChanged(MessageListAdapter adapter);
    163         void onContentChanged(MessageListAdapter adapter);
    164     }
    165 
    166     public void setOnDataSetChangedListener(OnDataSetChangedListener l) {
    167         mOnDataSetChangedListener = l;
    168     }
    169 
    170     public void setMsgListItemHandler(Handler handler) {
    171         mMsgListItemHandler = handler;
    172     }
    173 
    174     @Override
    175     public void notifyDataSetChanged() {
    176         super.notifyDataSetChanged();
    177         if (LOCAL_LOGV) {
    178             Log.v(TAG, "MessageListAdapter.notifyDataSetChanged().");
    179         }
    180 
    181         mMessageItemCache.evictAll();
    182 
    183         if (mOnDataSetChangedListener != null) {
    184             mOnDataSetChangedListener.onDataSetChanged(this);
    185         }
    186     }
    187 
    188     @Override
    189     protected void onContentChanged() {
    190         if (getCursor() != null && !getCursor().isClosed()) {
    191             if (mOnDataSetChangedListener != null) {
    192                 mOnDataSetChangedListener.onContentChanged(this);
    193             }
    194         }
    195     }
    196 
    197     @Override
    198     public View newView(Context context, Cursor cursor, ViewGroup parent) {
    199         return mInflater.inflate(getItemViewType(cursor) == INCOMING_ITEM_TYPE ?
    200                 R.layout.message_list_item_recv : R.layout.message_list_item_send,
    201                 parent, false);
    202     }
    203 
    204     public MessageItem getCachedMessageItem(String type, long msgId, Cursor c) {
    205         MessageItem item = mMessageItemCache.get(getKey(type, msgId));
    206         if (item == null && c != null && isCursorValid(c)) {
    207             try {
    208                 item = new MessageItem(mContext, type, c, mColumnsMap, mHighlight);
    209                 mMessageItemCache.put(getKey(item.mType, item.mMsgId), item);
    210             } catch (MmsException e) {
    211                 Log.e(TAG, "getCachedMessageItem: ", e);
    212             }
    213         }
    214         return item;
    215     }
    216 
    217     private boolean isCursorValid(Cursor cursor) {
    218         // Check whether the cursor is valid or not.
    219         if (cursor.isClosed() || cursor.isBeforeFirst() || cursor.isAfterLast()) {
    220             return false;
    221         }
    222         return true;
    223     }
    224 
    225     private static long getKey(String type, long id) {
    226         if (type.equals("mms")) {
    227             return -id;
    228         } else {
    229             return id;
    230         }
    231     }
    232 
    233     @Override
    234     public boolean areAllItemsEnabled() {
    235         return true;
    236     }
    237 
    238     /* MessageListAdapter says that it contains two types of views. Really, it just contains
    239      * a single type, a MessageListItem. Depending upon whether the message is an incoming or
    240      * outgoing message, the avatar and text and other items are laid out either left or right
    241      * justified. That works fine for everything but the message text. When views are recycled,
    242      * there's a greater than zero chance that the right-justified text on outgoing messages
    243      * will remain left-justified. The best solution at this point is to tell the adapter we've
    244      * got two different types of views. That way we won't recycle views between the two types.
    245      * @see android.widget.BaseAdapter#getViewTypeCount()
    246      */
    247     @Override
    248     public int getViewTypeCount() {
    249         return 2;   // Incoming and outgoing messages
    250     }
    251 
    252     @Override
    253     public int getItemViewType(int position) {
    254         Cursor cursor = (Cursor)getItem(position);
    255         return getItemViewType(cursor);
    256     }
    257 
    258     private int getItemViewType(Cursor cursor) {
    259         String type = cursor.getString(mColumnsMap.mColumnMsgType);
    260         int boxId;
    261         if ("sms".equals(type)) {
    262             boxId = cursor.getInt(mColumnsMap.mColumnSmsType);
    263         } else {
    264             boxId = cursor.getInt(mColumnsMap.mColumnMmsMessageBox);
    265         }
    266         return boxId == Mms.MESSAGE_BOX_INBOX ? INCOMING_ITEM_TYPE : OUTGOING_ITEM_TYPE;
    267     }
    268 
    269     public static class ColumnsMap {
    270         public int mColumnMsgType;
    271         public int mColumnMsgId;
    272         public int mColumnSmsAddress;
    273         public int mColumnSmsBody;
    274         public int mColumnSmsDate;
    275         public int mColumnSmsDateSent;
    276         public int mColumnSmsRead;
    277         public int mColumnSmsType;
    278         public int mColumnSmsStatus;
    279         public int mColumnSmsLocked;
    280         public int mColumnSmsErrorCode;
    281         public int mColumnMmsSubject;
    282         public int mColumnMmsSubjectCharset;
    283         public int mColumnMmsDate;
    284         public int mColumnMmsDateSent;
    285         public int mColumnMmsRead;
    286         public int mColumnMmsMessageType;
    287         public int mColumnMmsMessageBox;
    288         public int mColumnMmsDeliveryReport;
    289         public int mColumnMmsReadReport;
    290         public int mColumnMmsErrorType;
    291         public int mColumnMmsLocked;
    292 
    293         public ColumnsMap() {
    294             mColumnMsgType            = COLUMN_MSG_TYPE;
    295             mColumnMsgId              = COLUMN_ID;
    296             mColumnSmsAddress         = COLUMN_SMS_ADDRESS;
    297             mColumnSmsBody            = COLUMN_SMS_BODY;
    298             mColumnSmsDate            = COLUMN_SMS_DATE;
    299             mColumnSmsDateSent        = COLUMN_SMS_DATE_SENT;
    300             mColumnSmsType            = COLUMN_SMS_TYPE;
    301             mColumnSmsStatus          = COLUMN_SMS_STATUS;
    302             mColumnSmsLocked          = COLUMN_SMS_LOCKED;
    303             mColumnSmsErrorCode       = COLUMN_SMS_ERROR_CODE;
    304             mColumnMmsSubject         = COLUMN_MMS_SUBJECT;
    305             mColumnMmsSubjectCharset  = COLUMN_MMS_SUBJECT_CHARSET;
    306             mColumnMmsMessageType     = COLUMN_MMS_MESSAGE_TYPE;
    307             mColumnMmsMessageBox      = COLUMN_MMS_MESSAGE_BOX;
    308             mColumnMmsDeliveryReport  = COLUMN_MMS_DELIVERY_REPORT;
    309             mColumnMmsReadReport      = COLUMN_MMS_READ_REPORT;
    310             mColumnMmsErrorType       = COLUMN_MMS_ERROR_TYPE;
    311             mColumnMmsLocked          = COLUMN_MMS_LOCKED;
    312         }
    313 
    314         public ColumnsMap(Cursor cursor) {
    315             // Ignore all 'not found' exceptions since the custom columns
    316             // may be just a subset of the default columns.
    317             try {
    318                 mColumnMsgType = cursor.getColumnIndexOrThrow(
    319                         MmsSms.TYPE_DISCRIMINATOR_COLUMN);
    320             } catch (IllegalArgumentException e) {
    321                 Log.w("colsMap", e.getMessage());
    322             }
    323 
    324             try {
    325                 mColumnMsgId = cursor.getColumnIndexOrThrow(BaseColumns._ID);
    326             } catch (IllegalArgumentException e) {
    327                 Log.w("colsMap", e.getMessage());
    328             }
    329 
    330             try {
    331                 mColumnSmsAddress = cursor.getColumnIndexOrThrow(Sms.ADDRESS);
    332             } catch (IllegalArgumentException e) {
    333                 Log.w("colsMap", e.getMessage());
    334             }
    335 
    336             try {
    337                 mColumnSmsBody = cursor.getColumnIndexOrThrow(Sms.BODY);
    338             } catch (IllegalArgumentException e) {
    339                 Log.w("colsMap", e.getMessage());
    340             }
    341 
    342             try {
    343                 mColumnSmsDate = cursor.getColumnIndexOrThrow(Sms.DATE);
    344             } catch (IllegalArgumentException e) {
    345                 Log.w("colsMap", e.getMessage());
    346             }
    347 
    348             try {
    349                 mColumnSmsDateSent = cursor.getColumnIndexOrThrow(Sms.DATE_SENT);
    350             } catch (IllegalArgumentException e) {
    351                 Log.w("colsMap", e.getMessage());
    352             }
    353 
    354             try {
    355                 mColumnSmsType = cursor.getColumnIndexOrThrow(Sms.TYPE);
    356             } catch (IllegalArgumentException e) {
    357                 Log.w("colsMap", e.getMessage());
    358             }
    359 
    360             try {
    361                 mColumnSmsStatus = cursor.getColumnIndexOrThrow(Sms.STATUS);
    362             } catch (IllegalArgumentException e) {
    363                 Log.w("colsMap", e.getMessage());
    364             }
    365 
    366             try {
    367                 mColumnSmsLocked = cursor.getColumnIndexOrThrow(Sms.LOCKED);
    368             } catch (IllegalArgumentException e) {
    369                 Log.w("colsMap", e.getMessage());
    370             }
    371 
    372             try {
    373                 mColumnSmsErrorCode = cursor.getColumnIndexOrThrow(Sms.ERROR_CODE);
    374             } catch (IllegalArgumentException e) {
    375                 Log.w("colsMap", e.getMessage());
    376             }
    377 
    378             try {
    379                 mColumnMmsSubject = cursor.getColumnIndexOrThrow(Mms.SUBJECT);
    380             } catch (IllegalArgumentException e) {
    381                 Log.w("colsMap", e.getMessage());
    382             }
    383 
    384             try {
    385                 mColumnMmsSubjectCharset = cursor.getColumnIndexOrThrow(Mms.SUBJECT_CHARSET);
    386             } catch (IllegalArgumentException e) {
    387                 Log.w("colsMap", e.getMessage());
    388             }
    389 
    390             try {
    391                 mColumnMmsMessageType = cursor.getColumnIndexOrThrow(Mms.MESSAGE_TYPE);
    392             } catch (IllegalArgumentException e) {
    393                 Log.w("colsMap", e.getMessage());
    394             }
    395 
    396             try {
    397                 mColumnMmsMessageBox = cursor.getColumnIndexOrThrow(Mms.MESSAGE_BOX);
    398             } catch (IllegalArgumentException e) {
    399                 Log.w("colsMap", e.getMessage());
    400             }
    401 
    402             try {
    403                 mColumnMmsDeliveryReport = cursor.getColumnIndexOrThrow(Mms.DELIVERY_REPORT);
    404             } catch (IllegalArgumentException e) {
    405                 Log.w("colsMap", e.getMessage());
    406             }
    407 
    408             try {
    409                 mColumnMmsReadReport = cursor.getColumnIndexOrThrow(Mms.READ_REPORT);
    410             } catch (IllegalArgumentException e) {
    411                 Log.w("colsMap", e.getMessage());
    412             }
    413 
    414             try {
    415                 mColumnMmsErrorType = cursor.getColumnIndexOrThrow(PendingMessages.ERROR_TYPE);
    416             } catch (IllegalArgumentException e) {
    417                 Log.w("colsMap", e.getMessage());
    418             }
    419 
    420             try {
    421                 mColumnMmsLocked = cursor.getColumnIndexOrThrow(Mms.LOCKED);
    422             } catch (IllegalArgumentException e) {
    423                 Log.w("colsMap", e.getMessage());
    424             }
    425         }
    426     }
    427 
    428 }
    429