Java tutorial
/* * Copyright (C) 2008, 2013 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.aboveware.sms.ui; import java.util.regex.Pattern; import android.annotation.TargetApi; import android.content.Context; import android.database.Cursor; import android.os.Build; import android.os.Handler; import android.provider.Telephony.TextBasedSmsColumns; import android.support.v4.widget.CursorAdapter; 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.ListView; import com.aboveware.sms.R; import com.aboveware.sms.conversations.Conversation; import com.aboveware.sms.conversations.ConversationsDatabase; import com.aboveware.sms.conversations.Message; import com.aboveware.sms.conversations.PadConversations; /** * The back-end data adapter of a message list. */ public class MessageListAdapter extends CursorAdapter { @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private static class MessageItemCache extends LruCache<Long, MessageItem> { public MessageItemCache(int maxSize) { super(maxSize); } } public interface OnDataSetChangedListener { void onContentChanged(MessageListAdapter adapter); void onDataSetChanged(MessageListAdapter adapter); } private static final String TAG = MessageListAdapter.class.getSimpleName(); private static final int CACHE_SIZE = 50; public static int INCOMING_ITEM_TYPE_SMS = 0; public static int OUTGOING_ITEM_TYPE_SMS = 1; protected LayoutInflater mInflater; private final MessageItemCache mMessageItemCache; private OnDataSetChangedListener mOnDataSetChangedListener; private Handler mMsgListItemHandler; private Pattern mHighlight; private Context mContext; private boolean mIsGroupConversation; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public MessageListAdapter(Context context, ListView listView, Pattern highlight) { super(context, null, 0); mContext = context; mHighlight = highlight; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mMessageItemCache = new MessageItemCache(CACHE_SIZE); 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 boolean areAllItemsEnabled() { return true; } @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof MessageListItem) { String type = cursor.getString(ConversationsDatabase.messageTypeIndex); long msgId = cursor.getLong(ConversationsDatabase.messageIdIndex); MessageItem msgItem = getCachedMessageItem(type, msgId, cursor); if (msgItem != null) { MessageListItem mli = (MessageListItem) view; int position = cursor.getPosition(); mli.bind(msgItem, mIsGroupConversation, position); mli.setMsgListItemHandler(mMsgListItemHandler); } } } public void cancelBackgroundLoading() { mMessageItemCache.evictAll(); // causes entryRemoved to be called for each MessageItem in the cache which causes us to cancel // loading of background pdu's and images. } public MessageItem getCachedMessageItem(String type, long msgId, Cursor cursor) { MessageItem item = mMessageItemCache.get(msgId); if (item == null && cursor != null && isCursorValid(cursor)) { try { long threadId = cursor.getLong(ConversationsDatabase.messageThreadIdIndex); Conversation conversation = PadConversations.getConversation(threadId); // Skip the draft message, it has SMS id == 0 if (msgId != 0) { Message message = conversation.getMessage(msgId); if (message == null) Log.w(MessageListActivity.class.getName(), "MsgID " + msgId + " is null"); else Log.w(MessageListActivity.class.getName(), "MsgID " + msgId + message.getAddress()); item = new MessageItem(mContext, conversation.getPadRecipients().get(0).getContact(), type, message, mHighlight); mMessageItemCache.put(item.msgId, item); } } catch (Exception e) { Log.e(TAG, "getCachedMessageItem: ", e); } } return item; } public Cursor getCursorForItem(MessageItem item) { Cursor cursor = getCursor(); if (isCursorValid(cursor)) { if (cursor.moveToFirst()) { do { long id = cursor.getLong(mRowIDColumn); if (id == item.msgId) { return cursor; } } while (cursor.moveToNext()); } } return null; } private int getItemViewType(Cursor cursor) { int boxId = cursor.getInt(ConversationsDatabase.messageTypeIndex); // Note that messages from the SIM card all have a boxId of zero. return (boxId == TextBasedSmsColumns.MESSAGE_TYPE_INBOX || boxId == TextBasedSmsColumns.MESSAGE_TYPE_ALL) ? INCOMING_ITEM_TYPE_SMS : OUTGOING_ITEM_TYPE_SMS; } @Override public int getItemViewType(int position) { Cursor cursor = (Cursor) getItem(position); return getItemViewType(cursor); } public int getPosition(long msgId) { int position = 0; Cursor cursor = getCursor(); if (isCursorValid(cursor)) { if (cursor.moveToFirst()) { do { long id = cursor.getLong(mRowIDColumn); if (id == msgId) { return position; } ++position; } while (cursor.moveToNext()); } } return -1; } /* * 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 } private boolean isCursorValid(Cursor cursor) { // Check whether the cursor is valid or not. if (cursor == null || cursor.isClosed() || cursor.isBeforeFirst() || cursor.isAfterLast()) { return false; } return true; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(getItemViewType(cursor) == INCOMING_ITEM_TYPE_SMS ? R.layout.message_list_item_recv : R.layout.message_list_item_send, parent, false); } @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); mMessageItemCache.evictAll(); if (mOnDataSetChangedListener != null) { mOnDataSetChangedListener.onDataSetChanged(this); } } @Override protected void onContentChanged() { if (getCursor() != null && !getCursor().isClosed()) { if (mOnDataSetChangedListener != null) { mOnDataSetChangedListener.onContentChanged(this); } } } public void setIsGroupConversation(boolean isGroup) { mIsGroupConversation = isGroup; } public void setMsgListItemHandler(Handler handler) { mMsgListItemHandler = handler; } public void setOnDataSetChangedListener(OnDataSetChangedListener listener) { mOnDataSetChangedListener = listener; } }