am 979749e8: am 5de694a2: am a04cf879: am f4909bf3: am 5a726879: am 9da13d25: Externally Reported Low Severity Security Vulnerability: SMS Resend Vulnerability in Android

* commit '979749e84490cab29c0ba9c5e3fb08fa6beb9fbd':
  Externally Reported Low Severity Security Vulnerability: SMS Resend Vulnerability in Android
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 482705e..bfe46ff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -197,6 +197,9 @@
                 <action android:name="android.intent.action.CONTENT_CHANGED" />
             </intent-filter>
             <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+            <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
             </intent-filter>
         </receiver>
diff --git a/src/com/android/mms/MmsApp.java b/src/com/android/mms/MmsApp.java
index 74f4043..9092a29 100644
--- a/src/com/android/mms/MmsApp.java
+++ b/src/com/android/mms/MmsApp.java
@@ -19,6 +19,7 @@
 
 import android.app.Application;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.drm.DrmManagerClient;
 import android.location.Country;
@@ -34,6 +35,9 @@
 import com.android.mms.data.Conversation;
 import com.android.mms.layout.LayoutManager;
 import com.android.mms.transaction.MessagingNotification;
+import com.android.mms.transaction.MmsSystemEventReceiver;
+import com.android.mms.transaction.SmsReceiver;
+import com.android.mms.transaction.SmsReceiverService;
 import com.android.mms.util.DownloadManager;
 import com.android.mms.util.DraftCache;
 import com.android.mms.util.PduLoaderManager;
@@ -94,6 +98,23 @@
         LayoutManager.init(this);
         SmileyParser.init(this);
         MessagingNotification.init(this);
+
+        activePendingMessages();
+    }
+
+    /**
+     * Try to process all pending messages(which were interrupted by user, OOM, Mms crashing,
+     * etc...) when Mms app is (re)launched.
+     */
+    private void activePendingMessages() {
+        // For Mms: try to process all pending transactions if possible
+        MmsSystemEventReceiver.wakeUpService(this);
+
+        // For Sms: retry to send smses in outbox and queued box
+        sendBroadcast(new Intent(SmsReceiverService.ACTION_SEND_INACTIVE_MESSAGE,
+                null,
+                this,
+                SmsReceiver.class));
     }
 
     synchronized public static MmsApp getApplication() {
diff --git a/src/com/android/mms/data/Conversation.java b/src/com/android/mms/data/Conversation.java
index 91bea24..106cea2 100644
--- a/src/com/android/mms/data/Conversation.java
+++ b/src/com/android/mms/data/Conversation.java
@@ -13,6 +13,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.provider.BaseColumns;
@@ -30,10 +31,14 @@
 import com.android.mms.MmsApp;
 import com.android.mms.R;
 import com.android.mms.transaction.MessagingNotification;
+import com.android.mms.transaction.MmsMessageSender;
 import com.android.mms.ui.ComposeMessageActivity;
 import com.android.mms.ui.MessageUtils;
+import com.android.mms.util.AddressUtils;
 import com.android.mms.util.DraftCache;
 
+import com.google.android.mms.pdu.PduHeaders;
+
 /**
  * An interface for finding information about conversations and/or creating new ones.
  */
@@ -297,6 +302,42 @@
         }
     }
 
+    private void sendReadReport(final Context context,
+            final long threadId,
+            final int status) {
+        String selection = Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF
+            + " AND " + Mms.READ + " = 0"
+            + " AND " + Mms.READ_REPORT + " = " + PduHeaders.VALUE_YES;
+
+        if (threadId != -1) {
+            selection = selection + " AND " + Mms.THREAD_ID + " = " + threadId;
+        }
+
+        final Cursor c = SqliteWrapper.query(context, context.getContentResolver(),
+                        Mms.Inbox.CONTENT_URI, new String[] {Mms._ID, Mms.MESSAGE_ID},
+                        selection, null, null);
+
+        try {
+            if (c == null || c.getCount() == 0) {
+                return;
+            }
+
+            while (c.moveToNext()) {
+                Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, c.getLong(0));
+                if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
+                    LogTag.debug("sendReadReport: uri = " + uri);
+                }
+                MmsMessageSender.sendReadRec(context, AddressUtils.getFrom(context, uri),
+                                             c.getString(1), status);
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+
     /**
      * Marks all messages in this conversation as read and updates
      * relevant notifications.  This method returns immediately;
@@ -347,6 +388,7 @@
                     }
 
                     if (needUpdate) {
+                        sendReadReport(mContext, mThreadId, PduHeaders.READ_STATUS_READ);
                         LogTag.debug("markAsRead: update read/seen for thread uri: " +
                                 threadUri);
                         mContext.getContentResolver().update(threadUri, sReadContentValues,
diff --git a/src/com/android/mms/model/LayoutModel.java b/src/com/android/mms/model/LayoutModel.java
index 97b1637..0280534 100644
--- a/src/com/android/mms/model/LayoutModel.java
+++ b/src/com/android/mms/model/LayoutModel.java
@@ -110,6 +110,11 @@
         if (mTextRegion == null) {
             createDefaultTextRegion();
         }
+        // LayoutModel will re-construct when orientation changes, so we need to
+        // initialize mLayoutType here. Otherwise, the mLayoutType is alway default
+        // value (LAYOUT_BOTTOM_TEXT) after LayoutModel re-construct.
+        mLayoutType =
+                (mImageRegion.getTop() == 0) ? LAYOUT_BOTTOM_TEXT : LAYOUT_TOP_TEXT;
     }
 
     public RegionModel getRootLayout() {
diff --git a/src/com/android/mms/model/VideoModel.java b/src/com/android/mms/model/VideoModel.java
index a426b42..a71e455 100644
--- a/src/com/android/mms/model/VideoModel.java
+++ b/src/com/android/mms/model/VideoModel.java
@@ -70,7 +70,8 @@
     }
 
     private void initFromFile(Uri uri) {
-        mSrc = uri.getPath();
+        String path = uri.getPath();
+        mSrc = path.substring(path.lastIndexOf('/') + 1);
         MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
         String extension = MimeTypeMap.getFileExtensionFromUrl(mSrc);
         if (TextUtils.isEmpty(extension)) {
diff --git a/src/com/android/mms/transaction/MmsSystemEventReceiver.java b/src/com/android/mms/transaction/MmsSystemEventReceiver.java
index b8eb917..9b78ea0 100644
--- a/src/com/android/mms/transaction/MmsSystemEventReceiver.java
+++ b/src/com/android/mms/transaction/MmsSystemEventReceiver.java
@@ -20,13 +20,12 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.net.Uri;
 import android.provider.Telephony.Mms;
 import android.util.Log;
 
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.mms.LogTag;
 import com.android.mms.MmsApp;
 
@@ -43,9 +42,9 @@
  */
 public class MmsSystemEventReceiver extends BroadcastReceiver {
     private static final String TAG = "MmsSystemEventReceiver";
-    private static MmsSystemEventReceiver sMmsSystemEventReceiver;
+    private static ConnectivityManager mConnMgr = null;
 
-    private static void wakeUpService(Context context) {
+    public static void wakeUpService(Context context) {
         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
             Log.v(TAG, "wakeUpService: start transaction service ...");
         }
@@ -63,14 +62,23 @@
         if (action.equals(Mms.Intents.CONTENT_CHANGED_ACTION)) {
             Uri changed = (Uri) intent.getParcelableExtra(Mms.Intents.DELETED_CONTENTS);
             MmsApp.getApplication().getPduLoaderManager().removePdu(changed);
-        } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
-            String state = intent.getStringExtra(PhoneConstants.STATE_KEY);
+        } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+            if (mConnMgr == null) {
+                mConnMgr = (ConnectivityManager) context
+                        .getSystemService(Context.CONNECTIVITY_SERVICE);
+            }
+            NetworkInfo mmsNetworkInfo = mConnMgr
+                    .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+            boolean available = mmsNetworkInfo.isAvailable();
+            boolean isConnected = mmsNetworkInfo.isConnected();
 
             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                Log.v(TAG, "ANY_DATA_STATE event received: " + state);
+                Log.v(TAG, "TYPE_MOBILE_MMS available = " + available +
+                           ", isConnected = " + isConnected);
             }
 
-            if (state.equals("CONNECTED")) {
+            // Wake up transact service when MMS data is available and isn't connected.
+            if (available && !isConnected) {
                 wakeUpService(context);
             }
         } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
@@ -79,34 +87,10 @@
             // Called on the UI thread so don't block.
             MessagingNotification.nonBlockingUpdateNewMessageIndicator(
                     context, MessagingNotification.THREAD_NONE, false);
-        }
-    }
 
-    public static void registerForConnectionStateChanges(Context context) {
-        unRegisterForConnectionStateChanges(context);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
-        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-            Log.v(TAG, "registerForConnectionStateChanges");
-        }
-        if (sMmsSystemEventReceiver == null) {
-            sMmsSystemEventReceiver = new MmsSystemEventReceiver();
-        }
-
-        context.registerReceiver(sMmsSystemEventReceiver, intentFilter);
-    }
-
-    public static void unRegisterForConnectionStateChanges(Context context) {
-        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-            Log.v(TAG, "unRegisterForConnectionStateChanges");
-        }
-        if (sMmsSystemEventReceiver != null) {
-            try {
-                context.unregisterReceiver(sMmsSystemEventReceiver);
-            } catch (IllegalArgumentException e) {
-                // Allow un-matched register-unregister calls
-            }
+            // Scan and send pending Mms once after boot completed since
+            // ACTION_ANY_DATA_CONNECTION_STATE_CHANGED wasn't registered in a whole life cycle
+            wakeUpService(context);
         }
     }
 }
diff --git a/src/com/android/mms/transaction/ReadRecTransaction.java b/src/com/android/mms/transaction/ReadRecTransaction.java
index d424860..bcbfa62 100644
--- a/src/com/android/mms/transaction/ReadRecTransaction.java
+++ b/src/com/android/mms/transaction/ReadRecTransaction.java
@@ -42,11 +42,12 @@
  * <li>Notifies the TransactionService about succesful completion.
  * </ul>
  */
-public class ReadRecTransaction extends Transaction {
+public class ReadRecTransaction extends Transaction implements Runnable{
     private static final String TAG = "ReadRecTransaction";
     private static final boolean DEBUG = false;
     private static final boolean LOCAL_LOGV = false;
 
+    private Thread mThread;
     private final Uri mReadReportURI;
 
     public ReadRecTransaction(Context context,
@@ -67,6 +68,11 @@
      */
     @Override
     public void process() {
+        mThread = new Thread(this, "ReadRecTransaction");
+        mThread.start();
+    }
+
+    public void run() {
         PduPersister persister = PduPersister.getPduPersister(mContext);
 
         try {
diff --git a/src/com/android/mms/transaction/SmsReceiverService.java b/src/com/android/mms/transaction/SmsReceiverService.java
index 724e863..4985edf 100755
--- a/src/com/android/mms/transaction/SmsReceiverService.java
+++ b/src/com/android/mms/transaction/SmsReceiverService.java
@@ -81,7 +81,9 @@
     public static final String EXTRA_MESSAGE_SENT_SEND_NEXT ="SendNextMsg";
 
     public static final String ACTION_SEND_MESSAGE =
-        "com.android.mms.transaction.SEND_MESSAGE";
+            "com.android.mms.transaction.SEND_MESSAGE";
+    public static final String ACTION_SEND_INACTIVE_MESSAGE =
+            "com.android.mms.transaction.SEND_INACTIVE_MESSAGE";
 
     // This must match the column IDs below.
     private static final String[] SEND_PROJECTION = new String[] {
@@ -209,6 +211,8 @@
                     handleServiceStateChanged(intent);
                 } else if (ACTION_SEND_MESSAGE.endsWith(action)) {
                     handleSendMessage();
+                } else if (ACTION_SEND_INACTIVE_MESSAGE.equals(action)) {
+                    handleSendInactiveMessage();
                 }
             }
             // NOTE: We MUST not call stopSelf() directly, since we need to
@@ -231,6 +235,12 @@
         }
     }
 
+    private void handleSendInactiveMessage() {
+        // Inactive messages includes all messages in outbox and queued box.
+        moveOutboxMessagesToQueuedBox();
+        sendFirstQueuedMessage();
+    }
+
     public synchronized void sendFirstQueuedMessage() {
         boolean success = true;
         // get all the queued messages from the database
@@ -272,6 +282,12 @@
                         mSending = false;
                         messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
                         success = false;
+                        // Sending current message fails. Try to send more pending messages
+                        // if there is any.
+                        sendBroadcast(new Intent(SmsReceiverService.ACTION_SEND_MESSAGE,
+                                null,
+                                this,
+                                SmsReceiver.class));
                     }
                 }
             } finally {
@@ -388,6 +404,24 @@
     }
 
     /**
+     * Move all messages that are in the outbox to the queued state
+     * @return The number of messages that were actually moved
+     */
+    private int moveOutboxMessagesToQueuedBox() {
+        ContentValues values = new ContentValues(1);
+
+        values.put(Sms.TYPE, Sms.MESSAGE_TYPE_QUEUED);
+
+        int messageCount = SqliteWrapper.update(
+                getApplicationContext(), getContentResolver(), Outbox.CONTENT_URI,
+                values, "type = " + Sms.MESSAGE_TYPE_OUTBOX, null);
+        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
+            Log.v(TAG, "moveOutboxMessagesToQueuedBox messageCount: " + messageCount);
+        }
+        return messageCount;
+    }
+
+    /**
      * Move all messages that are in the outbox to the failed state and set them to unread.
      * @return The number of messages that were actually moved
      */
diff --git a/src/com/android/mms/transaction/TransactionBundle.java b/src/com/android/mms/transaction/TransactionBundle.java
index 5962b90..ea3edc0 100644
--- a/src/com/android/mms/transaction/TransactionBundle.java
+++ b/src/com/android/mms/transaction/TransactionBundle.java
@@ -19,7 +19,7 @@
 
 import android.os.Bundle;
 
-import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.uicc.IccUtils;
 
 /**
  * A wrapper around the Bundle instances used to start the TransactionService.
diff --git a/src/com/android/mms/transaction/TransactionService.java b/src/com/android/mms/transaction/TransactionService.java
index 7fd2715..3de1f71 100644
--- a/src/com/android/mms/transaction/TransactionService.java
+++ b/src/com/android/mms/transaction/TransactionService.java
@@ -229,15 +229,6 @@
                     int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(
                             PendingMessages.MSG_TYPE);
 
-                    if (noNetwork) {
-                        // Make sure we register for connection state changes.
-                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                            Log.v(TAG, "onNewIntent: registerForConnectionStateChanges");
-                        }
-                        MmsSystemEventReceiver.registerForConnectionStateChanges(
-                                getApplicationContext());
-                    }
-
                     while (cursor.moveToNext()) {
                         int msgType = cursor.getInt(columnIndexOfMsgType);
                         int transactionType = getTransactionType(msgType);
@@ -299,11 +290,6 @@
                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                     Log.v(TAG, "stopSelfIfIdle: STOP!");
                 }
-                // Make sure we're no longer listening for connection state changes.
-                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                    Log.v(TAG, "stopSelfIfIdle: unRegisterForConnectionStateChanges");
-                }
-                MmsSystemEventReceiver.unRegisterForConnectionStateChanges(getApplicationContext());
 
                 stopSelf(startId);
             }
@@ -315,8 +301,12 @@
     }
 
     private boolean isNetworkAvailable() {
-        NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
-        return (ni == null ? false : ni.isAvailable());
+        if (mConnMgr == null) {
+            return false;
+        } else {
+            NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+            return (ni == null ? false : ni.isAvailable());
+        }
     }
 
     private int getTransactionType(int msgType) {
@@ -469,7 +459,6 @@
             sendBroadcast(intent);
         } finally {
             transaction.detach(this);
-            MmsSystemEventReceiver.unRegisterForConnectionStateChanges(getApplicationContext());
             stopSelf(serviceId);
         }
     }
@@ -881,11 +870,15 @@
                 return;
             }
 
-            boolean noConnectivity =
-                intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+            NetworkInfo mmsNetworkInfo = null;
 
-            NetworkInfo networkInfo = (NetworkInfo)
-                intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+            if (mConnMgr != null) {
+                mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+            } else {
+                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                    Log.v(TAG, "mConnMgr is null, bail");
+                }
+            }
 
             /*
              * If we are being informed that connectivity has been established
@@ -894,47 +887,50 @@
              */
 
             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + networkInfo);
+                Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo);
             }
 
             // Check availability of the mobile network.
-            if ((networkInfo == null) || (networkInfo.getType() !=
-                    ConnectivityManager.TYPE_MOBILE_MMS)) {
+            if ((mmsNetworkInfo == null)) {
                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                    Log.v(TAG, "   type is not TYPE_MOBILE_MMS, bail");
+                    Log.v(TAG, "mms type is null, bail");
                 }
+            } else {
                 // This is a very specific fix to handle the case where the phone receives an
                 // incoming call during the time we're trying to setup the mms connection.
                 // When the call ends, restart the process of mms connectivity.
-                if (networkInfo != null &&
-                        Phone.REASON_VOICE_CALL_ENDED.equals(networkInfo.getReason())) {
+                if (Phone.REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) {
                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                         Log.v(TAG, "   reason is " + Phone.REASON_VOICE_CALL_ENDED +
                                 ", retrying mms connectivity");
                     }
                     renewMmsConnectivity();
+                    return;
                 }
-                return;
-            }
 
-            if (!networkInfo.isConnected()) {
-                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                    Log.v(TAG, "   TYPE_MOBILE_MMS not connected, bail");
+                if (mmsNetworkInfo.isConnected()) {
+                    TransactionSettings settings = new TransactionSettings(
+                            TransactionService.this, mmsNetworkInfo.getExtraInfo());
+                    // If this APN doesn't have an MMSC, wait for one that does.
+                    if (TextUtils.isEmpty(settings.getMmscUrl())) {
+                        Log.v(TAG, "   empty MMSC url, bail");
+                        return;
+                    }
+                    mServiceHandler.processPendingTransaction(null, settings);
+                } else {
+                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                        Log.v(TAG, "   TYPE_MOBILE_MMS not connected, bail");
+                    }
+
+                    // Retry mms connectivity once it's possible to connect
+                    if (mmsNetworkInfo.isAvailable()) {
+                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                            Log.v(TAG, "   retrying mms connectivity for it's available");
+                        }
+                        renewMmsConnectivity();
+                    }
                 }
-                return;
             }
-
-            TransactionSettings settings = new TransactionSettings(
-                    TransactionService.this, networkInfo.getExtraInfo());
-
-            // If this APN doesn't have an MMSC, wait for one that does.
-            if (TextUtils.isEmpty(settings.getMmscUrl())) {
-                Log.v(TAG, "   empty MMSC url, bail");
-                return;
-            }
-
-            renewMmsConnectivity();
-            mServiceHandler.processPendingTransaction(null, settings);
         }
     };
 }
diff --git a/src/com/android/mms/ui/IconListAdapter.java b/src/com/android/mms/ui/IconListAdapter.java
index e52a0d2..288be7e 100644
--- a/src/com/android/mms/ui/IconListAdapter.java
+++ b/src/com/android/mms/ui/IconListAdapter.java
@@ -35,7 +35,33 @@
 public class IconListAdapter extends ArrayAdapter<IconListAdapter.IconListItem> {
     protected LayoutInflater mInflater;
     private static final int mResource = R.layout.icon_list_item;
+    private ViewHolder mViewHolder;
 
+    static class ViewHolder {
+        private View mView;
+        private TextView mTextView;
+        private ImageView mImageView;
+
+        public ViewHolder(View view) {
+            mView = view;
+        }
+
+        public TextView getTextView() {
+            if (mTextView == null) {
+                mTextView = (TextView) mView.findViewById(R.id.text1);
+            }
+
+            return mTextView;
+        }
+
+        public ImageView getImageView() {
+            if (mImageView == null) {
+                mImageView = (ImageView) mView.findViewById(R.id.icon);
+            }
+
+            return mImageView;
+        }
+    }
     public IconListAdapter(Context context,
             List<IconListItem> items) {
         super(context, mResource, items);
@@ -44,22 +70,22 @@
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        TextView text;
-        ImageView image;
-
         View view;
         if (convertView == null) {
             view = mInflater.inflate(mResource, parent, false);
+            mViewHolder = new ViewHolder(view);
+            view.setTag(mViewHolder);
         } else {
             view = convertView;
+            mViewHolder = (ViewHolder) view.getTag();
         }
 
         // Set text field
-        text = (TextView) view.findViewById(R.id.text1);
+        TextView text = mViewHolder.getTextView();
         text.setText(getItem(position).getTitle());
 
         // Set resource icon
-        image = (ImageView) view.findViewById(R.id.icon);
+        ImageView image = mViewHolder.getImageView();
         image.setImageResource(getItem(position).getResource());
 
         return view;
diff --git a/src/com/android/mms/ui/ManageSimMessages.java b/src/com/android/mms/ui/ManageSimMessages.java
index beadb54..e783294 100644
--- a/src/com/android/mms/ui/ManageSimMessages.java
+++ b/src/com/android/mms/ui/ManageSimMessages.java
@@ -150,6 +150,8 @@
                 // Let user know the SIM is empty
                 updateState(SHOW_EMPTY);
             }
+            // Show option menu when query complete.
+            invalidateOptionsMenu();
         }
     }
 
diff --git a/src/com/android/mms/ui/MessageListItem.java b/src/com/android/mms/ui/MessageListItem.java
index 880b7b6..ffe06fb 100644
--- a/src/com/android/mms/ui/MessageListItem.java
+++ b/src/com/android/mms/ui/MessageListItem.java
@@ -212,6 +212,7 @@
         mDateView.setText(buildTimestampLine(msgSizeText + " " + mMessageItem.mTimestamp));
 
         switch (mMessageItem.getMmsDownloadStatus()) {
+            case DownloadManager.STATE_PRE_DOWNLOADING:
             case DownloadManager.STATE_DOWNLOADING:
                 showDownloadingAttachment();
                 break;
@@ -246,6 +247,9 @@
                         intent.putExtra(TransactionBundle.TRANSACTION_TYPE,
                                 Transaction.RETRIEVE_TRANSACTION);
                         mContext.startService(intent);
+
+                        DownloadManager.getInstance().markState(
+                                    mMessageItem.mMessageUri, DownloadManager.STATE_PRE_DOWNLOADING);
                     }
                 });
                 break;
diff --git a/src/com/android/mms/ui/MessageUtils.java b/src/com/android/mms/ui/MessageUtils.java
index 57075f8..502bfde 100644
--- a/src/com/android/mms/ui/MessageUtils.java
+++ b/src/com/android/mms/ui/MessageUtils.java
@@ -904,6 +904,7 @@
         // Launch the slideshow activity to play/view.
         Intent intent = new Intent(context, SlideshowActivity.class);
         intent.setData(msgUri);
+        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         if (requestCode > 0 && context instanceof Activity) {
             ((Activity)context).startActivityForResult(intent, requestCode);
         } else {
diff --git a/src/com/android/mms/ui/RecipientsEditor.java b/src/com/android/mms/ui/RecipientsEditor.java
index 7cbb066..4de2118 100644
--- a/src/com/android/mms/ui/RecipientsEditor.java
+++ b/src/com/android/mms/ui/RecipientsEditor.java
@@ -280,6 +280,13 @@
     }
 
     private int pointToPosition(int x, int y) {
+        // Check layout before getExtendedPaddingTop().
+        // mLayout is used in getExtendedPaddingTop().
+        Layout layout = getLayout();
+        if (layout == null) {
+            return -1;
+        }
+
         x -= getCompoundPaddingLeft();
         y -= getExtendedPaddingTop();
 
@@ -287,11 +294,6 @@
         x += getScrollX();
         y += getScrollY();
 
-        Layout layout = getLayout();
-        if (layout == null) {
-            return -1;
-        }
-
         int line = layout.getLineForVertical(y);
         int off = layout.getOffsetForHorizontal(line, x);
 
diff --git a/src/com/android/mms/ui/SlideshowAttachmentView.java b/src/com/android/mms/ui/SlideshowAttachmentView.java
index 22dccef..03d50e5 100644
--- a/src/com/android/mms/ui/SlideshowAttachmentView.java
+++ b/src/com/android/mms/ui/SlideshowAttachmentView.java
@@ -125,7 +125,7 @@
     }
 
     public void reset() {
-        mImageView.setImageURI(null);
+        mImageView.setImageBitmap(null);
         mTextView.setText("");
     }
 
diff --git a/src/com/android/mms/ui/SlideshowPresenter.java b/src/com/android/mms/ui/SlideshowPresenter.java
index 64af07b..acb7a01 100644
--- a/src/com/android/mms/ui/SlideshowPresenter.java
+++ b/src/com/android/mms/ui/SlideshowPresenter.java
@@ -200,7 +200,7 @@
         }
 
         if (dataChanged) {
-            view.setImage(image.getSrc(), image.getBitmap(r.getWidth(), r.getHeight()));
+            view.setImage(image.getSrc(), image.getBitmap(transformedWidth, transformedHeight));
         }
 
         if (view instanceof AdaptableSlideViewInterface) {
diff --git a/src/com/android/mms/util/DownloadManager.java b/src/com/android/mms/util/DownloadManager.java
index 5210597..3e061e3 100644
--- a/src/com/android/mms/util/DownloadManager.java
+++ b/src/com/android/mms/util/DownloadManager.java
@@ -57,6 +57,7 @@
     public static final int STATE_DOWNLOADING       = 0x81;
     public static final int STATE_TRANSIENT_FAILURE = 0x82;
     public static final int STATE_PERMANENT_FAILURE = 0x87;
+    public static final int STATE_PRE_DOWNLOADING   = 0x88;
 
     private final Context mContext;
     private final Handler mHandler;
diff --git a/src/com/android/mms/util/ThumbnailManager.java b/src/com/android/mms/util/ThumbnailManager.java
index 3834168..681ba46 100644
--- a/src/com/android/mms/util/ThumbnailManager.java
+++ b/src/com/android/mms/util/ThumbnailManager.java
@@ -252,6 +252,8 @@
                 bitmap = getBitmap(mIsVideo);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Couldn't load bitmap for " + mUri, e);
+            } catch (OutOfMemoryError e) {
+                Log.e(TAG, "Couldn't load bitmap for " + mUri, e);
             }
             final Bitmap resultBitmap = bitmap;
 
diff --git a/src/com/android/mms/widget/MmsWidgetProvider.java b/src/com/android/mms/widget/MmsWidgetProvider.java
index a050f68..99b7903 100644
--- a/src/com/android/mms/widget/MmsWidgetProvider.java
+++ b/src/com/android/mms/widget/MmsWidgetProvider.java
@@ -67,10 +67,8 @@
                     MmsWidgetProvider.class));
 
             // We need to update all Mms appwidgets on the home screen.
-            for (int appWidgetId : appWidgetIds) {
-                appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId,
-                        R.id.conversation_list);
-            }
+            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds,
+                    R.id.conversation_list);
         } else {
             super.onReceive(context, intent);
         }
diff --git a/src/com/android/mms/widget/MmsWidgetService.java b/src/com/android/mms/widget/MmsWidgetService.java
index 4497e67..a0644a8 100644
--- a/src/com/android/mms/widget/MmsWidgetService.java
+++ b/src/com/android/mms/widget/MmsWidgetService.java
@@ -16,6 +16,7 @@
 
 package com.android.mms.widget;
 
+import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.Intent;
@@ -183,9 +184,8 @@
             if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) {
                 Log.v(TAG, "getConversationCount");
             }
-            synchronized (sWidgetLock) {
-                return Math.min(mConversationCursor.getCount(), MAX_CONVERSATIONS_COUNT);
-            }
+
+            return Math.min(mConversationCursor.getCount(), MAX_CONVERSATIONS_COUNT);
         }
 
         /*
@@ -301,8 +301,11 @@
             RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
             view.setTextViewText(
                     R.id.loading_text, mContext.getText(R.string.view_more_conversations));
-            view.setOnClickFillInIntent(R.id.widget_loading,
-                   new Intent(mContext, ConversationList.class));
+            PendingIntent pendingIntent =
+                    PendingIntent.getActivity(mContext, 0, new Intent(mContext,
+                            ConversationList.class),
+                            PendingIntent.FLAG_UPDATE_CURRENT);
+            view.setOnClickPendingIntent(R.id.widget_loading, pendingIntent);
             return view;
         }