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

* commit 'a2f21654b52cceb019fcc792166921cc15b92e35':
  Externally Reported Low Severity Security Vulnerability: SMS Resend Vulnerability in Android
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bfe46ff..743686a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -295,10 +295,10 @@
         </provider>
 
         <service android:name=".ui.NoConfirmationSendService"
-                 android:permission="android.permission.SEND_SMS_NO_CONFIRMATION"
+                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
                  android:exported="true" >
             <intent-filter>
-                <action android:name="com.android.mms.intent.action.SENDTO_NO_CONFIRMATION" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="sms" />
                 <data android:scheme="smsto" />
diff --git a/apptests/AndroidManifest.xml b/apptests/AndroidManifest.xml
index f38330a..41966db 100644
--- a/apptests/AndroidManifest.xml
+++ b/apptests/AndroidManifest.xml
@@ -22,6 +22,7 @@
         that the new SendIntent works because the app will crash because no service
         to handle the intent (without SEND_SMS permission) can be found. -->
     <uses-permission android:name="android.permission.SEND_SMS_NO_CONFIRMATION" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <!-- We add an application tag here just so that we can indicate that
          this package needs to link against the android.test library,
diff --git a/apptests/src/com/android/mms/tests/SmsSendIntentTestActivity.java b/apptests/src/com/android/mms/tests/SmsSendIntentTestActivity.java
index 5902646..8af9bf8 100644
--- a/apptests/src/com/android/mms/tests/SmsSendIntentTestActivity.java
+++ b/apptests/src/com/android/mms/tests/SmsSendIntentTestActivity.java
@@ -17,9 +17,11 @@
 package com.android.mms.apptests;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -56,7 +58,9 @@
         mRecipient = (EditText)findViewById(R.id.sms_recipient);
         mMessage = (EditText)findViewById(R.id.sms_content);
 
-        mRecipient.setText("650-933-0884"); // use this to prime a number
+        String line1Number = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
+                .getLine1Number();
+        mRecipient.setText(line1Number); // use this to prime a number
 
         Button sendButton = (Button) findViewById(R.id.sms_send_message);
         sendButton.setOnClickListener(new OnClickListener() {
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 776df6f..b8c713f 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -34,7 +34,7 @@
     <string name="menu_view" msgid="7448852683948080108">"スレッド一覧表示"</string>
     <string name="menu_debug_dump" msgid="6855869130206549643">"ダンプをデバッグ"</string>
     <string name="refreshing" msgid="987335057871404222">"更新中..."</string>
-    <string name="menu_cell_broadcasts" msgid="5646161375983084660">"エリアメール"</string>
+    <string name="menu_cell_broadcasts" msgid="5646161375983084660">"緊急警報"</string>
     <string name="has_draft" msgid="2487465595514636160">"下書き"</string>
     <string name="no_subject_view" msgid="7795086723069563537">"(件名なし)"</string>
     <string name="messagelist_sender_self" msgid="3579016854116401602">"自分"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 0f9dca9..b4e872c 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -20,7 +20,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="1503967887341230795">"Mensag."</string>
+    <string name="app_label" msgid="1503967887341230795">"Mensagens"</string>
     <string name="new_message" msgid="1530369762346003973">"Nova mensagem"</string>
     <string name="menu_call_back" msgid="7538482331117914146">"Ligar a <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="menu_send_email" msgid="671366308915241664">"Enviar e-mail a <xliff:g id="NAME">%s</xliff:g>"</string>
@@ -206,7 +206,7 @@
     <string name="from_label" msgid="2055117571548171397">"De: "</string>
     <string name="to_address_label" msgid="5837363600471845801">"Para: "</string>
     <string name="bcc_label" msgid="530867161453958774">"Bcc: "</string>
-    <string name="sent_label" msgid="2722190650145251584">"Enviada: "</string>
+    <string name="sent_label" msgid="2722190650145251584">"Enviadas: "</string>
     <string name="received_label" msgid="8618029024731693906">"Recebida: "</string>
     <string name="saved_label" msgid="4183160415593488207">"Guardada: "</string>
     <string name="subject_label" msgid="8721241914144101631">"Assunto: "</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 14228bf..b9c8fca 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -278,7 +278,7 @@
     <string name="copy_to_sdcard" msgid="757028609638184856">"Uložiť prílohu"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Príloha bola uložená."</string>
     <string name="copy_to_sdcard_fail" msgid="4944606369631916737">"Prílohu sa nepodarilo uložiť."</string>
-    <string name="save_ringtone" msgid="2310157075045201051">"Uložiť ako vyzváňací tón"</string>
+    <string name="save_ringtone" msgid="2310157075045201051">"Uložiť ako tón zvonenia"</string>
     <string name="saved_ringtone" msgid="3847363932276708626">"Tón zvonenia bol uložený."</string>
     <string name="saved_ringtone_fail" msgid="4778500070919251116">"Tón zvonenia sa nepodarilo uložiť."</string>
     <string name="menu_insert_smiley" msgid="4964504393061025071">"Vložiť smajlík"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 2a96b33..aa8005e 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -153,8 +153,8 @@
     <string name="preferences_title" msgid="6650089610332670157">"Mipangilio"</string>
     <string name="restore_default" msgid="7165341506551155053">"Rejesha mipangilio ya chaguo-msingi"</string>
     <string name="pref_notification_settings_title" msgid="6420782563630369776">"Arifa"</string>
-    <string name="pref_mms_settings_title" msgid="55618772118264355">"Jumbe za midia-anuai (MMS)"</string>
-    <string name="pref_sms_settings_title" msgid="5173078369851066881">"Jumbe fupi (SMS)"</string>
+    <string name="pref_mms_settings_title" msgid="55618772118264355">"MMS"</string>
+    <string name="pref_sms_settings_title" msgid="5173078369851066881">"SMS"</string>
     <string name="pref_sms_storage_title" msgid="9001233319190616445">"Hifadhi"</string>
     <string name="pref_summary_manage_sim_messages" msgid="4141349892597640864">"Dhibiti ujumbe uliohifadhiwa kwenye kadi yako ya SIM"</string>
     <string name="pref_summary_mms_delivery_reports" msgid="4874657984217756112">"Omba ripoti ya kutuma kwa kila ujumbe unaotuma"</string>
@@ -190,7 +190,7 @@
     <string name="confirm_delete_all_conversations" msgid="9036166685235092757">"Tungo zote zitafutwa."</string>
     <string name="confirm_delete_message" msgid="9121480656609809591">"Video itafutwa."</string>
     <string name="confirm_delete_locked_message" msgid="7203411948190100955">"Futa ujumbe huu uliofungwa?"</string>
-    <string name="confirm_delete_all_SIM_messages" msgid="8693652297557966665">"Jumbe zote kwenye sim kadi zitafutwa."</string>
+    <string name="confirm_delete_all_SIM_messages" msgid="8693652297557966665">"Ujumbe wowote ulio kwenye SIM kadi utafutwa."</string>
     <string name="confirm_delete_SIM_message" msgid="8535128079045452425">"Ujumbe ulio kwenye SIM kadi utafutwa."</string>
     <string name="delete_unlocked" msgid="7545321606698162261">"Futa ujumbe uliofungwa"</string>
     <string name="delete" msgid="1409973060081564612">"Futa"</string>
@@ -226,7 +226,7 @@
     <string name="sim_delete" msgid="610790510655316922">"Futa"</string>
     <string name="sim_manage_messages_title" msgid="3989147182100584333">"Ujumbe wa maandishi kwenye SIM kadi"</string>
     <string name="sim_view" msgid="1997173541766393706">"Ona"</string>
-    <string name="sim_empty" msgid="2356766833071636297">"Hakuna  jumbe kwenye \'SIM kadi\'."</string>
+    <string name="sim_empty" msgid="2356766833071636297">"Hakuna ujumbe wowote kwenye SIM kadi."</string>
     <string name="delivery_header_title" msgid="5361719578869045764">"Ripoti"</string>
     <string name="status_none" msgid="8253075950774894961">"(Hamna)"</string>
     <string name="status_pending" msgid="2739860824607984892">"Inasubiri"</string>
@@ -252,7 +252,7 @@
     <skip />
     <string name="notification_multiple" msgid="7684007285202109490">"Ujumbe <xliff:g id="COUNT">%s</xliff:g>usiosomwa"</string>
     <string name="notification_multiple_title" msgid="332602028959557541">"Ujumbe mpya wa"</string>
-    <string name="notification_failed_multiple" msgid="6192531993698497229">"Jumbe <xliff:g id="COUNT">%s</xliff:g>  hazikutumwa"</string>
+    <string name="notification_failed_multiple" msgid="6192531993698497229">"MMS <xliff:g id="COUNT">%s</xliff:g> hazikutumwa"</string>
     <string name="notification_failed_multiple_title" msgid="1112032024904397126">"Ujumbe haujatumwa"</string>
     <string name="sim_full_title" msgid="1809829051697702810">"Kadi ya SIM imejaa"</string>
     <string name="sim_full_body" msgid="6241664980790322925">"Futa ujumbe mwingine ili kuweka nafasi ya zaidi."</string>
@@ -316,19 +316,19 @@
     <string name="pref_mms_clear_search_history_summary" msgid="7960005384066460035">"Futa utafutaji wa awali wa ujumbe ili usionekane katika kisanduku cha Kutafuta"</string>
     <string name="save" msgid="6847069284991531310">"Hifadhi"</string>
     <string name="storage_limits_title" msgid="7074684882530693016">"Dhibiti ujumbe"</string>
-    <string name="storage_limits_message" msgid="2010501485394745696">"Weka kikomo kwa idadi ya jumbe unazohifadhi kwa kila mazungumzo?"</string>
+    <string name="storage_limits_message" msgid="2010501485394745696">"Weka kikomo cha idadi ya mazungumzo unayohifadhi kwa kila mazungumzo?"</string>
     <string name="storage_limits_setting" msgid="4952781049308537373">"Weka vikomo"</string>
     <string name="storage_limits_setting_dismiss" msgid="1433841310158458034">"Hakuna vikomo"</string>
-    <string name="too_many_unsent_mms" msgid="4436493698891224126">"Haiwezi kutuma ujumbe sasa hivi. Jumbe nyingi za midia-anuai hazijatumwa."</string>
+    <string name="too_many_unsent_mms" msgid="4436493698891224126">"Haiwezi kutuma ujumbe sasa hivi. MMS nyingi ambazo hazijatumwa."</string>
     <string name="sending_message" msgid="2054406576361149715">"INATUMA..."</string>
     <string name="pick_too_many_recipients" msgid="650087588867628044">"Wapokeaji wengi mno"</string>
     <string name="adding_recipients" msgid="2962810172527532357">"Inaongeza wapokeaji..."</string>
     <string name="draft_separator" msgid="5402575086540243019">", "</string>
     <string name="message_count_format" msgid="4434763220590778012">" <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
-    <string name="message_count_notification" msgid="3629968600032690007">"Jumbe <xliff:g id="NUMBER">%1$s</xliff:g> mpya"</string>
+    <string name="message_count_notification" msgid="3629968600032690007">"Mawasiliano <xliff:g id="NUMBER">%1$s</xliff:g> mapya"</string>
   <plurals name="message_count_notification_overflow">
     <item quantity="one" msgid="3340591167508150806">"+<xliff:g id="NUMBER">%1$s</xliff:g> ujumbe mwingine"</item>
-    <item quantity="other" msgid="1393002799298501480">"+<xliff:g id="NUMBER">%1$s</xliff:g> jumbe zingine"</item>
+    <item quantity="other" msgid="1393002799298501480">"+ mawasiliano mengine <xliff:g id="NUMBER">%1$s</xliff:g>"</item>
   </plurals>
     <string name="error_state" msgid="5177386717626893507">"Hali isiyofanana"</string>
     <string name="error_state_text" msgid="7918866106335387710">"Hali ya nyuzi na wapokeaji haiendani. Tafadhali chukua ripoti kuondoa hitilfu na iripoti kupitia http://go/droidanizer"</string>
diff --git a/src/com/android/mms/TempFileProvider.java b/src/com/android/mms/TempFileProvider.java
index 6e71df7..016c55d 100644
--- a/src/com/android/mms/TempFileProvider.java
+++ b/src/com/android/mms/TempFileProvider.java
@@ -60,7 +60,7 @@
         return 0;
     }
 
-    private ParcelFileDescriptor getTempStoreFd() {
+    private ParcelFileDescriptor getTempStoreFd(String mode) {
         String fileName = getScrapPath(getContext());
         ParcelFileDescriptor pfd = null;
 
@@ -75,9 +75,15 @@
                 return null;
             }
 
-            pfd = ParcelFileDescriptor.open(file,
-                    ParcelFileDescriptor.MODE_READ_WRITE
-                            | android.os.ParcelFileDescriptor.MODE_CREATE);
+            int modeFlags;
+            if (mode.equals("r")) {
+                modeFlags = ParcelFileDescriptor.MODE_READ_ONLY;
+            } else {
+                modeFlags = ParcelFileDescriptor.MODE_READ_WRITE
+                            | ParcelFileDescriptor.MODE_CREATE
+                            | ParcelFileDescriptor.MODE_TRUNCATE;
+            }
+            pfd = ParcelFileDescriptor.open(file, modeFlags);
         } catch (Exception ex) {
             Log.e(TAG, "getTempStoreFd: error creating pfd for " + fileName, ex);
         }
@@ -104,7 +110,7 @@
 
         switch (match) {
             case MMS_SCRAP_SPACE:
-                fd = getTempStoreFd();
+                fd = getTempStoreFd(mode);
                 break;
         }
 
diff --git a/src/com/android/mms/transaction/MessagingNotification.java b/src/com/android/mms/transaction/MessagingNotification.java
index e52ad3f..a1d7395 100644
--- a/src/com/android/mms/transaction/MessagingNotification.java
+++ b/src/com/android/mms/transaction/MessagingNotification.java
@@ -165,7 +165,7 @@
 
     private static OnDeletedReceiver sNotificationDeletedReceiver = new OnDeletedReceiver();
     private static Intent sNotificationOnDeleteIntent;
-    private static Handler sToastHandler = new Handler();
+    private static Handler sHandler = new Handler();
     private static PduPersister sPduPersister;
     private static final int MAX_BITMAP_DIMEN_DP = 360;
     private static float sScreenDensity;
@@ -307,9 +307,17 @@
             return;
         }
         Uri ringtoneUri = Uri.parse(ringtoneStr);
-        NotificationPlayer player = new NotificationPlayer(LogTag.APP);
+        final NotificationPlayer player = new NotificationPlayer(LogTag.APP);
         player.play(context, ringtoneUri, false, AudioManager.STREAM_NOTIFICATION,
                 IN_CONVERSATION_NOTIFICATION_VOLUME);
+
+        // Stop the sound after five seconds to handle continuous ringtones
+        sHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                player.stop();
+            }
+        }, 5000);
     }
 
     /**
@@ -784,7 +792,7 @@
             return;
         }
 
-        sToastHandler.post(new Runnable() {
+        sHandler.post(new Runnable() {
             @Override
             public void run() {
                 Toast.makeText(context, message, (int)timeMillis).show();
diff --git a/src/com/android/mms/transaction/Transaction.java b/src/com/android/mms/transaction/Transaction.java
index adc1722..ddfe8df 100644
--- a/src/com/android/mms/transaction/Transaction.java
+++ b/src/com/android/mms/transaction/Transaction.java
@@ -211,57 +211,31 @@
         ConnectivityManager connMgr =
                 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
 
-        int inetAddr;
+        InetAddress inetAddr;
         if (settings.isProxySet()) {
             String proxyAddr = settings.getProxyAddress();
-            inetAddr = lookupHost(proxyAddr);
-            if (inetAddr == -1) {
-                throw new IOException("Cannot establish route for " + url + ": Unknown host");
-            } else {
-                if (!connMgr.requestRouteToHost(
-                        ConnectivityManager.TYPE_MOBILE_MMS, inetAddr)) {
-                    throw new IOException("Cannot establish route to proxy " + inetAddr);
-                }
+            try {
+              inetAddr = InetAddress.getByName(proxyAddr);
+            } catch (UnknownHostException e) {
+                throw new IOException("Cannot establish route for " + url +
+                                      ": Unknown proxy " + proxyAddr);
+            }
+            if (!connMgr.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr)) {
+                throw new IOException("Cannot establish route to proxy " + inetAddr);
             }
         } else {
             Uri uri = Uri.parse(url);
-            inetAddr = lookupHost(uri.getHost());
-            if (inetAddr == -1) {
+            try {
+                inetAddr = InetAddress.getByName(uri.getHost());
+            } catch (UnknownHostException e) {
                 throw new IOException("Cannot establish route for " + url + ": Unknown host");
-            } else {
-                if (!connMgr.requestRouteToHost(
-                        ConnectivityManager.TYPE_MOBILE_MMS, inetAddr)) {
-                    throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
-                }
+            }
+            if (!connMgr.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr)) {
+                throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
             }
         }
     }
 
-    /**
-     * Look up a host name and return the result as an int. Works if the argument
-     * is an IP address in dot notation. Obviously, this can only be used for IPv4
-     * addresses.
-     * @param hostname the name of the host (or the IP address)
-     * @return the IP address as an {@code int} in network byte order
-     */
-    // TODO: move this to android-common
-    public static int lookupHost(String hostname) {
-        InetAddress inetAddress;
-        try {
-            inetAddress = InetAddress.getByName(hostname);
-        } catch (UnknownHostException e) {
-            return -1;
-        }
-        byte[] addrBytes;
-        int addr;
-        addrBytes = inetAddress.getAddress();
-        addr = ((addrBytes[3] & 0xff) << 24)
-                | ((addrBytes[2] & 0xff) << 16)
-                | ((addrBytes[1] & 0xff) << 8)
-                |  (addrBytes[0] & 0xff);
-        return addr;
-    }
-
     @Override
     public String toString() {
         return getClass().getName() + ": serviceId=" + mServiceId;
diff --git a/src/com/android/mms/transaction/TransactionService.java b/src/com/android/mms/transaction/TransactionService.java
index 3de1f71..f869920 100644
--- a/src/com/android/mms/transaction/TransactionService.java
+++ b/src/com/android/mms/transaction/TransactionService.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.mms.LogTag;
 import com.android.mms.R;
+import com.android.mms.util.DownloadManager;
 import com.android.mms.util.RateController;
 import com.google.android.mms.pdu.GenericPdu;
 import com.google.android.mms.pdu.NotificationInd;
@@ -213,7 +214,7 @@
                     int count = cursor.getCount();
 
                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
-                        Log.v(TAG, "onNewIntent: cursor.count=" + count);
+                        Log.v(TAG, "onNewIntent: cursor.count=" + count + " action=" + action);
                     }
 
                     if (count == 0) {
@@ -232,6 +233,10 @@
                     while (cursor.moveToNext()) {
                         int msgType = cursor.getInt(columnIndexOfMsgType);
                         int transactionType = getTransactionType(msgType);
+                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                            Log.v(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +
+                                    transactionType);
+                        }
                         if (noNetwork) {
                             onNetworkUnavailable(serviceId, transactionType);
                             return;
@@ -247,12 +252,36 @@
                                 int failureType = cursor.getInt(
                                         cursor.getColumnIndexOrThrow(
                                                 PendingMessages.ERROR_TYPE));
-                                if ((failureType != MmsSms.NO_ERROR ||
-                                        !ACTION_ENABLE_AUTO_RETRIEVE.equals(action)) &&
-                                        !isTransientFailure(failureType)) {
+                                DownloadManager downloadManager = DownloadManager.getInstance();
+                                boolean autoDownload = downloadManager.isAuto();
+                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                                    Log.v(TAG, "onNewIntent: failureType=" + failureType +
+                                            " action=" + action + " isTransientFailure:" +
+                                            isTransientFailure(failureType) + " autoDownload=" +
+                                            autoDownload);
+                                }
+                                if (!autoDownload) {
+                                    // If autodownload is turned off, don't process the
+                                    // transaction.
+                                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                                        Log.v(TAG, "onNewIntent: skipping - autodownload off");
+                                    }
                                     break;
                                 }
-                                // fall-through
+                                // Logic is twisty. If there's no failure or the failure
+                                // is a non-permanent failure, we want to process the transaction.
+                                // Otherwise, break out and skip processing this transaction.
+                                if (!(failureType == MmsSms.NO_ERROR ||
+                                        isTransientFailure(failureType))) {
+                                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                                        Log.v(TAG, "onNewIntent: skipping - permanent error");
+                                    }
+                                    break;
+                                }
+                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                                    Log.v(TAG, "onNewIntent: falling through and processing");
+                                }
+                               // fall-through
                             default:
                                 Uri uri = ContentUris.withAppendedId(
                                         Mms.CONTENT_URI,
@@ -260,6 +289,9 @@
                                 TransactionBundle args = new TransactionBundle(
                                         transactionType, uri.toString());
                                 // FIXME: We use the same startId for all MMs.
+                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                                    Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);
+                                }
                                 launchTransaction(serviceId, args, false);
                                 break;
                         }
@@ -297,7 +329,7 @@
     }
 
     private static boolean isTransientFailure(int type) {
-        return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR);
+        return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
     }
 
     private boolean isNetworkAvailable() {
diff --git a/src/com/android/mms/ui/ComposeMessageActivity.java b/src/com/android/mms/ui/ComposeMessageActivity.java
index 020a0e6..12b421c 100644
--- a/src/com/android/mms/ui/ComposeMessageActivity.java
+++ b/src/com/android/mms/ui/ComposeMessageActivity.java
@@ -228,6 +228,9 @@
 
     private static final long NO_DATE_FOR_DIALOG = -1L;
 
+    private static final String KEY_EXIT_ON_SENT = "exit_on_sent";
+    private static final String KEY_FORWARDED_MESSAGE = "forwarded_message";
+
     private static final String EXIT_ECM_RESULT = "exit_ecm_result";
 
     // When the conversation has a lot of messages and a new message is sent, the list is scrolled
@@ -256,8 +259,10 @@
 
     private Conversation mConversation;     // Conversation we are working in
 
-    private boolean mExitOnSent;            // Should we finish() after sending a message?
-                                            // TODO: mExitOnSent is obsolete -- remove
+    // When mSendDiscreetMode is true, this activity only allows a user to type in and send
+    // a single sms, send the message, and then exits. The message history and menus are hidden.
+    private boolean mSendDiscreetMode;
+    private boolean mForwardMessageMode;
 
     private View mTopPanel;                 // View containing the recipient and subject editors
     private View mBottomPanel;              // View containing the text editor, send button, ec.
@@ -1250,8 +1255,8 @@
                 // on the UI thread.
                 Intent intent = createIntent(ComposeMessageActivity.this, 0);
 
-                intent.putExtra("exit_on_sent", true);
-                intent.putExtra("forwarded_message", true);
+                intent.putExtra(KEY_EXIT_ON_SENT, true);
+                intent.putExtra(KEY_FORWARDED_MESSAGE, true);
                 if (mTempThreadId > 0) {
                     intent.putExtra(THREAD_ID, mTempThreadId);
                 }
@@ -1611,6 +1616,9 @@
                 if (isDrm) {
                     extension += DrmUtils.getConvertExtension(type);
                 }
+                // Remove leading periods. The gallery ignores files starting with a period.
+                fileName = fileName.replaceAll("^.", "");
+
                 File file = getUniqueDestination(dir + fileName, extension);
 
                 // make sure the path is valid and directories created for this file.
@@ -2175,7 +2183,7 @@
      * @param debugFlag shows where this is being called from
      */
     private void loadMessagesAndDraft(int debugFlag) {
-        if (!mMessagesAndDraftLoaded) {
+        if (!mSendDiscreetMode && !mMessagesAndDraftLoaded) {
             if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
                 Log.v(TAG, "### CMA.loadMessagesAndDraft: flag=" + debugFlag);
             }
@@ -2217,8 +2225,11 @@
 
         mWorkingMessage.writeStateToBundle(outState);
 
-        if (mExitOnSent) {
-            outState.putBoolean("exit_on_sent", mExitOnSent);
+        if (mSendDiscreetMode) {
+            outState.putBoolean(KEY_EXIT_ON_SENT, mSendDiscreetMode);
+        }
+        if (mForwardMessageMode) {
+            outState.putBoolean(KEY_FORWARDED_MESSAGE, mForwardMessageMode);
         }
     }
 
@@ -2610,6 +2621,12 @@
 
         menu.clear();
 
+        if (mSendDiscreetMode && !mForwardMessageMode) {
+            // When we're in send-a-single-message mode from the lock screen, don't show
+            // any menus.
+            return true;
+        }
+
         if (isRecipientCallable()) {
             MenuItem item = menu.add(0, MENU_CALL_RECIPIENT, 0, R.string.menu_call)
                 .setIcon(R.drawable.ic_menu_call)
@@ -3194,7 +3211,7 @@
 
         // If this is a forwarded message, it will have an Intent extra
         // indicating so.  If not, bail out.
-        if (intent.getBooleanExtra("forwarded_message", false) == false) {
+        if (!mForwardMessageMode) {
             return false;
         }
 
@@ -3543,6 +3560,9 @@
     }
 
     private void startMsgListQuery(int token) {
+        if (mSendDiscreetMode) {
+            return;
+        }
         Uri conversationUri = mConversation.getUri();
 
         if (conversationUri == null) {
@@ -3587,7 +3607,7 @@
         mMsgListAdapter.setMsgListItemHandler(mMessageListItemHandler);
         mMsgListView.setAdapter(mMsgListAdapter);
         mMsgListView.setItemsCanFocus(false);
-        mMsgListView.setVisibility(View.VISIBLE);
+        mMsgListView.setVisibility(mSendDiscreetMode ? View.INVISIBLE : View.VISIBLE);
         mMsgListView.setOnCreateContextMenuListener(mMsgListMenuCreateListener);
         mMsgListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
@@ -3731,7 +3751,7 @@
             mScrollOnSend = true;   // in the next onQueryComplete, scroll the list to the end.
         }
         // But bail out if we are supposed to exit after the message is sent.
-        if (mExitOnSent) {
+        if (mSendDiscreetMode) {
             finish();
         }
     }
@@ -3838,7 +3858,12 @@
                     ContactList.getByNumbers(recipients,
                             false /* don't block */, true /* replace number */), false);
             addRecipientsListeners();
-            mExitOnSent = bundle.getBoolean("exit_on_sent", false);
+            mSendDiscreetMode = bundle.getBoolean(KEY_EXIT_ON_SENT, false);
+            mForwardMessageMode = bundle.getBoolean(KEY_FORWARDED_MESSAGE, false);
+
+            if (mSendDiscreetMode) {
+                mMsgListView.setVisibility(View.INVISIBLE);
+            }
             mWorkingMessage.readStateFromBundle(bundle);
 
             return;
@@ -3872,7 +3897,11 @@
         addRecipientsListeners();
         updateThreadIdIfRunning();
 
-        mExitOnSent = intent.getBooleanExtra("exit_on_sent", false);
+        mSendDiscreetMode = intent.getBooleanExtra(KEY_EXIT_ON_SENT, false);
+        mForwardMessageMode = intent.getBooleanExtra(KEY_FORWARDED_MESSAGE, false);
+        if (mSendDiscreetMode) {
+            mMsgListView.setVisibility(View.INVISIBLE);
+        }
         if (intent.hasExtra("sms_body")) {
             mWorkingMessage.setText(intent.getStringExtra("sms_body"));
         }
diff --git a/src/com/android/mms/ui/ConversationList.java b/src/com/android/mms/ui/ConversationList.java
index ac71baa..884f484 100644
--- a/src/com/android/mms/ui/ConversationList.java
+++ b/src/com/android/mms/ui/ConversationList.java
@@ -171,6 +171,9 @@
     public void onPause() {
         super.onPause();
 
+        // Don't listen for changes while we're paused.
+        mListAdapter.setOnContentChangedListener(null);
+
         // Remember where the list is scrolled to so we can restore the scroll position
         // when we come back to this activity and *after* we complete querying for the
         // conversations.
@@ -180,6 +183,13 @@
         mSavedFirstItemOffset = (firstChild == null) ? 0 : firstChild.getTop();
     }
 
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mListAdapter.setOnContentChangedListener(mContentChangedListener);
+    }
+
     private void setupActionBar() {
         ActionBar actionBar = getActionBar();
 
diff --git a/src/com/android/mms/ui/NoConfirmationSendService.java b/src/com/android/mms/ui/NoConfirmationSendService.java
index f1ae0fa..bbd6519 100644
--- a/src/com/android/mms/ui/NoConfirmationSendService.java
+++ b/src/com/android/mms/ui/NoConfirmationSendService.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -38,8 +39,6 @@
         setIntentRedelivery(true);
     }
 
-    public static final String SEND_NO_CONFIRM_INTENT_ACTION =
-        "com.android.mms.intent.action.SENDTO_NO_CONFIRMATION";
     private static final String TAG = "Mms/NoConfirmationSendService";
 
     @Override
@@ -47,7 +46,7 @@
         ComposeMessageActivity.log("NoConfirmationSendService onHandleIntent");
 
         String action = intent.getAction();
-        if (!SEND_NO_CONFIRM_INTENT_ACTION.equals(action)) {
+        if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(action)) {
             ComposeMessageActivity.log("NoConfirmationSendService onHandleIntent wrong action: " +
                     action);
             return;
diff --git a/src/com/android/mms/ui/RecipientsAdapter.java b/src/com/android/mms/ui/RecipientsAdapter.java
deleted file mode 100644
index bafb410..0000000
--- a/src/com/android/mms/ui/RecipientsAdapter.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * 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.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.DataUsageFeedback;
-import android.telephony.PhoneNumberUtils;
-import android.text.Annotation;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.ResourceCursorAdapter;
-import android.widget.TextView;
-
-import com.android.mms.MmsApp;
-import com.android.mms.R;
-import com.android.mms.data.Contact;
-
-/**
- * This adapter is used to filter contacts on both name and number.
- */
-public class RecipientsAdapter extends ResourceCursorAdapter {
-
-    public static final int CONTACT_ID_INDEX = 1;
-    public static final int TYPE_INDEX       = 2;
-    public static final int NUMBER_INDEX     = 3;
-    public static final int LABEL_INDEX      = 4;
-    public static final int NAME_INDEX       = 5;
-    public static final int NORMALIZED_NUMBER = 6;
-
-    private static final String[] PROJECTION_PHONE = {
-        Phone._ID,                  // 0
-        Phone.CONTACT_ID,           // 1
-        Phone.TYPE,                 // 2
-        Phone.NUMBER,               // 3
-        Phone.LABEL,                // 4
-        Phone.DISPLAY_NAME,         // 5
-        Phone.NORMALIZED_NUMBER,    // 6
-    };
-
-    private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC,"
-            + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final String mDefaultCountryIso;
-
-    public RecipientsAdapter(Context context) {
-        // Note that the RecipientsAdapter doesn't support auto-requeries. If we
-        // want to respond to changes in the contacts we're displaying in the drop-down,
-        // code using this adapter would have to add a line such as:
-        //   mRecipientsAdapter.setOnDataSetChangedListener(mDataSetChangedListener);
-        // See ComposeMessageActivity for an example.
-        super(context, R.layout.recipient_filter_item, null, false /* no auto-requery */);
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-        mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso();
-    }
-
-    @Override
-    public final CharSequence convertToString(Cursor cursor) {
-        String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX);
-        if (number == null) {
-            return "";
-        }
-        number = number.trim();
-
-        String name = cursor.getString(RecipientsAdapter.NAME_INDEX);
-        int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX);
-
-        String label = cursor.getString(RecipientsAdapter.LABEL_INDEX);
-        CharSequence displayLabel = Phone.getDisplayLabel(mContext, type, label);
-
-        if (name == null) {
-            name = "";
-        } else {
-            // Names with commas are the bane of the recipient editor's existence.
-            // We've worked around them by using spans, but there are edge cases
-            // where the spans get deleted. Furthermore, having commas in names
-            // can be confusing to the user since commas are used as separators
-            // between recipients. The best solution is to simply remove commas
-            // from names.
-            name = name.replace(", ", " ")
-                       .replace(",", " ");  // Make sure we leave a space between parts of names.
-        }
-
-        String nameAndNumber = Contact.formatNameAndNumber( name, number,
-                cursor.getString(NORMALIZED_NUMBER));
-
-        SpannableString out = new SpannableString(nameAndNumber);
-        int len = out.length();
-
-        if (!TextUtils.isEmpty(name)) {
-            out.setSpan(new Annotation("name", name), 0, len,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        } else {
-            out.setSpan(new Annotation("name", number), 0, len,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        }
-
-        String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX);
-        out.setSpan(new Annotation("person_id", person_id), 0, len,
-                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        out.setSpan(new Annotation("label", displayLabel.toString()), 0, len,
-                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        out.setSpan(new Annotation("number", number), 0, len,
-                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
-        return out;
-    }
-
-    @Override
-    public final void bindView(View view, Context context, Cursor cursor) {
-        TextView name = (TextView) view.findViewById(R.id.name);
-        name.setText(cursor.getString(NAME_INDEX));
-
-        TextView label = (TextView) view.findViewById(R.id.label);
-        int type = cursor.getInt(TYPE_INDEX);
-        CharSequence labelText = Phone.getDisplayLabel(mContext, type,
-                cursor.getString(LABEL_INDEX));
-        // When there's no label, getDisplayLabel() returns a CharSequence of length==1 containing
-        // a unicode non-breaking space. Need to check for that and consider that as "no label".
-        if (labelText.length() == 0 ||
-                (labelText.length() == 1 && labelText.charAt(0) == '\u00A0')) {
-            label.setVisibility(View.GONE);
-        } else {
-            label.setText(labelText);
-            label.setVisibility(View.VISIBLE);
-        }
-
-        TextView number = (TextView) view.findViewById(R.id.number);
-        number.setText(
-                PhoneNumberUtils.formatNumber(cursor.getString(NUMBER_INDEX),
-                        cursor.getString(NORMALIZED_NUMBER), mDefaultCountryIso));
-    }
-
-    @Override
-    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-        String phone = "";
-        String cons = null;
-
-        if (constraint != null) {
-            cons = constraint.toString();
-
-            if (usefulAsDigits(cons)) {
-                phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
-                if (phone.equals(cons)) {
-                    phone = "";
-                } else {
-                    phone = phone.trim();
-                }
-            }
-        }
-
-        Uri uri = Phone.CONTENT_FILTER_URI.buildUpon()
-                .appendPath(cons)
-                .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
-                        DataUsageFeedback.USAGE_TYPE_SHORT_TEXT)
-                .build();
-        /*
-         * if we decide to filter based on phone types use a selection
-         * like this.
-        String selection = String.format("%s=%s OR %s=%s OR %s=%s",
-                Phone.TYPE,
-                Phone.TYPE_MOBILE,
-                Phone.TYPE,
-                Phone.TYPE_WORK_MOBILE,
-                Phone.TYPE,
-                Phone.TYPE_MMS);
-         */
-        Cursor phoneCursor =
-            mContentResolver.query(uri,
-                    PROJECTION_PHONE,
-                    null, //selection,
-                    null,
-                    null);
-
-        if (phone.length() > 0) {
-            Object[] result = new Object[7];
-            result[0] = Integer.valueOf(-1);                    // ID
-            result[1] = Long.valueOf(-1);                       // CONTACT_ID
-            result[2] = Integer.valueOf(Phone.TYPE_CUSTOM);     // TYPE
-            result[3] = phone;                                  // NUMBER
-
-            /*
-             * The "\u00A0" keeps Phone.getDisplayLabel() from deciding
-             * to display the default label ("Home") next to the transformation
-             * of the letters into numbers.
-             */
-            result[4] = "\u00A0";                               // LABEL
-            result[5] = cons;                                   // NAME
-            result[6] = phone;                                  // NORMALIZED_NUMBER
-
-            MatrixCursor translated = new MatrixCursor(PROJECTION_PHONE, 1);
-            translated.addRow(result);
-            return new MergeCursor(new Cursor[] { translated, phoneCursor });
-        } else {
-            return phoneCursor;
-        }
-    }
-
-    /**
-     * Returns true if all the characters are meaningful as digits
-     * in a phone number -- letters, digits, and a few punctuation marks.
-     */
-    private boolean usefulAsDigits(CharSequence cons) {
-        int len = cons.length();
-
-        for (int i = 0; i < len; i++) {
-            char c = cons.charAt(i);
-
-            if ((c >= '0') && (c <= '9')) {
-                continue;
-            }
-            if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+')
-                    || (c == '#') || (c == '*')) {
-                continue;
-            }
-            if ((c >= 'A') && (c <= 'Z')) {
-                continue;
-            }
-            if ((c >= 'a') && (c <= 'z')) {
-                continue;
-            }
-
-            return false;
-        }
-
-        return true;
-    }
-}
diff --git a/src/com/android/mms/ui/SlideshowActivity.java b/src/com/android/mms/ui/SlideshowActivity.java
index c76b178..f85e573 100644
--- a/src/com/android/mms/ui/SlideshowActivity.java
+++ b/src/com/android/mms/ui/SlideshowActivity.java
@@ -377,6 +377,11 @@
         public boolean canSeekForward() {
             return true;
         }
+
+        @Override
+        public int getAudioSessionId() {
+            return 0;
+        }
     }
 
     public void handleEvent(Event evt) {