• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MessageListAdapter( Context context, Cursor c, ListView listView, boolean useDefaultColumnsMap, Pattern highlight)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
bindView(View view, Context context, Cursor cursor)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 {
onDataSetChanged(MessageListAdapter adapter)162         void onDataSetChanged(MessageListAdapter adapter);
onContentChanged(MessageListAdapter adapter)163         void onContentChanged(MessageListAdapter adapter);
164     }
165 
setOnDataSetChangedListener(OnDataSetChangedListener l)166     public void setOnDataSetChangedListener(OnDataSetChangedListener l) {
167         mOnDataSetChangedListener = l;
168     }
169 
setMsgListItemHandler(Handler handler)170     public void setMsgListItemHandler(Handler handler) {
171         mMsgListItemHandler = handler;
172     }
173 
174     @Override
notifyDataSetChanged()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
onContentChanged()189     protected void onContentChanged() {
190         if (getCursor() != null && !getCursor().isClosed()) {
191             if (mOnDataSetChangedListener != null) {
192                 mOnDataSetChangedListener.onContentChanged(this);
193             }
194         }
195     }
196 
197     @Override
newView(Context context, Cursor cursor, ViewGroup parent)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 
getCachedMessageItem(String type, long msgId, Cursor c)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 
isCursorValid(Cursor cursor)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 
getKey(String type, long id)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
areAllItemsEnabled()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
getViewTypeCount()248     public int getViewTypeCount() {
249         return 2;   // Incoming and outgoing messages
250     }
251 
252     @Override
getItemViewType(int position)253     public int getItemViewType(int position) {
254         Cursor cursor = (Cursor)getItem(position);
255         return getItemViewType(cursor);
256     }
257 
getItemViewType(Cursor cursor)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 
ColumnsMap()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 
ColumnsMap(Cursor cursor)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