Merge "Mms: Upon already initialized, return immediately"
diff --git a/Android.mk b/Android.mk
index 0d36b29..634d6d8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -29,6 +29,8 @@
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
+LOCAL_PRIVILEGED_MODULE := true
+
 include $(BUILD_PACKAGE)
 
 # This finds and builds the test apk as well, so a single make does both.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cd48a26..33ab25c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -41,7 +41,12 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.INSTALL_DRM" />
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
-
+    <!-- System apps can access the  receiver through intent-->
+    <permission android:name="android.permission.MMS_SEND_OUTBOX_MSG"
+                android:protectionLevel="signatureOrSystem"
+                android:label="@string/label_mms_send_outbox_msg"
+                android:description="@string/desc_mms_send_outbox_msg"/>
+    <uses-permission android:name="android.permission.MMS_SEND_OUTBOX_MSG"/>
     <application android:name="MmsApp"
             android:label="@string/app_label"
             android:icon="@mipmap/ic_launcher_smsmms"
@@ -176,6 +181,7 @@
         <activity android:name=".ui.ClassZeroActivity"
             android:label="@string/class_0_message_activity"
             android:theme="@android:style/Theme.Translucent"
+            android:launchMode="singleTop"
             android:excludeFromRecents="true">
         </activity>
 
@@ -187,16 +193,24 @@
         <receiver android:name=".transaction.PushReceiver"
             android:permission="android.permission.BROADCAST_WAP_PUSH">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
                 <data android:mimeType="application/vnd.wap.mms-message" />
             </intent-filter>
         </receiver>
-
+     <receiver android:name=".transaction.MmsPushOutboxMessages"
+            android:permission="android.permission.MMS_SEND_OUTBOX_MSG">
+            <intent-filter>
+                <action android:name="android.intent.action.MMS_SEND_OUTBOX_MSG" />
+            </intent-filter>
+        </receiver>
         <receiver android:name=".transaction.MmsSystemEventReceiver">
             <intent-filter>
                 <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>
@@ -215,7 +229,7 @@
         <receiver android:name=".transaction.PrivilegedSmsReceiver"
             android:permission="android.permission.BROADCAST_SMS">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
             </intent-filter>
         </receiver>
 
@@ -291,10 +305,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" />
@@ -312,7 +326,12 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
+        <receiver android:name=".ui.SmsStorageMonitor" >
+            <intent-filter>
+                <action android:name="android.intent.action.DEVICE_STORAGE_FULL" />
+                <action android:name="android.intent.action.DEVICE_STORAGE_NOT_FULL" />
+            </intent-filter>
+        </receiver>
         <!-- Widget -->
         <receiver android:name=".widget.MmsWidgetProvider"  android:label="@string/app_label">
             <intent-filter>
@@ -327,7 +346,5 @@
         <service android:name=".widget.MmsWidgetService"
                  android:permission="android.permission.BIND_REMOTEVIEWS"
                  android:exported="false" />
-
-
     </application>
 </manifest>
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/drawable-hdpi/ic_launcher_smsmms.png b/res/drawable-hdpi/ic_launcher_smsmms.png
new file mode 100644
index 0000000..2c52f06
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_smsmms.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_emoticons.png b/res/drawable-hdpi/ic_menu_emoticons.png
deleted file mode 100644
index 4a1f744..0000000
--- a/res/drawable-hdpi/ic_menu_emoticons.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/list_pressed_holo.9.png b/res/drawable-hdpi/list_pressed_holo.9.png
deleted file mode 100644
index 5c9a34a..0000000
--- a/res/drawable-hdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/list_pressed_holo_light.9.png b/res/drawable-hdpi/list_pressed_holo_light.9.png
index 5654cd6..2054530 100644
--- a/res/drawable-hdpi/list_pressed_holo_light.9.png
+++ b/res/drawable-hdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_smsmms.png b/res/drawable-mdpi/ic_launcher_smsmms.png
new file mode 100644
index 0000000..a6d2f55
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_smsmms.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_emoticons.png b/res/drawable-mdpi/ic_menu_emoticons.png
deleted file mode 100644
index 765000b..0000000
--- a/res/drawable-mdpi/ic_menu_emoticons.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/list_pressed_holo.9.png b/res/drawable-mdpi/list_pressed_holo.9.png
deleted file mode 100644
index 5cc4ea1..0000000
--- a/res/drawable-mdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/list_pressed_holo_light.9.png b/res/drawable-mdpi/list_pressed_holo_light.9.png
index 6e77525..061904c 100644
--- a/res/drawable-mdpi/list_pressed_holo_light.9.png
+++ b/res/drawable-mdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_smsmms.png b/res/drawable-xhdpi/ic_launcher_smsmms.png
new file mode 100644
index 0000000..3c82a06
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_smsmms.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_emoticons.png b/res/drawable-xhdpi/ic_menu_emoticons.png
deleted file mode 100644
index d615c59..0000000
--- a/res/drawable-xhdpi/ic_menu_emoticons.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/list_pressed_holo.9.png b/res/drawable-xhdpi/list_pressed_holo.9.png
deleted file mode 100644
index 3c88a1a..0000000
--- a/res/drawable-xhdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/list_pressed_holo_light.9.png b/res/drawable-xhdpi/list_pressed_holo_light.9.png
index e4b3393..f4af926 100644
--- a/res/drawable-xhdpi/list_pressed_holo_light.9.png
+++ b/res/drawable-xhdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/attachment_editor_bg.9.png b/res/drawable-xxhdpi/attachment_editor_bg.9.png
new file mode 100644
index 0000000..7fcafb6
--- /dev/null
+++ b/res/drawable-xxhdpi/attachment_editor_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_angel.png b/res/drawable-xxhdpi/emo_im_angel.png
new file mode 100644
index 0000000..7d317e2
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_angel.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_cool.png b/res/drawable-xxhdpi/emo_im_cool.png
new file mode 100644
index 0000000..a05fabe
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_cool.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_crying.png b/res/drawable-xxhdpi/emo_im_crying.png
new file mode 100644
index 0000000..102800d
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_crying.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_embarrassed.png b/res/drawable-xxhdpi/emo_im_embarrassed.png
new file mode 100644
index 0000000..6e5d226
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_embarrassed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_foot_in_mouth.png b/res/drawable-xxhdpi/emo_im_foot_in_mouth.png
new file mode 100644
index 0000000..c328f8c
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_foot_in_mouth.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_happy.png b/res/drawable-xxhdpi/emo_im_happy.png
new file mode 100644
index 0000000..11e0163
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_happy.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_heart.png b/res/drawable-xxhdpi/emo_im_heart.png
new file mode 100644
index 0000000..42832e0
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_heart.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_kissing.png b/res/drawable-xxhdpi/emo_im_kissing.png
new file mode 100644
index 0000000..b929861
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_kissing.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_laughing.png b/res/drawable-xxhdpi/emo_im_laughing.png
new file mode 100644
index 0000000..4ed90bc
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_laughing.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_lips_are_sealed.png b/res/drawable-xxhdpi/emo_im_lips_are_sealed.png
new file mode 100644
index 0000000..bcbcf8d
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_lips_are_sealed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_mad.png b/res/drawable-xxhdpi/emo_im_mad.png
new file mode 100644
index 0000000..e1b2059
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_mad.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_money_mouth.png b/res/drawable-xxhdpi/emo_im_money_mouth.png
new file mode 100644
index 0000000..e17cf77
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_money_mouth.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_pokerface.png b/res/drawable-xxhdpi/emo_im_pokerface.png
new file mode 100644
index 0000000..b20d867
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_pokerface.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_sad.png b/res/drawable-xxhdpi/emo_im_sad.png
new file mode 100644
index 0000000..0696d99
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_sad.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_smirk.png b/res/drawable-xxhdpi/emo_im_smirk.png
new file mode 100644
index 0000000..b9b4300
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_smirk.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_surprised.png b/res/drawable-xxhdpi/emo_im_surprised.png
new file mode 100644
index 0000000..bd821d7
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_surprised.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_tongue_sticking_out.png b/res/drawable-xxhdpi/emo_im_tongue_sticking_out.png
new file mode 100644
index 0000000..af21474
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_tongue_sticking_out.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_undecided.png b/res/drawable-xxhdpi/emo_im_undecided.png
new file mode 100644
index 0000000..c43aa0b
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_undecided.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_winking.png b/res/drawable-xxhdpi/emo_im_winking.png
new file mode 100644
index 0000000..41cdd23
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_winking.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_wtf.png b/res/drawable-xxhdpi/emo_im_wtf.png
new file mode 100644
index 0000000..36f0b32
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_wtf.png
Binary files differ
diff --git a/res/drawable-xxhdpi/emo_im_yelling.png b/res/drawable-xxhdpi/emo_im_yelling.png
new file mode 100644
index 0000000..db210eb
--- /dev/null
+++ b/res/drawable-xxhdpi/emo_im_yelling.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_audio_holo_light.png b/res/drawable-xxhdpi/ic_attach_audio_holo_light.png
new file mode 100644
index 0000000..e984d96
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_audio_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_capture_audio_holo_light.png b/res/drawable-xxhdpi/ic_attach_capture_audio_holo_light.png
new file mode 100644
index 0000000..548d6ab
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_capture_audio_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_capture_picture_holo_light.png b/res/drawable-xxhdpi/ic_attach_capture_picture_holo_light.png
new file mode 100644
index 0000000..655a8ba
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_capture_picture_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_capture_video_holo_light.png b/res/drawable-xxhdpi/ic_attach_capture_video_holo_light.png
new file mode 100644
index 0000000..f4db487
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_capture_video_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_picture_holo_light.png b/res/drawable-xxhdpi/ic_attach_picture_holo_light.png
new file mode 100644
index 0000000..8e99fe2
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_picture_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_slideshow_holo_light.png b/res/drawable-xxhdpi/ic_attach_slideshow_holo_light.png
new file mode 100644
index 0000000..4104022
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_slideshow_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attach_video_holo_light.png b/res/drawable-xxhdpi/ic_attach_video_holo_light.png
new file mode 100644
index 0000000..bc9471d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attach_video_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attachment_universal_small.png b/res/drawable-xxhdpi/ic_attachment_universal_small.png
new file mode 100644
index 0000000..9f35e49
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attachment_universal_small.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dialog_attach.png b/res/drawable-xxhdpi/ic_dialog_attach.png
new file mode 100644
index 0000000..87ffab8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_dialog_attach.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_smsmms.png b/res/drawable-xxhdpi/ic_launcher_smsmms.png
new file mode 100644
index 0000000..0d61198
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_smsmms.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_list_alert_sms_failed.png b/res/drawable-xxhdpi/ic_list_alert_sms_failed.png
new file mode 100644
index 0000000..df98ef2
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_list_alert_sms_failed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_lock_message_sms.png b/res/drawable-xxhdpi/ic_lock_message_sms.png
new file mode 100644
index 0000000..5b640ea
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_lock_message_sms.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_attachment.png b/res/drawable-xxhdpi/ic_menu_attachment.png
new file mode 100644
index 0000000..548377a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_attachment.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_call.png b/res/drawable-xxhdpi/ic_menu_call.png
new file mode 100644
index 0000000..82f5049
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_call.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_msg_compose_holo_dark.png b/res/drawable-xxhdpi/ic_menu_msg_compose_holo_dark.png
new file mode 100644
index 0000000..8708602
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_msg_compose_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_search_holo_dark.png b/res/drawable-xxhdpi/ic_menu_search_holo_dark.png
new file mode 100644
index 0000000..cf4a022
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_search_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_trash_holo_dark.png b/res/drawable-xxhdpi/ic_menu_trash_holo_dark.png
new file mode 100644
index 0000000..3fd78d2
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_trash_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_send_disabled_holo_light.png b/res/drawable-xxhdpi/ic_send_disabled_holo_light.png
new file mode 100644
index 0000000..e8c4141
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_send_disabled_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_send_holo_light.png b/res/drawable-xxhdpi/ic_send_holo_light.png
new file mode 100644
index 0000000..b8c475e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_send_holo_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_mms_delivered.png b/res/drawable-xxhdpi/ic_sms_mms_delivered.png
new file mode 100644
index 0000000..598d88b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_mms_delivered.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_mms_details.png b/res/drawable-xxhdpi/ic_sms_mms_details.png
new file mode 100644
index 0000000..3e96c19
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_mms_details.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_mms_not_delivered.png b/res/drawable-xxhdpi/ic_sms_mms_not_delivered.png
new file mode 100644
index 0000000..f5f0e20
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_mms_not_delivered.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_mms_pending.png b/res/drawable-xxhdpi/ic_sms_mms_pending.png
new file mode 100644
index 0000000..ab55a9f
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_mms_pending.png
Binary files differ
diff --git a/res/drawable-xxhdpi/list_pressed_holo_light.9.png b/res/drawable-xxhdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..1352a17
--- /dev/null
+++ b/res/drawable-xxhdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/list_selected_holo_light.9.png b/res/drawable-xxhdpi/list_selected_holo_light.9.png
new file mode 100644
index 0000000..7cfb33d
--- /dev/null
+++ b/res/drawable-xxhdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_mms.png b/res/drawable-xxhdpi/stat_notify_mms.png
new file mode 100755
index 0000000..2dc5c09
--- /dev/null
+++ b/res/drawable-xxhdpi/stat_notify_mms.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_sms.png b/res/drawable-xxhdpi/stat_notify_sms.png
new file mode 100755
index 0000000..2c25016
--- /dev/null
+++ b/res/drawable-xxhdpi/stat_notify_sms.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_sms_failed.png b/res/drawable-xxhdpi/stat_notify_sms_failed.png
new file mode 100755
index 0000000..c01e6a1
--- /dev/null
+++ b/res/drawable-xxhdpi/stat_notify_sms_failed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_sys_no_sim.png b/res/drawable-xxhdpi/stat_sys_no_sim.png
new file mode 100755
index 0000000..e7512ed
--- /dev/null
+++ b/res/drawable-xxhdpi/stat_sys_no_sim.png
Binary files differ
diff --git a/res/drawable/banner_button_selectable.xml b/res/drawable/banner_button_selectable.xml
new file mode 100644
index 0000000..8eae254
--- /dev/null
+++ b/res/drawable/banner_button_selectable.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2012 Google Inc.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_pressed="true"
+        android:drawable="@color/banner_item_pressed" />
+    <item
+        android:state_activated="true"
+        android:drawable="@color/banner_item_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@color/banner_item_selected" />
+    <item
+        android:drawable="@color/banner_item_transparent" />
+</selector>
diff --git a/res/drawable/widget_conversation_read_selector.xml b/res/drawable/widget_conversation_read_selector.xml
index b262e21..38c8341 100644
--- a/res/drawable/widget_conversation_read_selector.xml
+++ b/res/drawable/widget_conversation_read_selector.xml
@@ -19,7 +19,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
-          android:drawable="@drawable/list_pressed_holo" />
+          android:drawable="@drawable/list_pressed_holo_light" />
     <item android:state_focused="true"
           android:drawable="@drawable/list_focused_holo" />
     <item android:state_selected="true"
diff --git a/res/drawable/widget_conversation_unread_selector.xml b/res/drawable/widget_conversation_unread_selector.xml
index 631152f..deb5493 100644
--- a/res/drawable/widget_conversation_unread_selector.xml
+++ b/res/drawable/widget_conversation_unread_selector.xml
@@ -19,7 +19,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
-          android:drawable="@drawable/list_pressed_holo" />
+          android:drawable="@drawable/list_pressed_holo_light" />
     <item android:state_focused="true"
           android:drawable="@drawable/list_focused_holo" />
     <item android:state_selected="true"
diff --git a/res/layout/banner_sms_promo.xml b/res/layout/banner_sms_promo.xml
new file mode 100644
index 0000000..860747b
--- /dev/null
+++ b/res/layout/banner_sms_promo.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2013 Google Inc.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/banner_sms_promo"
+    android:orientation="horizontal"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    android:paddingTop="16dip"
+    android:paddingBottom="16dip"
+    android:layout_width="match_parent"
+    android:layout_gravity="center_vertical"
+    android:layout_height="wrap_content"
+    android:background="@drawable/class_zero_background" >
+    <ImageView
+        android:id="@+id/banner_sms_default_app_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/ic_launcher_smsmms"
+        android:gravity="center_vertical"
+        android:importantForAccessibility="no" />
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="8dip"
+        android:gravity="center_vertical" >
+        <TextView
+            android:id="@+id/banner_sms_promo_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:paddingBottom="@dimen/banner_s_space"
+            android:singleLine="true"
+            android:text="@string/banner_sms_promo_title_initial"
+            style="@style/BannerTitleText" />
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:singleLine="false"
+            android:text="@string/banner_sms_promo_message"
+            style="@style/BannerContentText" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/conversation_list_screen.xml b/res/layout/conversation_list_screen.xml
index 1d3d91f..0d14f49 100644
--- a/res/layout/conversation_list_screen.xml
+++ b/res/layout/conversation_list_screen.xml
@@ -20,12 +20,14 @@
 
 <RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"  >
+    android:layout_height="match_parent"
+    android:focusable="true"  >
 
     <ListView android:id="@android:id/list"
         style="?android:attr/listViewWhiteStyle"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_below="@+id/banner_sms_promo"
         android:drawSelectorOnTop="false"
         android:scrollbarStyle="insideOverlay"
         android:background="@android:color/white"
@@ -38,4 +40,6 @@
         android:gravity="center"
         android:text="@string/loading_conversations"
         android:textAppearance="?android:attr/textAppearanceMedium" />
+
+     <include layout="@layout/banner_sms_promo" />
 </RelativeLayout>
diff --git a/res/layout/smiley_menu_item.xml b/res/layout/smiley_menu_item.xml
deleted file mode 100644
index 530254c..0000000
--- a/res/layout/smiley_menu_item.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
-    android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight">
-
-    <ImageView android:id="@+id/smiley_icon"
-        android:layout_gravity="center_vertical"
-        android:layout_marginLeft="18dip"
-        android:layout_width="20dip"
-        android:layout_height="20dip" />
-
-    <TextView android:id="@+id/smiley_name"
-        android:singleLine="true"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:layout_marginLeft="15dip"
-        android:layout_gravity="center_vertical"
-        android:layout_weight="1"
-        android:layout_width="0dip"
-        android:layout_height="wrap_content" />
-
-    <TextView android:id="@+id/smiley_text"
-        android:singleLine="true"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:layout_marginRight="10dip"
-        android:layout_gravity="center_vertical"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-
-</LinearLayout>
diff --git a/res/mipmap-xxhdpi/ic_launcher_smsmms.png b/res/mipmap-xxhdpi/ic_launcher_smsmms.png
index f7145a3..0d61198 100644
--- a/res/mipmap-xxhdpi/ic_launcher_smsmms.png
+++ b/res/mipmap-xxhdpi/ic_launcher_smsmms.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 5931255..9a2cb0e 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Verval: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Onafgelewerde boodskap"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kon nie hierdie boodskap stuur nie."\n"Gepoog: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kon nie hierdie boodskap stuur nie.\nGepoog: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Kon nie hierdie boodskap stuur nie."</string>
     <string name="delete_thread" msgid="757258847736632791">"Vee draad uit"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Stuur aan"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Teksboodskap-limiet"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimediaboodskap-limiet"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Kennisgewings"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Vertoon boodskapkennisgewings in statusbalk"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibreer"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibreer ook ter kennisgewing"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Kies luitoon"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Klank"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Stel aantal boodskappe om te stoor"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Altyd"</item>
-    <item msgid="6069709696037750627">"Net wanneer stil"</item>
-    <item msgid="7486145357487111435">"Nooit"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibreer"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Stil"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Herwin outomaties"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Gaan haal boodskappe outomaties"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Outoherwinning wanneer swerf"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Boodskap nie gestuur nie"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Raak om die boodskap te redigeer en probeer weer."</string>
     <string name="download_later" msgid="5531365714424360903">"Kan nie nou aflaai nie. Probeer later weer."</string>
+    <string name="no_apn" msgid="505932916503312015">"Geen APN is op hierdie toestel gespesifiseer nie."</string>
     <string name="select_audio" msgid="3528161449756771832">"Kies oudio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Stoor aanhegsel"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Aanhegsel gestoor."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Jou foon se berging is vol"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Jy sal nie nuwe SMS/MMS-boodskappe kry nie"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 0fccc99..d9517ea 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -28,7 +28,7 @@
     <string name="menu_preferences" msgid="4693148116114749414">"ቅንብሮች"</string>
     <string name="menu_add_address_to_contacts" msgid="4491980950419914944">"ወደ <xliff:g id="CONTACTEMAILORNUMBER">%s</xliff:g>ሰዎች አክል"</string>
     <string name="menu_call" msgid="5877123227307074690">"ደውል"</string>
-    <string name="menu_search" msgid="2289469305728821360">" ፈልግ"</string>
+    <string name="menu_search" msgid="2289469305728821360">"ፍለጋ"</string>
     <string name="menu_delete_all" msgid="808729454898114735">"ሁሉንም ክሮች ሰርዝ"</string>
     <string name="menu_delete" msgid="1851666911396479006">"ክር ሰርዝ"</string>
     <string name="menu_view" msgid="7448852683948080108">"የፍርግም ክር"</string>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"ያበቃል: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"ያልደረሰ መልዕክት"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"ይህን መልዕክት ለመላክ  አልተቻለም።"\n" ሙከራ ተደርጓል፡<xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"ይህን መልዕክት ለመላክ  አልተቻለም።\n ሙከራ ተደርጓል፡<xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"ይህን መልዕክት መላክ አልተቻለም።"</string>
     <string name="delete_thread" msgid="757258847736632791">"ክር ሰርዝ"</string>
     <string name="menu_forward" msgid="9026858380050046756">"አስተላልፍ"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"የፅሁፍ መልዕክትወሰን"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"ማህደረ ብዙ መረጃ መልዕክት ወሰን"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"ማሳወቂያዎች"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"በሁኔታ አሞሌ ላይ የመልዕክት ማሳወቂያዎችን አሳይ"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"ንዘር"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"ስታሳውቅ ንዘር"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"የደወል ቅላጼ ምረጥ"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"ድምፅ"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"ለማስቀመጥ የመልዕክቶች ቁጥር አዘጋጅ"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"ዘወትር"</item>
-    <item msgid="6069709696037750627">"ፀጥታ ሲሆን ብቻ"</item>
-    <item msgid="7486145357487111435">"በፍፁም"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"ንዘር"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"ፀጥታ"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"በራስ- ሰርስረህ አውጣ"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"በራስ ሰር መልዕክቶች ሰርስረህ አውጣ"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"በእንቅስቃሴ ላይ በራስ- ሰርስረህ አውጣ"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"መልዕክት አልተላከም"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"መልዕክት ለመከለስ እና እንደገና ለመሞከር ንካ"</string>
     <string name="download_later" msgid="5531365714424360903">"አሁን ማውረድ አልተቻለም፡፡ እባክህ እንደገና ሞክር፡፡"</string>
+    <string name="no_apn" msgid="505932916503312015">"በመሣሪያው ላይ ምንም የተገለጸ ኤ ፒ ኤን የለም።"</string>
     <string name="select_audio" msgid="3528161449756771832">"አውዲዮ ምረጥ"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"አባሪ አስቀምጥ"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"አባሪ ተቀምጧል"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"፣ "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"የስልክዎ ማከማቻ ሙሉ ነው"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"አዲስ የኤስ ኤም ኤስ/ኤም ኤም ኤስ መልዕክቶች አይደርሱዎትም"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 277f590..2b21a68 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"تاريخ انتهاء الصلاحية: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"كيلوبايت"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"رسالة لم يتم تسليمها"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"يتعذر إرسال هذه الرسالة."\n"المحاولة التي تمت: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"يتعذر إرسال هذه الرسالة.\nالمحاولة التي تمت: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"تعذر إرسال هذه الرسالة."</string>
     <string name="delete_thread" msgid="757258847736632791">"حذف السلسلة"</string>
     <string name="menu_forward" msgid="9026858380050046756">"إعادة توجيه"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"حد الرسالة النصية"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"حد رسالة الوسائط المتعددة"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"التنبيهات"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"عرض تنبيهات الرسائل في شريط الحالة"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"اهتزاز"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"الاهتزاز عند إعلامي أيضًا"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"اختيار نغمة الرنين"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"الصوت"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"تعيين عدد الرسائل التي سيتم حفظها"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"دومًا"</item>
-    <item msgid="6069709696037750627">"فقط عندما يكون صامتًا"</item>
-    <item msgid="7486145357487111435">"مطلقًا"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"اهتزاز"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"صامت"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"استرداد تلقائي"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"استرداد الرسائل تلقائيًا"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"استرداد تلقائي للتجوال"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"لم يتم إرسال الرسالة"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"المس لمراجعة الرسالة والمحاولة مرة أخرى."</string>
     <string name="download_later" msgid="5531365714424360903">"لا يمكن التنزيل الآن. حاول مرة أخرى لاحقًا."</string>
+    <string name="no_apn" msgid="505932916503312015">"لم يتم تحديد APN على هذا الجهاز."</string>
     <string name="select_audio" msgid="3528161449756771832">"اختيار الصوت"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"حفظ المرفق"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"تم حفظ المرفق."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"، "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"تخزين الهاتف مكتمل"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"لن تتلقى أية رسائل قصيرة SMS/رسائل وسائط متعددة جديدة"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 84bbc37..be06da5 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Мінае: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"Кб"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Недастаўленыя паведамленні"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Немагчыма адправіць паведамленне."\n"Спроба: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Немагчыма адправіць паведамленне.\nСпроба: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Немагчыма адправіць гэта паведамленне."</string>
     <string name="delete_thread" msgid="757258847736632791">"Выдаліць галіну"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Наперад"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Абмежаванне тэкставых паведамленняў"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Абмежаванне мультымедыйных паведамленняў"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Апавяшчэнні"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Паказваць апавяшчэнні аб паведамленнях ў радку стану"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Вібраваць"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Таксама вібраваць, калі атрымана апавяшчэнне"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Выбраць рынгтон"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Гук"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Усталяваць колькасць паведамленняў для захавання"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Заўсёды"</item>
-    <item msgid="6069709696037750627">"Толькі ў рэжыме без гуку"</item>
-    <item msgid="7486145357487111435">"Ніколі"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Вібрацыя"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Ціхі рэжым"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Атрымліваць аўтаматычна"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Аўтаматычна атрымліваць паведамленні"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Аўтаматычна атрымліваць у роўмінгу"</string>
@@ -281,6 +274,8 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Паведамленне не адпраўленае"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Нацiснiце, каб прагледзець паведамленне, і паўтарыце спробу."</string>
     <string name="download_later" msgid="5531365714424360903">"Не атрымлiваецца спампаваць зараз. Паўтарыце спробу пазней."</string>
+    <!-- no translation found for no_apn (505932916503312015) -->
+    <skip />
     <string name="select_audio" msgid="3528161449756771832">"Выберыце аўдыё"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Захаваць укладанне"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Далучаны файл захаваны."</string>
@@ -349,4 +344,8 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g>—<xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <!-- no translation found for storage_warning_title (7124740686325942375) -->
+    <skip />
+    <!-- no translation found for storage_warning_content (1100367816649962354) -->
+    <skip />
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 8377321..697bfc3 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Изтича на: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"КБ"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Недоставено съобщение"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Това съобщение не можа да бъде изпратено."\n"Направен опит: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Това съобщение не можа да бъде изпратено.\nНаправен опит: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Това съобщение не можа да бъде изпратено."</string>
     <string name="delete_thread" msgid="757258847736632791">"Изтриване на нишката"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Препращане"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Ограничение за текстови съобщения"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Ограничение за мултимедийни съобщения"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Известия"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Да се показват известия за съобщения в лентата на състоянието"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Вибриране"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Да вибрира и при известяване"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Избор на мелодия"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Звук"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Задайте броя съобщения за запазване"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Винаги"</item>
-    <item msgid="6069709696037750627">"Само при тих режим"</item>
-    <item msgid="7486145357487111435">"Никога"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Вибриране"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Тих режим"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Автоматично извличане"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Автоматично извличане на съобщения"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Автоматично извличане при роуминг"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Съобщението не е изпратено"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Докоснете, за да прегледате съобщението, и опитайте отново."</string>
     <string name="download_later" msgid="5531365714424360903">"Не може да се изтегли в момента. Опитайте отново по-късно."</string>
+    <string name="no_apn" msgid="505932916503312015">"На устройството няма посочено име на точката за достъп (APN)."</string>
     <string name="select_audio" msgid="3528161449756771832">"Избор на аудио"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Запазване на прикач. файл"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Прикаченият файл бе запазен."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Хранилището на телефона ви е пълно"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Няма да получавате нови SMS/MMS съобщения"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 9984a2d..ec1f610 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Data de caducitat: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Missatge no lliurat"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No s\'ha pogut enviar aquest missatge."\n"Intent fet: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No s\'ha pogut enviar aquest missatge.\nIntent fet: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"No s\'ha pogut enviar aquest missatge."</string>
     <string name="delete_thread" msgid="757258847736632791">"Suprimeix el fil"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Reenvia"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Límit de missatges de text"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Límit de missatges multimèdia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificacions"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Mostra les notificacions de missatges a la barra d\'estat"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibra"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibra també en rebre una notificació"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Tria un to"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"So"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Definiu el nombre de missatges que voleu desar"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Sempre"</item>
-    <item msgid="6069709696037750627">"Només quan està en silenci"</item>
-    <item msgid="7486145357487111435">"Mai"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibra"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silenci"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Recuperació automàtica"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Recupera automàticament els missatges"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Recuperació automàtica en itinerància"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"No s\'ha enviat el missatge"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Toca-ho per revisar el missatge i tornar-ho a provar."</string>
     <string name="download_later" msgid="5531365714424360903">"No es pot baixar en aquest moment. Torna-ho a provar més tard."</string>
+    <string name="no_apn" msgid="505932916503312015">"No s\'ha especificat cap APN al dispositiu."</string>
     <string name="select_audio" msgid="3528161449756771832">"Tria l\'àudio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Desa el fitxer adjunt"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Fitxer adjunt desat"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"L\'emmagatzematge del telèfon és ple."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"No rebràs cap missatge SMS o MMS nou."</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 7fab010..f41fa43 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Platnost vyprší: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nedoručená zpráva"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Zprávu se nepodařilo odeslat."\n"Poslední pokus: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Zprávu se nepodařilo odeslat.\nPoslední pokus: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Zprávu se nepodařilo odeslat."</string>
     <string name="delete_thread" msgid="757258847736632791">"Smazat konverzaci"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Předat dál"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limit textových zpráv"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limit multimediálních zpráv"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Oznámení"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Zobrazovat oznámení o zprávě na stavovém řádku"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrace"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Při oznámení také vibrovat"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Zvolit vyzvánění"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Zvuk"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Nastavit počet ukládaných zpráv"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Vždy"</item>
-    <item msgid="6069709696037750627">"Pouze v tichém režimu"</item>
-    <item msgid="7486145357487111435">"Nikdy"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrace"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Tichý"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatické načítání"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automaticky načítat zprávy"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Autonačítání za roamingu"</string>
@@ -209,7 +202,7 @@
     <string name="message_type_label" msgid="7975373966795945566">"Typ: "</string>
     <string name="text_message" msgid="8196464345251877972">"Textová zpráva"</string>
     <string name="multimedia_message" msgid="1512644521083533071">"Multimediální zpráva"</string>
-    <string name="multimedia_notification" msgid="4124031788554972308">"Upozornění na multimediální zprávu"</string>
+    <string name="multimedia_notification" msgid="4124031788554972308">"Oznámení o multimediální zprávě"</string>
     <string name="from_label" msgid="2055117571548171397">"Od: "</string>
     <string name="to_address_label" msgid="5837363600471845801">"Komu: "</string>
     <string name="bcc_label" msgid="530867161453958774">"Skrytá kopie: "</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Zprávu se nepodařilo odeslat"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Dotkněte se obrazovky, zkontrolujte zprávu a zkuste to znovu."</string>
     <string name="download_later" msgid="5531365714424360903">"Nyní zprávy nelze načíst. Zkuste to znovu později."</string>
+    <string name="no_apn" msgid="505932916503312015">"V zařízení není zadáno žádné APN."</string>
     <string name="select_audio" msgid="3528161449756771832">"Zvolit zvuk"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Uložit přílohu"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Příloha byla uložena."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Úložiště vašeho telefonu je plné"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Neobdržíte žádné nové zprávy SMS/MMS"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index cf9629b..b0f6b2c 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Udløber: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"Kb"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Ikke afleveret besked"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Denne besked kunne ikke sendes."\n"Forsøg foretaget: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Denne besked kunne ikke sendes.\nForsøg foretaget: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Denne besked kunne ikke sendes."</string>
     <string name="delete_thread" msgid="757258847736632791">"Slet tråd"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Videresend"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Sms-begrænsning"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Mms-begrænsning"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Underretninger"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Vis underretninger vedr. beskeder på statusbjælken"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibration"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibration ved meddelelse"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Vælg ringetone"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Lyd"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Angiv det antal beskeder, der skal gemmes"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Altid"</item>
-    <item msgid="6069709696037750627">"Kun ved lydløs"</item>
-    <item msgid="7486145357487111435">"Aldrig"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibration"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Lydløs"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatisk hentning"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Hent automatisk beskeder"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Aut. hent. under roaming"</string>
@@ -234,7 +227,7 @@
     <string name="sim_manage_messages_title" msgid="3989147182100584333">"Sms-beskeder på SIM-kort"</string>
     <string name="sim_view" msgid="1997173541766393706">"Vis"</string>
     <string name="sim_empty" msgid="2356766833071636297">"Der er ingen beskeder på SIM-kortet."</string>
-    <string name="delivery_header_title" msgid="5361719578869045764">"Rapporter"</string>
+    <string name="delivery_header_title" msgid="5361719578869045764">"Rapportér"</string>
     <string name="status_none" msgid="8253075950774894961">"(Ingen)"</string>
     <string name="status_pending" msgid="2739860824607984892">"Afventer"</string>
     <string name="status_read" msgid="7576195253780627332">"Læs"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Beskeden er ikke sendt"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Tryk for at få vist beskeden, og prøv igen."</string>
     <string name="download_later" msgid="5531365714424360903">"Der kan ikke downloades lige nu. Prøv igen senere."</string>
+    <string name="no_apn" msgid="505932916503312015">"Der er ikke angivet en APN på enheden."</string>
     <string name="select_audio" msgid="3528161449756771832">"Vælg lyd"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Gem vedhæftet fil"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Den vedhæftede fil blev gemt."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Din telefons hukommelse er fuld"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Du vil ikke modtage nye SMS/MMS-beskeder"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index a9af102..30a07fe 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Ablaufdatum: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nicht gelieferte Nachricht"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Senden der Nachricht nicht möglich"\n"Letzter Versuch: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Senden der Nachricht nicht möglich\nLetzter Versuch: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Nachricht konnte nicht gesendet werden."</string>
     <string name="delete_thread" msgid="757258847736632791">"Konversation löschen"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Weiterleiten"</string>
@@ -82,7 +82,7 @@
     <string name="remove" msgid="4625444913256213175">"Entfernen"</string>
     <string name="send" msgid="4589898724889248486">"Senden"</string>
     <string name="mms" msgid="1587631213714914559">"MMS"</string>
-    <string name="send_mms" msgid="2933162044872550662">"Senden Sie eine MMS."</string>
+    <string name="send_mms" msgid="2933162044872550662">"MMS senden"</string>
     <string name="all_threads" msgid="2663426830306307194">"Alle Threads"</string>
     <string name="type_to_compose_text_enter_to_send" msgid="3539985471008581265">"Nachricht schreiben"</string>
     <string name="open_keyboard_to_compose_message" msgid="7860109685511253191">"Tastatur öffnen, um Nachricht einzugeben"</string>
@@ -95,7 +95,7 @@
     <string name="failed_to_resize_image" msgid="5608354151631833669">"Bildgröße nicht angepasst."</string>
     <string name="resize_image_error_information" msgid="3783200130776554475">"Dieses Bild ist selbst nach der Größenanpassung noch zu groß zum Senden."</string>
     <string name="forward_prefix" msgid="276280492803486464">"WG: "</string>
-    <string name="discard_message_reason" msgid="2667664943003796133">"Die Nachricht wird verworfen, da keine gültigen Empfänger vorhanden sind."</string>
+    <string name="discard_message_reason" msgid="2667664943003796133">"Die Nachricht wird verworfen, weil keine gültigen Empfänger vorhanden sind."</string>
     <string name="has_invalid_recipient" msgid="1485388396900997383">"Ungültige(r) Empfänger: &lt;<xliff:g id="NAME">%1$s</xliff:g>&gt;"</string>
     <string name="invalid_destination" msgid="4573835601024786416">"Ungültige Zieladresse"</string>
     <string name="service_not_activated" msgid="9178218144019626176">"Dieser Dienst ist nicht im Netzwerk aktiviert."</string>
@@ -105,7 +105,7 @@
     <string name="cannot_send_message" msgid="3414307479833622644">"Nachricht kann nicht gesendet werden."</string>
     <string name="cannot_send_message_reason" msgid="8745080126044054343">"Für die Nachricht sind keine gültigen Empfänger vorhanden."</string>
     <string name="cannot_forward_drm_obj" msgid="5047623751430559077">"Das in der Nachricht enthaltene DRM-Objekt kann nicht weitergeleitet werden."</string>
-    <string name="converting_to_picture_message" msgid="2980023239017588627">"Konvertierung in Multimedia-Nachricht wird ausgeführt..."</string>
+    <string name="converting_to_picture_message" msgid="2980023239017588627">"Konvertierung in MMS wird ausgeführt..."</string>
     <string name="cannot_add_slide_anymore" msgid="4631404749618820994">"Es können keine weiteren Dias hinzugefügt werden."</string>
     <string name="cannot_add_picture_and_video" msgid="4954353772391381447">"In eine Folie können nicht gleichzeitig ein Bild und ein Video eingefügt werden."</string>
     <string name="cannot_save_message" msgid="3375907366965264446">"Nachricht kann nicht gespeichert werden."</string>
@@ -147,7 +147,7 @@
     <string name="yes" msgid="3246158147503160811">"OK"</string>
     <string name="no" msgid="4289742508556913860">"Abbrechen"</string>
     <string name="set" msgid="4642365398263275979">"Festlegen"</string>
-    <string name="play" msgid="1888432400937785115">"Wiedergeben"</string>
+    <string name="play" msgid="1888432400937785115">"Abspielen"</string>
     <string name="edit" msgid="5912118407791492696">"Bearbeiten"</string>
     <string name="try_to_send" msgid="1996751738286080976">"Sendeversuch starten"</string>
     <string name="preferences_title" msgid="6650089610332670157">"Einstellungen"</string>
@@ -172,20 +172,13 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Höchstzahl an SMS"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Höchstzahl an MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Benachrichtigungen"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Bei Nachrichteneingang Benachrichtigung in Statuszeile anzeigen"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibration"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Bei Benachrichtigung vibrieren"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Klingelton auswählen"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Töne"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Anzahl der zu speichernden Nachrichten festlegen"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Immer"</item>
-    <item msgid="6069709696037750627">"Nur im Lautlos-Modus"</item>
-    <item msgid="7486145357487111435">"Nie"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibration"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Lautlos"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatisch abrufen"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Nachrichten automatisch empfangen"</string>
-    <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Autom. Roaming-Abruf"</string>
+    <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatischer Roaming-Abruf"</string>
     <string name="pref_summary_mms_retrieval_during_roaming" msgid="2427892806582531020">"Bei Roaming Nachrichten automatisch abrufen"</string>
     <string name="confirm_dialog_title" msgid="2187213750475782725">"Löschen?"</string>
     <string name="confirm_dialog_locked_title" msgid="8179085718150932242">"Gesperrte Nachricht entfernen?"</string>
@@ -208,7 +201,7 @@
     <string name="message_details_title" msgid="9223295113731634528">"Nachrichtendetails"</string>
     <string name="message_type_label" msgid="7975373966795945566">"Typ: "</string>
     <string name="text_message" msgid="8196464345251877972">"SMS"</string>
-    <string name="multimedia_message" msgid="1512644521083533071">"Multimedia-Nachricht"</string>
+    <string name="multimedia_message" msgid="1512644521083533071">"MMS"</string>
     <string name="multimedia_notification" msgid="4124031788554972308">"Benachrichtigung für Multimedia-Nachrichten"</string>
     <string name="from_label" msgid="2055117571548171397">"Von: "</string>
     <string name="to_address_label" msgid="5837363600471845801">"An: "</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Nachricht nicht gesendet"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Überprüfen Sie die Nachricht durch Tippen und versuchen Sie es erneut."</string>
     <string name="download_later" msgid="5531365714424360903">"Herunterladen momentan nicht möglich. Bitte versuchen Sie es später erneut."</string>
+    <string name="no_apn" msgid="505932916503312015">"Kein APN auf Gerät angegeben"</string>
     <string name="select_audio" msgid="3528161449756771832">"Audio wählen"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Anhang speichern"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Anhang gespeichert"</string>
@@ -306,7 +300,7 @@
     <string name="slideshow_activity" msgid="5658249461317434432">"Diashow"</string>
     <string name="class_0_message_activity" msgid="6631339964159861048">"Nachricht Klasse 0"</string>
     <string name="search_label" msgid="6042598929386174964">"SMS/MMS"</string>
-    <string name="search_hint" msgid="7273727663577472044">"SMS/MMS durchsuchen"</string>
+    <string name="search_hint" msgid="7273727663577472044">"In SMS/MMS suchen"</string>
     <string name="search" msgid="7560238620274735199">"SMS/MMS"</string>
     <string name="search_setting_description" msgid="4104004595086437572">"Text in meinen Nachrichten"</string>
     <string name="search_empty" msgid="2109551478056039278">"Keine Übereinstimmungen"</string>
@@ -324,7 +318,7 @@
     <string name="storage_limits_message" msgid="2010501485394745696">"Anzahl der pro Konversation gespeicherten Nachrichten begrenzen?"</string>
     <string name="storage_limits_setting" msgid="4952781049308537373">"Grenzwerte festlegen"</string>
     <string name="storage_limits_setting_dismiss" msgid="1433841310158458034">"Keine Begrenzung"</string>
-    <string name="too_many_unsent_mms" msgid="4436493698891224126">"Nachricht kann jetzt nicht gesendet werden. Zu viele ungesendete Multimedia-Nachrichten."</string>
+    <string name="too_many_unsent_mms" msgid="4436493698891224126">"Nachricht kann jetzt nicht gesendet werden. Zu viele ungesendete MMS."</string>
     <string name="sending_message" msgid="2054406576361149715">"Wird gesendet..."</string>
     <string name="pick_too_many_recipients" msgid="650087588867628044">"Zu viele Empfänger"</string>
     <string name="adding_recipients" msgid="2962810172527532357">"Empfänger werden hinzugefügt..."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Der Speicher Ihres Telefons ist voll."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Sie erhalten keine neuen SMS/MMS."</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index f271bf9..d21b4d2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Λήγει: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Μήνυμα που δεν παραδόθηκε"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Δεν ήταν δυνατή η αποστολή αυτού του μηνύματος."\n"Έγινε προσπάθεια στις: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Δεν ήταν δυνατή η αποστολή αυτού του μηνύματος.\nΈγινε προσπάθεια στις: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Δεν ήταν δυνατή η αποστολή αυτού του μηνύματος."</string>
     <string name="delete_thread" msgid="757258847736632791">"Διαγραφή νήματος"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Προώθηση"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Όριο μηνύματος κειμένου"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Όριο μηνύματος πολυμέσων"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Ειδοποιήσεις"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Προβολή ειδοποιήσεων μηνύματος στη γραμμή κατάστασης"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Δόνηση"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Δόνηση κατά την ειδοποίηση"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Επιλέξτε ήχο κλήσης"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Ήχος"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Ορισμός αριθμού μηνυμάτων για αποθήκευση"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Πάντα"</item>
-    <item msgid="6069709696037750627">"Μόνο στο αθόρυβο"</item>
-    <item msgid="7486145357487111435">"Ποτέ"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Δόνηση"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Σίγαση"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Αυτόματη ανάκτηση"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Αυτόματη ανάκτηση μηνυμάτων"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Αυτόματη ανάκτηση περιαγωγής"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Δεν έγινε αποστολή του μηνύματος"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Αγγίξτε για να ελέγξετε το μήνυμα και δοκιμάστε ξανά."</string>
     <string name="download_later" msgid="5531365714424360903">"Δεν είναι δυνατή η λήψη αυτήν τη στιγμή. Δοκιμάστε ξανά αργότερα."</string>
+    <string name="no_apn" msgid="505932916503312015">"Δεν έχει προσδιοριστεί APN στη συσκευή."</string>
     <string name="select_audio" msgid="3528161449756771832">"Επιλογή ήχου"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Αποθήκευση επισύναψης"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Η επισύναψη αποθηκεύτηκε."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Ο αποθηκευτικός χώρος του τηλεφώνου σας είναι πλήρης"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Δεν θα λαμβάνετε νέα μηνύματα SMS/MMS"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index c80a222..236829e 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Expires: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Undelivered message"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Couldn\'t send this message."\n"Attempt made: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Couldn\'t send this message.\nAttempt made: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Couldn\'t send this message."</string>
     <string name="delete_thread" msgid="757258847736632791">"Delete thread"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Forward"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Text-message limit"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimedia message limit"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notifications"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Display message notifications in status bar"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrate"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Also vibrate when notified"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Choose ringtone"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sound"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Set number of messages to save"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Always"</item>
-    <item msgid="6069709696037750627">"Only when silent"</item>
-    <item msgid="7486145357487111435">"Never"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrate"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silent"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Auto-retrieve"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automatically retrieve messages"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Roaming auto-retrieve"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Message not sent"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Touch to review the message and try again."</string>
     <string name="download_later" msgid="5531365714424360903">"Can\'t download right now. Try again later."</string>
+    <string name="no_apn" msgid="505932916503312015">"No APN specified on the device."</string>
     <string name="select_audio" msgid="3528161449756771832">"Choose audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Save attachment"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Attachment saved."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Your phone\'s storage is full"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"You won\'t receive new SMS/MMS messages"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 1f9c4a4..628cb7f 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Expira: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mensaje no entregado"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No se pudo enviar este mensaje."\n" Último intento: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No se pudo enviar este mensaje.\n Último intento: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"No se pudo enviar este mensaje."</string>
     <string name="delete_thread" msgid="757258847736632791">"Eliminar conversación"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Reenviar"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Límite de mensajes de texto"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Límite de mensaje multimedia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificaciones"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Mostrar notificaciones de mensaje en la barra de estado"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrar cuando se reciba una notificación"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Elegir un tono"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sonido"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Establecer el número de mensajes que se deben guardar"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Siempre"</item>
-    <item msgid="6069709696037750627">"Sólo cuando esté en silencio"</item>
-    <item msgid="7486145357487111435">"Nunca"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrar"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silencioso"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Recuperación automática"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Recuperar mensajes automáticamente"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Recuperación automática en roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mensaje no enviado"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Selecciona esta opción para revisar el mensaje e intenta enviarlo de nuevo."</string>
     <string name="download_later" msgid="5531365714424360903">"No se puede descargar en este momento. Vuelve a intentarlo más tarde."</string>
+    <string name="no_apn" msgid="505932916503312015">"No se especificó ningún APN en el dispositivo."</string>
     <string name="select_audio" msgid="3528161449756771832">"Elige el audio."</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Guardar archivo adjunto"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Adjunto guardado"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"No queda espacio de almacenamiento en tu teléfono."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"No recibirás mensajes SMS/MMS nuevos."</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index fbe465a..68b9c3f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -34,7 +34,7 @@
     <string name="menu_view" msgid="7448852683948080108">"Ver conversación"</string>
     <string name="menu_debug_dump" msgid="6855869130206549643">"VOLCADO DE DEPURACIÓN"</string>
     <string name="refreshing" msgid="987335057871404222">"Actualizando..."</string>
-    <string name="menu_cell_broadcasts" msgid="5646161375983084660">"Difusiones móviles"</string>
+    <string name="menu_cell_broadcasts" msgid="5646161375983084660">"Mensajes de radiodifusión"</string>
     <string name="has_draft" msgid="2487465595514636160">"Borrador"</string>
     <string name="no_subject_view" msgid="7795086723069563537">"(Sin asunto)"</string>
     <string name="messagelist_sender_self" msgid="3579016854116401602">"Yo"</string>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Vencimiento: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mensaje no entregado"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No se ha podido enviar el mensaje."\n"Último intento: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"No se ha podido enviar el mensaje.\nÚltimo intento: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"No se ha podido enviar el mensaje."</string>
     <string name="delete_thread" msgid="757258847736632791">"Eliminar cadena"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Reenviar"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Límite de mensajes de texto"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Límite de mensajes multimedia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificaciones"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Mostrar notificación de mensajes en la barra de estado"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibración"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrar también al notificar"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Seleccionar tono"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sonido"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Elige el número de mensajes que quieres guardar"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Siempre"</item>
-    <item msgid="6069709696037750627">"Solo en modo silencio"</item>
-    <item msgid="7486145357487111435">"Nunca"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibración"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silencio"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Recuperar automáticamente"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Recuperar mensajes automáticamente"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"En itinerancia"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mensaje no enviado"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Toca esta opción para revisar el mensaje e intenta enviarlo de nuevo."</string>
     <string name="download_later" msgid="5531365714424360903">"No se puede descargar en este momento. Inténtalo de nuevo más tarde."</string>
+    <string name="no_apn" msgid="505932916503312015">"No se ha especificado ningún APN en el dispositivo."</string>
     <string name="select_audio" msgid="3528161449756771832">"Seleccionar audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Guardar archivo adjunto"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Adjunto guardado"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"No queda espacio de almacenamiento en tu teléfono"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"No recibirás nuevos mensajes SMS/MMS"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 05b12e1..cd40543 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Aegub: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Kohaletoimetamata sõnum"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Seda sõnumit ei saa saata."\n"Prooviti saata: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Seda sõnumit ei saa saata.\nProoviti saata: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Sõnumit ei saa saata."</string>
     <string name="delete_thread" msgid="757258847736632791">"Kustuta lõim"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Edasta"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Tekstsõnumi piirang"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimeediumsõnumi limiit"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Teadistused"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Kuva sõnumiteatisi olekuribal."</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibreering"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibreeri ka teavitamise ajal"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Helina valimine"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Heli"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Määrake sõnumite arv salvestamiseks"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Alati"</item>
-    <item msgid="6069709696037750627">"Ainult hääletus režiimis"</item>
-    <item msgid="7486145357487111435">"Mitte kunagi"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibreering"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Hääletu"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Võta automaatselt vastu"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Võta sõnumid automaatselt vastu"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automaatne vastuvõtmine rändlusel"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Sõnumit ei saadetud"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Puudutage sõnumi ülevaatamiseks ja proovige uuesti."</string>
     <string name="download_later" msgid="5531365714424360903">"Praegu ei saa alla laadida. Proovige hiljem uuesti."</string>
+    <string name="no_apn" msgid="505932916503312015">"Seadmes pole APN-i täpsustatud."</string>
     <string name="select_audio" msgid="3528161449756771832">"Heli valimine"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Manuse salvestamine"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Manus on salvestatud."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Telefoni mälu on täis"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Te ei saa uusi SMS-/MMS-sõnumeid"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 0d05b1c..65eb265 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"تاریخ انقضا: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"کیلوبایت"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"پیام تحویل داده نشده"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"این پیام ارسال نشد."\n"تلاش انجام شده:<xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"این پیام ارسال نشد.\nتلاش انجام شده:<xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"این پیام ارسال نشد."</string>
     <string name="delete_thread" msgid="757258847736632791">"حذف رشته"</string>
     <string name="menu_forward" msgid="9026858380050046756">"هدایت"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"محدودیت پیام متنی"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"محدودیت پیام چند رسانه‌ای"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"اعلان‌ها"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"نمایش اعلان‌های پیام در نوار وضعیت"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"لرزش"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"همچنین لرزش هنگام اعلان"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"انتخاب آهنگ زنگ"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"صدا"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"تنظیم تعداد پیام‌ها برای ذخیره"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"همیشه"</item>
-    <item msgid="6069709696037750627">"فقط هنگام ساکت بودن"</item>
-    <item msgid="7486145357487111435">"هرگز"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"لرزش"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"بیصدا"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"بازیابی خودکار"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"بازیابی خودکار پیام‌ها"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"بازیابی خودکار در طول رومینگ"</string>
@@ -283,6 +276,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"پیام ارسال نشد"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"برای مرور پیام لمس کنید و دوباره سعی کنید."</string>
     <string name="download_later" msgid="5531365714424360903">"در حال حاضر نمی‌توانید دانلود کنید. در فرصتی دیگر دوباره سعی کنید."</string>
+    <string name="no_apn" msgid="505932916503312015">"هیچ APN در دستگاه مشخص نشده است."</string>
     <string name="select_audio" msgid="3528161449756771832">"انتخاب صدا"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"ذخیره پیوست"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"پیوست ذخیره شد."</string>
@@ -351,4 +345,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"، "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"فضای ذخیره‌سازی تلفن شما پر است"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"‫شما پیامک/پیام MMS جدیدی دریافت نمی‌کنید"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 68dfdaf..38ecc78 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Vanhenee: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kt"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Toimittamaton viesti"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Viestin lähettäminen epäonnistui."\n"Yritettiin: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Viestin lähettäminen epäonnistui.\nYritettiin: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Viestin lähettäminen epäonnistui."</string>
     <string name="delete_thread" msgid="757258847736632791">"Poista viestiketju"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Lähetä edelleen"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Tekstiviestirajoitus"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimediaviestirajoitus"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Ilmoitukset"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Näytä viesti-ilmoitukset tilapalkissa"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Värinä"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Käytä värinää ilmoituksien yhteydessä"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Valitse soittoääni"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Ääni"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Aseta tallennettavien viestien lukumäärä"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Aina"</item>
-    <item msgid="6069709696037750627">"Vain puhelimen ollessa äänetön"</item>
-    <item msgid="7486145357487111435">"Ei koskaan"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Värinä"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Äänetön"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automaattilataus"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Lataa viestit automaattisesti"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automaattilataus roaming-tilassa"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Viestiä ei lähetetty"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Valitse tämä, niin voit tarkistaa viestin ja yrittää uudelleen."</string>
     <string name="download_later" msgid="5531365714424360903">"Ei voi ladata juuri nyt. Yritä myöhemmin uudelleen."</string>
+    <string name="no_apn" msgid="505932916503312015">"Laitteen APN-määritys puuttuu."</string>
     <string name="select_audio" msgid="3528161449756771832">"Valitse ääni"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Tallenna liite"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Liite tallennettu."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Puhelimesi tallennustila on täynnä"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Et saa enää teksti- tai multimediaviestejä"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e569ff3..6f96a6d 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Date d\'expiration : <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"Ko"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Message non transmis"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Impossible d\'envoyer ce message."\n"Dernière tentative : <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Impossible d\'envoyer ce message.\nDernière tentative : <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Impossible d\'envoyer ce message."</string>
     <string name="delete_thread" msgid="757258847736632791">"Supprimer le fil"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Transférer"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limite pour les SMS"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limite pour les MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notifications"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Afficher les notifications de message dans la barre d\'état"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibreur"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrer également à la réception d\'une notification"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Sélectionner une sonnerie"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sonnerie"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Définir le nombre de messages à enregistrer"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Toujours"</item>
-    <item msgid="6069709696037750627">"En mode silencieux"</item>
-    <item msgid="7486145357487111435">"Jamais"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibreur"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Mode silencieux"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Récupération auto"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Récupérer automatiquement les messages"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Itinérance"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Le message n\'a pas été envoyé."</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Appuyez pour relire le message, puis réessayez."</string>
     <string name="download_later" msgid="5531365714424360903">"Impossible de télécharger pour le moment. Veuillez réessayer ultérieurement."</string>
+    <string name="no_apn" msgid="505932916503312015">"Aucun APN n\'est indiqué sur l\'appareil."</string>
     <string name="select_audio" msgid="3528161449756771832">"Sélectionner un fichier audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Enregistrer le fichier"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Pièce jointe enregistrée."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"L\'espace de stockage de votre téléphone est entièrement utilisé."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Vous ne recevrez pas les nouveaux SMS/MMS."</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 93eea41..3436e93 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"समय समाप्ति: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"अवितरित संदेश"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"यह संदेश नहीं भेजा जा सका."\n"प्रयास किया गया: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"यह संदेश नहीं भेजा जा सका.\nप्रयास किया गया: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"यह संदेश नहीं भेजा जा सका."</string>
     <string name="delete_thread" msgid="757258847736632791">"थ्रेड हटाएं"</string>
     <string name="menu_forward" msgid="9026858380050046756">"अग्रेषित करें"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"पाठ संदेश सीमा"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"मल्टीमीडिया संदेश सीमा"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"सूचनाएं"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"स्थिति बार में संदेश सूचनाएं दिखाएं"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"कंपन"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"सूचित किए जाने पर भी कंपन करें"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"रिंगटोन चुनें"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"ध्वनि"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"सहेजने के लिए संदेशों की संख्या सेट करें"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"हमेशा"</item>
-    <item msgid="6069709696037750627">"केवल मौन होने पर"</item>
-    <item msgid="7486145357487111435">"कभी नहीं"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"कंपन"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"मौन"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"स्वतः पुनर्प्राप्ति"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"संदेशों को अपने आप पुनर्प्राप्त करें"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"रोमिंग स्वतः पुनर्प्राप्ति"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"संदेश नहीं भेजा गया"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"संदेश की समीक्षा करने के लिए स्‍पर्श करें और पुनः प्रयास करें."</string>
     <string name="download_later" msgid="5531365714424360903">"अभी डाउनलोड नहीं कर सकता. बाद में पुन: प्रयास करें."</string>
+    <string name="no_apn" msgid="505932916503312015">"उपकरण पर कोई APN निर्दिष्ट नहीं."</string>
     <string name="select_audio" msgid="3528161449756771832">"ऑडियो चुनें"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"अनुलग्नक सहेजें"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"अनुलग्नक सहेजा गया."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"आपके फ़ोन का संग्रहण भर गया है"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"आपको नए SMS/MMS संदेश प्राप्त नहीं होंगे"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 698448e..11ce534 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Ističe: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Neisporučena poruka"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Ovu poruku nije moguće poslati."\n"Provedeni pokušaj: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Ovu poruku nije moguće poslati.\nProvedeni pokušaj: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Ovu poruku nije moguće poslati."</string>
     <string name="delete_thread" msgid="757258847736632791">"Izbriši nit"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Proslijedi"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Ograničenja za tekstualne poruke"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Ograničenje za multimedijske poruke"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Obavijesti"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Prikaz obavijesti o porukama u traci statusa"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibracija"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Po obavijesti također vibriraj"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Odaberi melodiju zvona"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Zvuk"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Postavi broj poruka za spremanje"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Uvijek"</item>
-    <item msgid="6069709696037750627">"Samo ako je bešumno"</item>
-    <item msgid="7486145357487111435">"Nikad"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibracija"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Bešumno"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatski dohvat"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automatski dohvaćaj poruke"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatski dohvat u roamingu"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Poruka nije poslana"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Dodirnite za pregled poruke i pokušajte ponovo."</string>
     <string name="download_later" msgid="5531365714424360903">"Preuzimanje u ovom trenutku nije moguće.  Pokušajte ponovo kasnije."</string>
+    <string name="no_apn" msgid="505932916503312015">"Na uređaju nije naveden APN."</string>
     <string name="select_audio" msgid="3528161449756771832">"Odaberi audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Spremi privitak"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Privitak je spremljen"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Pohrana telefona puna je"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Nećete primati nove SMS/MMS poruke"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index f3f16ff..930551f 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Lejár: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Kézbesítetlen üzenet"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nem sikerült elküldeni az üzenetet."\n"Utolsó kísérlet: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nem sikerült elküldeni az üzenetet.\nUtolsó kísérlet: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Nem sikerült elküldeni az üzenetet."</string>
     <string name="delete_thread" msgid="757258847736632791">"Szál törlése"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Továbbítás"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Szöveges üzenetek korlátja"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimédiás üzenetek korlátja"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Értesítések"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Üzenetekkel kapcsolatos értesítések megjelenítése az állapotsoron"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Rezgés"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Rezgés értesítések esetén is"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Válasszon csengőhangot"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Hang"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"A menteni kívánt üzenetek számának beállítása"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Mindig"</item>
-    <item msgid="6069709696037750627">"Csak néma üzemmódban"</item>
-    <item msgid="7486145357487111435">"Soha"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Rezgés"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Néma"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatikus letöltés"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Üzenetek automatikus letöltése"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatikus letöltés barangolás során"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Az üzenet nincs elküldve"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Érintse meg az üzenetet annak áttekintéséhez, és próbálja újra."</string>
     <string name="download_later" msgid="5531365714424360903">"Jelenleg nem lehet letölteni. Próbálkozzon később."</string>
+    <string name="no_apn" msgid="505932916503312015">"Nincs APN megadva az eszközön."</string>
     <string name="select_audio" msgid="3528161449756771832">"Hang kiválasztása"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Melléklet mentése"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"A melléklet elmentve."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"A telefon tárhelye megtelt"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"A jövőben nem kap új SMS/MMS-üzeneteket"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index f51278b..d16adeb 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Kedaluwarsa: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Pesan tidak terkirim"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tidak dapat mengirim pesan ini."\n"Upaya dilakukan: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tidak dapat mengirim pesan ini.\nUpaya dilakukan: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Tidak dapat mengirim pesan ini."</string>
     <string name="delete_thread" msgid="757258847736632791">"Hapus untaian"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Teruskan"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Batasan pesan teks"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Batasan pesan multimedia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Pemberitahuan"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Tampilkan pemberitahuan pesan pada bilah status"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Getar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Juga bergetar ketika diberi tahu"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Pilih nada dering"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Suara"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Setel jumlah pesan yang akan disimpan"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Selalu"</item>
-    <item msgid="6069709696037750627">"Hanya saat senyap"</item>
-    <item msgid="7486145357487111435">"Tidak pernah"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Getar"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Senyap"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Pungut otomatis"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Pungut pesan secara otomatis"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Pungut otomatis roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Pesan tidak dikirim"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Sentuh untuk meninjau pesan, lalu coba lagi."</string>
     <string name="download_later" msgid="5531365714424360903">"Tidak dapat mengunduh sekarang. Coba lagi nanti."</string>
+    <string name="no_apn" msgid="505932916503312015">"Tidak ada APN yang ditetapkan pada perangkat ini."</string>
     <string name="select_audio" msgid="3528161449756771832">"Pilih audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Simpan lampiran"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Lampiran disimpan."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Ruang penyimpanan ponsel Anda penuh"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Anda tidak akan menerima pesan SMS/MMS baru"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 730ef48..fb2cf9d 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Scadenza: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Messaggio non consegnato"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Invio messaggio non riuscito."\n"Tentativo eseguito: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Invio messaggio non riuscito.\nTentativo eseguito: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Invio messaggio non riuscito."</string>
     <string name="delete_thread" msgid="757258847736632791">"Elimina thread"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Inoltra"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limite SMS"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limite MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notifiche"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Visualizza notifiche messaggi nella barra di stato"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrazione"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Attiva anche vibrazione alla notifica"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Scegli suoneria"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Suono"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Imposta numero di messaggi da salvare"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Sempre"</item>
-    <item msgid="6069709696037750627">"Solo in modalità silenziosa"</item>
-    <item msgid="7486145357487111435">"Mai"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrazione"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silenzioso"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Recupero automatico"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Recupera messaggi automaticamente"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Recupero in roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Messaggio non inviato"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Tocca per esaminare il messaggio e riprovare."</string>
     <string name="download_later" msgid="5531365714424360903">"Impossibile effettuare il download ora. Riprova più tardi."</string>
+    <string name="no_apn" msgid="505932916503312015">"Nessun APN specificato sul dispositivo."</string>
     <string name="select_audio" msgid="3528161449756771832">"Scegli audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Salva allegato"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Allegato salvato."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"La memoria del telefono è piena"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Non riceverai nuovi SMS/MMS"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 579e843..5da5e7b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"תאריך תפוגה: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"הודעה שלא נמסרה"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"לא ניתן היה לשלוח הודעה זו."\n"נעשה ניסיון: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"לא ניתן היה לשלוח הודעה זו.\nנעשה ניסיון: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"לא ניתן לשלוח את ההודעה."</string>
     <string name="delete_thread" msgid="757258847736632791">"מחק שרשור"</string>
     <string name="menu_forward" msgid="9026858380050046756">"העבר"</string>
@@ -60,9 +60,9 @@
     <string name="done" msgid="7479926268660505871">"סיום"</string>
     <string name="move_up" msgid="7307578107431739359">"העבר למעלה"</string>
     <string name="move_down" msgid="6356509518429479053">"העבר למטה"</string>
-    <string name="remove_slide" msgid="2225604354024914900">"הסר שקופית"</string>
-    <string name="add_slide" msgid="9140721399423564187">"הוסף שקופית"</string>
-    <string name="add_slide_hint" msgid="7351356961076244584">"גע כדי ליצור שקופית חדשה."</string>
+    <string name="remove_slide" msgid="2225604354024914900">"הסר שקף"</string>
+    <string name="add_slide" msgid="9140721399423564187">"הוסף שקף"</string>
+    <string name="add_slide_hint" msgid="7351356961076244584">"גע כדי ליצור שקף חדש."</string>
     <string name="discard_slideshow" msgid="5054239124146413609">"מחק מצגת"</string>
     <string name="slide_show_part" msgid="5077675385448696846">"חלק <xliff:g id="PART">%1$s</xliff:g>/<xliff:g id="TOTAL">%2$s</xliff:g>"</string>
     <string name="remove_text" msgid="2164068850029476539">"הסר טקסט"</string>
@@ -120,7 +120,7 @@
     <string name="preview_slideshow" msgid="6824095909448728427">"תצוגה מקדימה"</string>
     <string name="replace_image" msgid="3171240952601443619">"החלף תמונה"</string>
     <string name="duration_sec" msgid="6617032324007824096">"תדירות (<xliff:g id="DURATION">%s</xliff:g> שניות)"</string>
-    <string name="duration_selector_title" msgid="5981097971537625852">"משך זמן של שקופית "</string>
+    <string name="duration_selector_title" msgid="5981097971537625852">"משך זמן של שקף "</string>
     <string name="layout_selector_title" msgid="7344600117972450771">"פריסת מצגת "</string>
     <string name="layout_top" msgid="6811021650398972346">"פריסה (למעלה)"</string>
     <string name="layout_bottom" msgid="5822765871095491843">"פריסה (בתחתית)"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"מגבלה של הודעת טקסט"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"מגבלת הודעות מולטימדיה"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"התראות"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"הצג התראות הודעה בשורת המצב"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"רטט"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"רטט גם בעת קבלת התראה"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"בחר רינגטון"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"צליל"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"הגדר מספר הודעות לשמירה"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"תמיד"</item>
-    <item msgid="6069709696037750627">"במצב שקט בלבד"</item>
-    <item msgid="7486145357487111435">"אף פעם"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"רטט"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"שקט"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"אחזור אוטומטי"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"אחזר הודעות באופן אוטומטי"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"אחזור אוטומטי בנדידה"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"ההודעה לא נשלחה"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"גע כדי לבדוק את ההודעה ונסה שוב."</string>
     <string name="download_later" msgid="5531365714424360903">"לא ניתן להוריד כרגע. נסה שוב מאוחר יותר."</string>
+    <string name="no_apn" msgid="505932916503312015">"לא צוין APN במכשיר."</string>
     <string name="select_audio" msgid="3528161449756771832">"בחר קובץ אודיו"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"שמור את הקובץ המצורף"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"הקובץ המצורף נשמר."</string>
@@ -291,7 +285,7 @@
     <string name="menu_insert_smiley" msgid="4964504393061025071">"הוסף סמיילי"</string>
     <string name="menu_group_participants" msgid="4288356090262299071">"משתתפי קבוצה"</string>
     <string name="select_link_title" msgid="3371818607625768447">"בחר פעולה"</string>
-    <string name="slide_number" msgid="1923958526266726635">"שקופית <xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="slide_number" msgid="1923958526266726635">"שקף <xliff:g id="NUMBER">%s</xliff:g>"</string>
   <plurals name="slide_duration">
     <item quantity="one" msgid="1654101988340583935">"שנייה <xliff:g id="NUMBER">%s</xliff:g>"</item>
     <item quantity="few" msgid="7887190171856293871">"<xliff:g id="NUMBER">%s</xliff:g> שניות"</item>
@@ -302,7 +296,7 @@
     <string name="change_duration_activity" msgid="7048606979485031693">"שנה את משך הזמן"</string>
     <string name="edit_slideshow_activity" msgid="8751493388760050529">"ערוך מצגת"</string>
     <string name="recipient_list_activity" msgid="1254938236543727019">"משתתפי קבוצה"</string>
-    <string name="edit_slide_activity" msgid="8752829671778696983">"ערוך שקופית"</string>
+    <string name="edit_slide_activity" msgid="8752829671778696983">"ערוך שקף"</string>
     <string name="slideshow_activity" msgid="5658249461317434432">"מצגת"</string>
     <string name="class_0_message_activity" msgid="6631339964159861048">"הודעה ברמה 0"</string>
     <string name="search_label" msgid="6042598929386174964">"סמס"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"שטח האחסון בטלפון שלך מלא"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"לא תקבל הודעות SMS/MMS חדשות"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index d07ccf6..42fcc8d 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>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"有効期限:<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"未配信メッセージ"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"このメッセージは送信できません。"\n"最終試行時間: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"このメッセージは送信できません。\n最終試行時間: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"このメッセージを送信できませんでした。"</string>
     <string name="delete_thread" msgid="757258847736632791">"スレッドを削除"</string>
     <string name="menu_forward" msgid="9026858380050046756">"転送"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"テキストメッセージの制限件数"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"マルチメディアメッセージの制限件数"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"通知"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"メッセージ受信: ステータスバーで通知"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"バイブレーション"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"メッセージ受信の通知: バイブレーションON"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"着信音を選択"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"着信音"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"保存するメッセージ件数を設定"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"常に使用"</item>
-    <item msgid="6069709696037750627">"マナーモード時のみ"</item>
-    <item msgid="7486145357487111435">"使用しない"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"バイブレーション"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"マナーモード"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"自動で取得"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"メッセージを自動的に取得する"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"ローミング時に自動取得"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"メッセージの送信失敗"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"タップしてメッセージを確認してからもう一度お試しください。"</string>
     <string name="download_later" msgid="5531365714424360903">"現在ダウンロードできません。しばらくしてからもう一度お試しください。"</string>
+    <string name="no_apn" msgid="505932916503312015">"端末にAPNが指定されていません。"</string>
     <string name="select_audio" msgid="3528161449756771832">"音声の選択"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"添付ファイルを保存"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"添付ファイルを保存しました。"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"、 "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"携帯端末のストレージに空き領域がありません"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"新しいSMS/MMSメッセージを受信しません"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index ecd4c26..090259a 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -26,7 +26,7 @@
     <string name="menu_send_email" msgid="671366308915241664">"<xliff:g id="NAME">%s</xliff:g>에게 이메일 보내기"</string>
     <string name="menu_compose_new" msgid="3763437973691046238">"새 메시지"</string>
     <string name="menu_preferences" msgid="4693148116114749414">"설정"</string>
-    <string name="menu_add_address_to_contacts" msgid="4491980950419914944">"피플에 <xliff:g id="CONTACTEMAILORNUMBER">%s</xliff:g> 추가"</string>
+    <string name="menu_add_address_to_contacts" msgid="4491980950419914944">"주소록에 <xliff:g id="CONTACTEMAILORNUMBER">%s</xliff:g> 추가"</string>
     <string name="menu_call" msgid="5877123227307074690">"전화걸기"</string>
     <string name="menu_search" msgid="2289469305728821360">"검색"</string>
     <string name="menu_delete_all" msgid="808729454898114735">"전체 대화목록 삭제"</string>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"만료 날짜: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"전송되지 않은 메시지"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"메시지를 보내지 못했습니다."\n"마지막으로 시도한 시각: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"메시지를 보내지 못했습니다.\n마지막으로 시도한 시각: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"메시지를 보내지 못했습니다."</string>
     <string name="delete_thread" msgid="757258847736632791">"대화목록 삭제"</string>
     <string name="menu_forward" msgid="9026858380050046756">"전달"</string>
@@ -91,7 +91,7 @@
     <string name="select_different_media" msgid="6241623357299382183">"다른 <xliff:g id="NAME">%1$s</xliff:g>을(를) 선택하세요."</string>
     <string name="exceed_message_size_limitation" msgid="6027753647094742437">"메시지 크기 한도에 도달했습니다."</string>
     <string name="message_too_big_for_video" msgid="2639367545165352543">"메시지가 너무 커서 동영상을 첨부할 수 없습니다."</string>
-    <string name="failed_to_add_media" msgid="7207464170168708485">"<xliff:g id="NAME">%1$s</xliff:g>님을 메시지에 추가할 수 없습니다."</string>
+    <string name="failed_to_add_media" msgid="7207464170168708485">"<xliff:g id="NAME">%1$s</xliff:g>을 메시지에 추가할 수 없습니다."</string>
     <string name="failed_to_resize_image" msgid="5608354151631833669">"사진 크기가 조정되지 않았습니다."</string>
     <string name="resize_image_error_information" msgid="3783200130776554475">"사진의 크기를 조정했지만 사진이 너무 커 아직도 전송할 수 없습니다."</string>
     <string name="forward_prefix" msgid="276280492803486464">"Fwd: "</string>
@@ -142,7 +142,7 @@
     <item msgid="7786079681602275449">"기타"</item>
   </string-array>
     <string name="menu_view_contact" msgid="1414670584423909451">"연락처 보기"</string>
-    <string name="menu_add_to_contacts" msgid="1258127580972228970">"피플에 추가"</string>
+    <string name="menu_add_to_contacts" msgid="1258127580972228970">"주소록에 추가"</string>
     <string name="hidden_sender_address" msgid="2776075636669924968">"숨은 발신자 주소"</string>
     <string name="yes" msgid="3246158147503160811">"확인"</string>
     <string name="no" msgid="4289742508556913860">"취소"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"문자 메시지 한도"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"멀티미디어 메시지 한도"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"알림"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"상태 표시줄에 메시지 알림 표시"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"진동"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"알림 시 진동도 함께 울림"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"벨소리 선택"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"소리"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"저장할 메시지 개수 설정"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"항상"</item>
-    <item msgid="6069709696037750627">"무음 시에만"</item>
-    <item msgid="7486145357487111435">"안함"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"진동"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"무음"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"자동 수신"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"메시지를 자동으로 수신"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"로밍 중 자동 수신"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"메시지를 보내지 못함"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"터치하여 메시지를 검토한 다음 다시 시도해 보세요."</string>
     <string name="download_later" msgid="5531365714424360903">"현재 다운로드할 수 없습니다. 나중에 다시 시도하세요."</string>
+    <string name="no_apn" msgid="505932916503312015">"기기에 지정된 APN이 없습니다."</string>
     <string name="select_audio" msgid="3528161449756771832">"오디오 선택"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"첨부파일 저장"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"첨부파일이 저장되었습니다."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"휴대전화 저장공간이 가득 찼습니다."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"새 SMS/MMS 메시지를 받지 않게 됩니다."</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index e9e134e..98d75f5 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Galioja iki: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nepristatytas pranešimas"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nepavyko išsiųsti šio pranešimo."\n"Bandyta: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nepavyko išsiųsti šio pranešimo.\nBandyta: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Nepavyko išsiųsti šio pranešimo."</string>
     <string name="delete_thread" msgid="757258847736632791">"Ištrinti giją"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Persiųsti"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Teksto pranešimo apribojimas"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Daugialypės terpės pranešimo apribojimas"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Įspėjimai"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Pateikti įspėjimus apie pranešimus būsenos juostoje"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibruoti"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Taip pat vibruoti, kai įspėjama"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Pasirinkti skambėjimo toną"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Garsas"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Nustatyti išsaugomų pranešimų skaičių"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Visada"</item>
-    <item msgid="6069709696037750627">"Tik kai veikia tyliai"</item>
-    <item msgid="7486145357487111435">"Niekada"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibruoti"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Begarsis"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatiškai nuskaityti"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automatiškai nuskaityti pranešimus"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatinis tarptinklinio ryšio nuskaitymas"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Pranešimas neišsiųstas"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Palieskite, kad peržiūrėtumėte pranešimą, ir bandykite dar kartą."</string>
     <string name="download_later" msgid="5531365714424360903">"Nepavyko atsisiųsti. Vėliau bandykite dar kartą."</string>
+    <string name="no_apn" msgid="505932916503312015">"Įrenginyje nenurodytas APN."</string>
     <string name="select_audio" msgid="3528161449756771832">"Pasirinkti garso įrašą"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Išsaugoti priedą"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Priedas išsaugotas."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g>–<xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Telefono atmintinėje nebėra vietos"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Negausite naujų SMS ar MMS žinučių"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 19921ac..cb04cd2 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Termiņš: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nepiegādāts ziņojums"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nevarēja nosūtīt šo ziņojumu."\n"Pēdējais mēģinājums: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nevarēja nosūtīt šo ziņojumu.\nPēdējais mēģinājums: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Nevarēja nosūtīt šo ziņojumu."</string>
     <string name="delete_thread" msgid="757258847736632791">"Dzēst pavedienu"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Pārsūtīt"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Īsziņu ierobežojums"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multiziņu ierobežojums"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Paziņojumi"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Attēlot statusa joslā paziņojumu par ziņojumu"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrēt"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrēt, arī saņemot paziņojumu"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Zvana signāla izvēle"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Signāls"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Iestatīt saglabājamo ziņojumu skaitu"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Vienmēr"</item>
-    <item msgid="6069709696037750627">"Tikai klusuma režīmā"</item>
-    <item msgid="7486145357487111435">"Nekad"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrēt"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Klusums"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automātiski izgūt"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automātiski izgūt ziņojumus"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Viesabonēšanas automātiska izgūšana"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Ziņojums nav nosūtīts"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Pieskarieties, lai pārskatītu ziņojumu un mēģinātu vēlreiz."</string>
     <string name="download_later" msgid="5531365714424360903">"Pašlaik nevar veikt lejupielādi. Vēlāk mēģiniet vēlreiz."</string>
+    <string name="no_apn" msgid="505932916503312015">"Ierīcē nav norādīts APN."</string>
     <string name="select_audio" msgid="3528161449756771832">"Audio izvēle"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Pielikuma saglabāšana"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Pielikums ir saglabāts."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> — <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Jūsu tālruņa atmiņa ir pilna."</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Jūs nesaņemsiet jaunas īsziņas/multiziņas."</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index cbe9092..97863b4 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Tamat tempoh: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mesej tidak diserahkan"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tidak dapat menghantar mesej ini "\n"Percubaan dibuat: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tidak dapat menghantar mesej ini \nPercubaan dibuat: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Tidak dapat menghantar mesej ini."</string>
     <string name="delete_thread" msgid="757258847736632791">"Padam urutan"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Kirim semula"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Had mesej teks"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Had mesej multimedia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Pemberitahuan"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Memaparkan mesej pemberitahuan dalam bar status"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Getar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Juga bergetar apabila diberitahu"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Pilih nada dering"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Bunyi"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Tetapkan bilangan mesej untuk disimpan"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Sentiasa"</item>
-    <item msgid="6069709696037750627">"Apabila senyap sahaja"</item>
-    <item msgid="7486145357487111435">"Jangan sekali-kali"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Getar"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Senyap"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Auto dapat kembali"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Dapatkan semula mesej secara automatik"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Auto dapat kembali perayauan"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mesej tidak dihantar"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Sentuh untuk menyemak semula mesej dan cuba lagi."</string>
     <string name="download_later" msgid="5531365714424360903">"Tidak boleh memuat turun sekarang. Cuba lagi kemudian."</string>
+    <string name="no_apn" msgid="505932916503312015">"Tiada APN dinyatakan pada peranti."</string>
     <string name="select_audio" msgid="3528161449756771832">"Pilih audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Menyimpan lampiran"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Lampiran disimpan."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Storan telefon anda penuh"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Anda tidak akan menerima mesej SMS/MMS baharu"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index dab187f..ba87796 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Utgår: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Melding ikke levert"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kunne ikke sende denne meldingen."\n"Antall forsøk: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kunne ikke sende denne meldingen.\nAntall forsøk: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Kunne ikke sende denne meldingen."</string>
     <string name="delete_thread" msgid="757258847736632791">"Slett tråd"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Videresend"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"SMS-meldingsgrense"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"MMS-meldingsgrense"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Varslinger"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Vis meldingsvarslinger i statusfeltet"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrering"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrer også ved varslinger"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Velg ringetone"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Lyd"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Angi antall meldinger som skal lagres"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Alltid"</item>
-    <item msgid="6069709696037750627">"Kun ved stille modus"</item>
-    <item msgid="7486145357487111435">"Aldri"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrering"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Stille"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Autohent"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Hent meldinger automatisk"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatisk henting ved bruk i utlandet"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Melding ikke sendt"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Trykk for å lese gjennom meldingen og prøve på nytt."</string>
     <string name="download_later" msgid="5531365714424360903">"Kan ikke laste ned akkurat nå. Prøv på nytt senere."</string>
+    <string name="no_apn" msgid="505932916503312015">"Ingen APN angitt på enheten."</string>
     <string name="select_audio" msgid="3528161449756771832">"Velg lyd"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Lagre vedlegg"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Vedlegg lagret."</string>
@@ -347,4 +341,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Lagringsplassen på telefonen din er full"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Du kommer ikke til å motta nye SMS- og MMS-meldinger"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 2f49f8b..fded6a8 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Verloopt op: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Niet-afgeleverd bericht"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kan dit bericht niet verzenden."\n"Poging gedaan: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Kan dit bericht niet verzenden.\nPoging gedaan: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Kan dit bericht niet verzenden."</string>
     <string name="delete_thread" msgid="757258847736632791">"Thread verwijderen"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Doorsturen"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limiet voor sms"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limiet voor MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Meldingen"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Berichtmeldingen in statusbalk weergeven"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Trillen"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Ook trillen bij melding"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Beltoon kiezen"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Geluid"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Aantal berichten instellen voor opslaan"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Altijd"</item>
-    <item msgid="6069709696037750627">"Alleen indien stil"</item>
-    <item msgid="7486145357487111435">"Nooit"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Trillen"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Stil"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatisch ophalen"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Berichten automatisch ophalen"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Ophalen tijdens roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Bericht niet verzonden"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Raak dit aan om het bericht te controleren en opnieuw te proberen."</string>
     <string name="download_later" msgid="5531365714424360903">"Kan nu niet downloaden. Probeer het later opnieuw."</string>
+    <string name="no_apn" msgid="505932916503312015">"Geen APN gespecificeerd op het apparaat."</string>
     <string name="select_audio" msgid="3528161449756771832">"Audio selecteren"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Bijlage opslaan"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Bijlage opgeslagen."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"De opslagruimte van uw telefoon is vol"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"U ontvangt geen nieuwe sms\'jes/mms\'jes"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 2560463..394d8a3 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Traci ważność: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Wiadomość niedostarczona"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nie można wysłać wiadomości."\n"Ostatnia próba: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Nie można wysłać wiadomości.\nOstatnia próba: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Nie udało się wysłać wiadomości."</string>
     <string name="delete_thread" msgid="757258847736632791">"Usuń wątek"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Prześlij dalej"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limit wiadomości tekstowych"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limit wiadomości MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Powiadomienia"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Wyświetlaj powiadomienie o wiadomości na pasku stanu"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Wibracje"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Wibracje również przy powiadomieniu"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Wybierz dzwonek"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Dźwięk"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Ustaw liczbę zapisywanych wiadomości"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Zawsze"</item>
-    <item msgid="6069709696037750627">"Tylko po wyciszeniu"</item>
-    <item msgid="7486145357487111435">"Nigdy"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Wibracje"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Wyciszony"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Autoodbieranie"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Odbierz wiadomości automatycznie"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"W roamingu"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Wiadomość nie została wysłana"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Dotknij, aby sprawdzić wiadomość i spróbować ponownie."</string>
     <string name="download_later" msgid="5531365714424360903">"Nie można teraz pobrać. Spróbuj ponownie później."</string>
+    <string name="no_apn" msgid="505932916503312015">"Na urządzeniu nie określono żadnego punktu dostępowego."</string>
     <string name="select_audio" msgid="3528161449756771832">"Wybierz dźwięk"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Zapisz załącznik"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Załącznik został zapisany."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Pamięć Twojego telefonu jest pełna"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Nie będziesz otrzymywać nowych SMS-ów/MMS-ów"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index b41ea4f..a5014b8 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>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Expira: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mensagem não entregue"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Não foi possível enviar esta mensagem."\n"Tentativa efetuada: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Não foi possível enviar esta mensagem.\nTentativa efetuada: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Não é possível enviar esta mensagem."</string>
     <string name="delete_thread" msgid="757258847736632791">"Eliminar tópico"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Reencaminhar"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limite de mensagens de texto"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limite mens. multimédia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificações"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Apresentar notificações de mensagem na barra de estado"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrar também quando notificado"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Escolher toque"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Som"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Definir o número de mensagens a guardar"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Sempre"</item>
-    <item msgid="6069709696037750627">"Apenas quando está em silêncio"</item>
-    <item msgid="7486145357487111435">"Nunca"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrar"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silencioso"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Retoma automática"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Retomar mensagens automaticamente"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Recuper. em roaming"</string>
@@ -213,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>
@@ -283,6 +276,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mensagem não enviada"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Toque para rever a mensagem e tente novamente."</string>
     <string name="download_later" msgid="5531365714424360903">"Não é possível transferir agora. Tente novamente mais tarde."</string>
+    <string name="no_apn" msgid="505932916503312015">"Não existe um APN especificado no dispositivo."</string>
     <string name="select_audio" msgid="3528161449756771832">"Escolher áudio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Guardar anexo"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Anexo guardado."</string>
@@ -351,4 +345,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"A memória do seu telemóvel está cheia"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Não vai receber mensagens SMS/MMS novas"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 1ee10a8..c6defc2 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Expira: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"Kb"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mensagem não entregue"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Não foi possível enviar esta mensagem."\n"Tentativa realizada: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Não foi possível enviar esta mensagem.\nTentativa realizada: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Não foi possível enviar esta mensagem."</string>
     <string name="delete_thread" msgid="757258847736632791">"Excluir conversa"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Encaminhar"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limite de mensagens de texto"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limite de mens. multimídia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificações"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Exibe notificações de mensagem na barra de status"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Também vibra quando notificado"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Escolher toque"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Som"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Definir número de mensagens a serem salvas"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Sempre"</item>
-    <item msgid="6069709696037750627">"Apenas quando estiver no silencioso"</item>
-    <item msgid="7486145357487111435">"Nunca"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrar"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silencioso"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Recuperação automática"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Recupera mensagens automaticamente"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Recuper. em roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mensagem não enviada"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Toque para revisar a mensagem e tentar novamente."</string>
     <string name="download_later" msgid="5531365714424360903">"Não é possível fazer download no momento. Tente novamente mais tarde."</string>
+    <string name="no_apn" msgid="505932916503312015">"Nenhum APN especificado no dispositivo."</string>
     <string name="select_audio" msgid="3528161449756771832">"Selecionar áudio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Salvar anexo"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Anexo salvo."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"O armazenamento do telefone está cheio"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Você não receberá novas mensagens SMS/MMS"</string>
 </resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 03b52e3..f19f9c5 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -202,18 +202,12 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limita dad SMS"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limita per ils MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Avis"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Mussar in avis en la trav da status en cas da l\'entrada da novs messadis"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrar"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Era vibrar cun ils avis"</string>
-    <!-- no translation found for pref_title_notification_ringtone (5467265237106229952) -->
+    <!-- no translation found for pref_title_notification_ringtone (8667533917574559659) -->
     <skip />
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Definir il dumber da messadis per memorisar"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Adina"</item>
-    <item msgid="6069709696037750627">"Mo sche silenzius"</item>
-    <item msgid="7486145357487111435">"Mai"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrar"</string>
+    <!-- no translation found for silent_ringtone (7981237991326592780) -->
+    <skip />
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Retschaiver automaticamain"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Retschaiver automaticamain ils messadis"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Retschaiver autom. cun roaming"</string>
@@ -331,6 +325,8 @@
     <skip />
     <!-- no translation found for download_later (5531365714424360903) -->
     <skip />
+    <!-- no translation found for no_apn (505932916503312015) -->
+    <skip />
     <!-- no translation found for select_audio (3528161449756771832) -->
     <skip />
     <!-- no translation found for copy_to_sdcard (757028609638184856) -->
@@ -424,4 +420,8 @@
     <skip />
     <!-- no translation found for message_timestamp_format (4188999027493614617) -->
     <skip />
+    <!-- no translation found for storage_warning_title (7124740686325942375) -->
+    <skip />
+    <!-- no translation found for storage_warning_content (1100367816649962354) -->
+    <skip />
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 11bf5b9..9231346 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -29,8 +29,8 @@
     <string name="menu_add_address_to_contacts" msgid="4491980950419914944">"Adăugaţi <xliff:g id="CONTACTEMAILORNUMBER">%s</xliff:g> la Persoane"</string>
     <string name="menu_call" msgid="5877123227307074690">"Apelaţi"</string>
     <string name="menu_search" msgid="2289469305728821360">"Căutaţi"</string>
-    <string name="menu_delete_all" msgid="808729454898114735">"Ştergeţi toate conversaţiile"</string>
-    <string name="menu_delete" msgid="1851666911396479006">"Ştergeţi conversaţia"</string>
+    <string name="menu_delete_all" msgid="808729454898114735">"Ștergeţi toate conversaţiile"</string>
+    <string name="menu_delete" msgid="1851666911396479006">"Ștergeţi conversaţia"</string>
     <string name="menu_view" msgid="7448852683948080108">"Vizualizaţi firele de conversaţie"</string>
     <string name="menu_debug_dump" msgid="6855869130206549643">"DEBUG DUMP"</string>
     <string name="refreshing" msgid="987335057871404222">"Se actualizează..."</string>
@@ -41,13 +41,13 @@
     <string name="view_slideshow" msgid="4990460971211388453">"Vizualizaţi prezentarea"</string>
     <string name="view_message_details" msgid="7613457646645796831">"Afişaţi detaliile"</string>
     <string name="view_delivery_report" msgid="8219095994071575215">"Vizualizaţi raportul"</string>
-    <string name="delete_message" msgid="6442394955910357234">"Ştergeţi"</string>
+    <string name="delete_message" msgid="6442394955910357234">"Ștergeţi"</string>
     <string name="expire_on" msgid="4436268382742593921">"Expiră la: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KO"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Mesaje neexpediate"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Acest mesaj nu s-a putut trimite."\n"Încercare efectuată: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Acest mesaj nu s-a putut trimite.\nÎncercare efectuată: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Acest mesaj nu s-a putut trimite."</string>
-    <string name="delete_thread" msgid="757258847736632791">"Ştergeţi conversaţia"</string>
+    <string name="delete_thread" msgid="757258847736632791">"Ștergeţi conversaţia"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Redirecţionaţi"</string>
     <string name="download" msgid="4488877642230207631">"Descărcaţi"</string>
     <string name="downloading" msgid="1779557575565350637">"Se descarcă"</string>
@@ -160,7 +160,7 @@
     <string name="pref_summary_mms_delivery_reports" msgid="4874657984217756112">"Solicitaţi un raport de expediere pentru fiecare mesaj trimis"</string>
     <string name="pref_summary_mms_read_reports" msgid="2748323864008907440">"Solicitaţi un raport de citire pentru fiecare mesaj trimis"</string>
     <string name="pref_summary_sms_delivery_reports" msgid="5852207702358546129">"Solicitaţi un raport de expediere pentru fiecare mesaj trimis"</string>
-    <string name="pref_summary_auto_delete" msgid="7719538116486177613">"Ştergeţi mesajele vechi când se atinge limita"</string>
+    <string name="pref_summary_auto_delete" msgid="7719538116486177613">"Ștergeţi mesajele vechi când se atinge limita"</string>
     <string name="pref_summary_delete_limit" msgid="597128041393045216">"<xliff:g id="COUNT">%1$s</xliff:g> (de) mesaje pe conversaţie"</string>
     <string name="pref_summary_mms_group_mms" msgid="2266928687440696745">"Utilizaţi serviciul MMS pentru a trimite un singur mesaj, atunci când există mai mulţi destinatari"</string>
     <string name="pref_title_manage_sim_messages" msgid="7820895261757881177">"Gestionaţi mesajele de pe cardul SIM"</string>
@@ -168,27 +168,20 @@
     <string name="pref_title_mms_group_mms" msgid="1251698526583908432">"Mesagerie de grup"</string>
     <string name="pref_title_mms_read_reports" msgid="4882179829495828541">"Citiţi rapoartele"</string>
     <string name="pref_title_sms_delivery_reports" msgid="876064208254451038">"Rapoarte de expediere"</string>
-    <string name="pref_title_auto_delete" msgid="7227835692631701134">"Ştergeţi mesajele vechi"</string>
+    <string name="pref_title_auto_delete" msgid="7227835692631701134">"Ștergeţi mesajele vechi"</string>
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limita mesajului text"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limita pentru mesajele multimedia"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Notificări"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Afişaţi notificări de mesaje în bara de stare"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrare"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrare şi la notificări"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Alegeţi un ton de sonerie"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sunet"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Setaţi numărul de mesaje pentru salvare"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Întotdeauna"</item>
-    <item msgid="6069709696037750627">"Numai când profilul este Silenţios"</item>
-    <item msgid="7486145357487111435">"Niciodată"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrare"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Silențios"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Preluare automată"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Primiţi automat mesajele"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Preluare automată a mesajelor când este activat serviciul de roaming"</string>
     <string name="pref_summary_mms_retrieval_during_roaming" msgid="2427892806582531020">"Preluarea automată a mesajelor în roaming"</string>
-    <string name="confirm_dialog_title" msgid="2187213750475782725">"Ştergeţi?"</string>
-    <string name="confirm_dialog_locked_title" msgid="8179085718150932242">"Ştergeţi mesajul blocat?"</string>
+    <string name="confirm_dialog_title" msgid="2187213750475782725">"Ștergeţi?"</string>
+    <string name="confirm_dialog_locked_title" msgid="8179085718150932242">"Ștergeţi mesajul blocat?"</string>
   <plurals name="confirm_delete_conversation">
     <item quantity="one" msgid="7509396981963472833">"O conversaţie va fi ştearsă."</item>
     <item quantity="other" msgid="4076915774012081811">"<xliff:g id="NUMBER">%1$s</xliff:g> (de) conversaţii vor fi şterse."</item>
@@ -196,11 +189,11 @@
     <string name="confirm_delete_conversation" msgid="6115966687389279855">"Firul de conversaţie va fi şters în întregime."</string>
     <string name="confirm_delete_all_conversations" msgid="9036166685235092757">"Toate firele de conversaţie vor fi şterse."</string>
     <string name="confirm_delete_message" msgid="9121480656609809591">"Mesajul va fi şters."</string>
-    <string name="confirm_delete_locked_message" msgid="7203411948190100955">"Ştergeţi acest mesaj blocat?"</string>
+    <string name="confirm_delete_locked_message" msgid="7203411948190100955">"Ștergeţi acest mesaj blocat?"</string>
     <string name="confirm_delete_all_SIM_messages" msgid="8693652297557966665">"Toate mesajele de pe cardul SIM vor fi şterse."</string>
     <string name="confirm_delete_SIM_message" msgid="8535128079045452425">"Acest mesaj de pe cardul SIM va fi şters."</string>
-    <string name="delete_unlocked" msgid="7545321606698162261">"Ştergeţi mesajele blocate"</string>
-    <string name="delete" msgid="1409973060081564612">"Ştergeţi"</string>
+    <string name="delete_unlocked" msgid="7545321606698162261">"Ștergeţi mesajele blocate"</string>
+    <string name="delete" msgid="1409973060081564612">"Ștergeţi"</string>
     <string name="select_conversations" msgid="7816517565640002844">"Alegeţi conversaţii"</string>
     <string name="no_conversations" msgid="6773767655149258445">"Nicio conversaţie."</string>
     <string name="loading_conversations" msgid="5510430408235967094">"Conversaţiile se încarcă."</string>
@@ -225,12 +218,12 @@
     <string name="message_class_label" msgid="5115748690481550562">"Clasa de mesaj: "</string>
     <string name="error_code_label" msgid="5810068247647058769">"Cod de eroare: "</string>
     <string name="menu_edit" msgid="3056513240155967777">"Modificaţi"</string>
-    <string name="menu_delete_messages" msgid="2610286783513247420">"Ştergeţi mesajele"</string>
+    <string name="menu_delete_messages" msgid="2610286783513247420">"Ștergeţi mesajele"</string>
     <string name="menu_lock" msgid="7603793126077557472">"Blocaţi"</string>
     <string name="menu_unlock" msgid="6662865558677424039">"Deblocaţi"</string>
     <string name="sim_copy_to_phone_memory" product="tablet" msgid="3721000641648594602">"Copiaţi în memoria computerului tablet PC"</string>
     <string name="sim_copy_to_phone_memory" product="default" msgid="5895767417844881971">"Copiaţi în memoria telefonului"</string>
-    <string name="sim_delete" msgid="610790510655316922">"Ştergeţi"</string>
+    <string name="sim_delete" msgid="610790510655316922">"Ștergeţi"</string>
     <string name="sim_manage_messages_title" msgid="3989147182100584333">"Mesaje text pe cardul SIM"</string>
     <string name="sim_view" msgid="1997173541766393706">"Vizualizaţi"</string>
     <string name="sim_empty" msgid="2356766833071636297">"Niciun mesaj pe acest card SIM."</string>
@@ -260,9 +253,9 @@
     <string name="notification_failed_multiple" msgid="6192531993698497229">"<xliff:g id="COUNT">%s</xliff:g> (de) mesaje nu au putut fi trimise"</string>
     <string name="notification_failed_multiple_title" msgid="1112032024904397126">"Mesajele nu au fost trimise"</string>
     <string name="sim_full_title" msgid="1809829051697702810">"Card SIM plin"</string>
-    <string name="sim_full_body" msgid="6241664980790322925">"Ştergeţi câteva mesaje pentru a crea spaţiu pentru mai multe."</string>
+    <string name="sim_full_body" msgid="6241664980790322925">"Ștergeţi câteva mesaje pentru a crea spaţiu pentru mai multe."</string>
     <string name="sms_full_title" msgid="6041287140125011854">"Memoria pentru mesajul text este plină"</string>
-    <string name="sms_full_body" msgid="4885861852397651868">"Un mesaj primit a fost respins din lipsă de memorie. Ştergeţi unele dintre mesajele vechi."</string>
+    <string name="sms_full_body" msgid="4885861852397651868">"Un mesaj primit a fost respins din lipsă de memorie. Ștergeţi unele dintre mesajele vechi."</string>
     <string name="sms_rejected_title" msgid="8548394922491529811">"Mesaj text respins"</string>
     <string name="sms_rejected_body" msgid="7813671133968158562">"Un mesaj primit a fost respins dintr-un motiv necunoscut."</string>
     <string name="type_audio" msgid="3842064092332309756">"audio"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Mesajul nu a fost trimis."</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Atingeţi pentru a revedea mesajul şi încercaţi din nou."</string>
     <string name="download_later" msgid="5531365714424360903">"Mesajele nu se pot descărca acum. Încercaţi din nou mai târziu."</string>
+    <string name="no_apn" msgid="505932916503312015">"Niciun APN specificat pe dispozitiv."</string>
     <string name="select_audio" msgid="3528161449756771832">"Alegeţi componenta audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Salvaţi ataşamentul"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Ataşamentul a fost salvat."</string>
@@ -315,10 +309,10 @@
     <item quantity="other" msgid="6196382287749539209">"<xliff:g id="NUMBER">%1$s</xliff:g> (de) rezultate pentru „<xliff:g id="SEARCH">%2$s</xliff:g>”"</item>
   </plurals>
     <string name="search_history" msgid="4127805495662693154">"<xliff:g id="COUNT">%1$s</xliff:g> (de) rezultate pentru „<xliff:g id="SEARCH">%2$s</xliff:g>”"</string>
-    <string name="confirm_clear_search_title" msgid="8510295993632032904">"Ştergeţi"</string>
+    <string name="confirm_clear_search_title" msgid="8510295993632032904">"Ștergeţi"</string>
     <string name="confirm_clear_search_text" msgid="8731877031837077478">"Istoricul căutărilor va fi şters."</string>
-    <string name="pref_mms_clear_search_history_title" msgid="6159758850628148164">"Ştergeţi istoricul căutărilor"</string>
-    <string name="pref_mms_clear_search_history_summary" msgid="7960005384066460035">"Ştergeţi căutarea anterioară de mesaje, pentru a evita afişarea acesteia în caseta de căutare."</string>
+    <string name="pref_mms_clear_search_history_title" msgid="6159758850628148164">"Ștergeţi istoricul căutărilor"</string>
+    <string name="pref_mms_clear_search_history_summary" msgid="7960005384066460035">"Ștergeţi căutarea anterioară de mesaje, pentru a evita afişarea acesteia în caseta de căutare."</string>
     <string name="save" msgid="6847069284991531310">"Salvaţi"</string>
     <string name="storage_limits_title" msgid="7074684882530693016">"Limita de mesaje"</string>
     <string name="storage_limits_message" msgid="2010501485394745696">"Doriţi să limitaţi numărul de mesaje salvate pe conversaţie?"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Spațiul de stocare al telefonului este plin"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Nu veți mai primi mesaje SMS/MMS"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0ab1164..2e18754 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/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>
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Срок истекает: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"КБ"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Недоставленное сообщение"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Не удалось отправить сообщение."\n"Попыток: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Не удалось отправить сообщение.\nПопыток: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Не удалось отправить сообщение."</string>
     <string name="delete_thread" msgid="757258847736632791">"Удалить цепочку"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Переслать"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Макс. количество SMS"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Макс. количество MMS"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Уведомления"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Отображать уведомления о сообщениях в строке состояния"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Вибросигнал"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Вибросигнал при получении уведомления"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Выбрать рингтон"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Рингтон"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Установить макс. кол-во сообщений в цепочке для хранения"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Всегда"</item>
-    <item msgid="6069709696037750627">"Только в режиме без звука"</item>
-    <item msgid="7486145357487111435">"Никогда"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Вибросигнал"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Без звука"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Автозагрузка"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Автоматически загружать MMS-сообщения"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Автозагрузка в роуминге"</string>
@@ -231,12 +224,12 @@
     <string name="sim_copy_to_phone_memory" product="tablet" msgid="3721000641648594602">"Копировать в память планшетного ПК"</string>
     <string name="sim_copy_to_phone_memory" product="default" msgid="5895767417844881971">"Копировать в память телефона"</string>
     <string name="sim_delete" msgid="610790510655316922">"Удалить"</string>
-    <string name="sim_manage_messages_title" msgid="3989147182100584333">"Текстовые сообщения на SIM-карте"</string>
+    <string name="sim_manage_messages_title" msgid="3989147182100584333">"SMS на SIM-карте"</string>
     <string name="sim_view" msgid="1997173541766393706">"Просмотр"</string>
     <string name="sim_empty" msgid="2356766833071636297">"На SIM-карте нет сообщений."</string>
     <string name="delivery_header_title" msgid="5361719578869045764">"Отчет"</string>
     <string name="status_none" msgid="8253075950774894961">"(Нет)"</string>
-    <string name="status_pending" msgid="2739860824607984892">"Ожидает"</string>
+    <string name="status_pending" msgid="2739860824607984892">"Ожидается отправка"</string>
     <string name="status_read" msgid="7576195253780627332">"Прочитанные"</string>
     <string name="status_received" msgid="7264334589461906965">"Получено"</string>
     <string name="status_failed" msgid="1998776666856593544">"Сбой"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Сообщение не отправлено"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Нажмите, чтобы просмотреть сообщение и повторить попытку."</string>
     <string name="download_later" msgid="5531365714424360903">"Не удалось загрузить сообщения. Повторите попытку позже."</string>
+    <string name="no_apn" msgid="505932916503312015">"Для этого устройства не задано ни одной точки доступа."</string>
     <string name="select_audio" msgid="3528161449756771832">"Выбор аудио"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Сохранить приложение"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Файл сохранен."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Недостаточно места"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Вы не сможете получать новые SMS и MMS."</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index a25b577..90a1c52 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Platnosť končí: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nedoručená správa"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Túto správu sa nepodarilo odoslať."\n"Posledný pokus: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Túto správu sa nepodarilo odoslať.\nPosledný pokus: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Správu sa nepodarilo odoslať."</string>
     <string name="delete_thread" msgid="757258847736632791">"Odstrániť konverzáciu"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Poslať ďalej"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limit textových správ"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limit multimediálnych správ"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Upozornenia"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Zobraziť oznámenia o správe v stavovom riadku"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrovať"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Pri oznámení tiež vibrovať"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Zvoliť tón zvonenia"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Zvuk"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Nastaviť počet ukladaných správ"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Vždy"</item>
-    <item msgid="6069709696037750627">"Iba v tichom režime"</item>
-    <item msgid="7486145357487111435">"Nikdy"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrovať"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Tichý"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Automatické načítanie"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Automaticky načítať správy"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Automatické načítanie pri roamingu"</string>
@@ -281,11 +274,12 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Správu sa nepodarilo odoslať"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Dotykom skontrolujte správu a skúste ju odoslať znova."</string>
     <string name="download_later" msgid="5531365714424360903">"Správu momentálne nie je možné prevziať. Skúste to znova neskôr."</string>
+    <string name="no_apn" msgid="505932916503312015">"V zariadení nie je zadaný žiadny názov prístupového bodu (APN)."</string>
     <string name="select_audio" msgid="3528161449756771832">"Zvoliť zvuk"</string>
     <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>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Úložisko vášho telefónu je plné"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Nedostanete žiadne správy SMS/MMS"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 83ec7de..57cfc47 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Poteče: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Nedostavljeno sporočilo"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tega sporočila ni bilo mogoče poslati."\n"Čas poskusa: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Tega sporočila ni bilo mogoče poslati.\nČas poskusa: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Tega sporočila ni bilo mogoče poslati."</string>
     <string name="delete_thread" msgid="757258847736632791">"Izbriši nit"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Posreduj"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Omejitev besedilnega sporočila"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Omejitev večpredstavnega sporočila"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Obvestila"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Prikaži obvestila o sporočilih v vrstici stanja"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibriranje"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibriraj tudi pri obvestilu"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Izberite melodijo zvonjenja"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Zvok"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Nastavi število sporočil za shranjevanje"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Vedno"</item>
-    <item msgid="6069709696037750627">"Samo v tihem načinu"</item>
-    <item msgid="7486145357487111435">"Nikoli"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibriranje"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Tiho"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Samodejni prenos"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Samodejni prenos sporočil"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Samodejna pridobitev med gostovanjem"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Sporočilo ni bilo poslano"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Dotaknite se, če želite pregledati sporočilo in poskusiti znova."</string>
     <string name="download_later" msgid="5531365714424360903">"Prenos trenutno ni mogoč. Poskusite znova pozneje."</string>
+    <string name="no_apn" msgid="505932916503312015">"V napravi ni določen APN."</string>
     <string name="select_audio" msgid="3528161449756771832">"Izberite zvočno datoteko"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Shrani prilogo"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Priloga je bila shranjena."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Shramba v telefonu je polna"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Novih sporočil SMS/MMS ne boste prejemali"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 2f3721e..2f44ed3 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Истиче: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Неиспоручена порука"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Није могуће послати ову поруку."\n"Време последњег покушаја: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Није могуће послати ову поруку.\nВреме последњег покушаја: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Није могуће послати ову поруку."</string>
     <string name="delete_thread" msgid="757258847736632791">"Избриши преписку"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Проследи"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Ограничење за текстуалну поруку"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Ограничење за мултимедијалну поруку"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Обавештења"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Прикажи обавештења о порукама у статусној траци"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Вибрација"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Такође укључи вибрацију када добијем обавештење"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Изабери мелодију звона"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Звук"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Подеси број порука за чување"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Увек"</item>
-    <item msgid="6069709696037750627">"Само у нечујном режиму"</item>
-    <item msgid="7486145357487111435">"Никад"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Вибрација"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Нечујно"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Аутоматско преузимање"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Аутоматско преузимање порука"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Аутоматско преузимање у ромингу"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Порука није послата"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Додирните да бисте прегледали поруку и покушали поново."</string>
     <string name="download_later" msgid="5531365714424360903">"Преузимање тренутно није могуће. Покушајте поново касније."</string>
+    <string name="no_apn" msgid="505932916503312015">"Назив приступне тачке није наведен на уређају."</string>
     <string name="select_audio" msgid="3528161449756771832">"Избор звука"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Сачувај прилог"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Прилог је сачуван."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Меморија телефона је пуна"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Нећете добијати нове SMS/MMS поруке"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index d31562d..876f893 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Upphör att gälla: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"kB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Meddelande som inte levererats"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Det gick inte att skicka meddelandet."\n"Försökte: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Det gick inte att skicka meddelandet.\nFörsökte: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Det gick inte att skicka meddelandet."</string>
     <string name="delete_thread" msgid="757258847736632791">"Ta bort tråd"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Vidarebefordra"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"SMS-begränsning"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"MMS-begränsning"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Aviseringar"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Visa meddelandeaviseringar i statusfältet"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Vibrera"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Vibrera även vid aviseringar"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Välj ringsignal"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Ljud"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Ange hur många meddelanden som ska sparas"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Alltid"</item>
-    <item msgid="6069709696037750627">"Bara vid tyst"</item>
-    <item msgid="7486145357487111435">"Aldrig"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Vibrera"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Tyst"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Autohämtning"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Hämta meddelanden automatiskt"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Autohämtning vid roaming"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Meddelandet har inte skickats"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Tryck om du vill granska meddelandet och försöka igen."</string>
     <string name="download_later" msgid="5531365714424360903">"Det går inte att hämta just nu. Försök igen senare."</string>
+    <string name="no_apn" msgid="505932916503312015">"Inget APN har angetts på enheten."</string>
     <string name="select_audio" msgid="3528161449756771832">"Välj ljud"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Spara bilagan"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Bilagan har sparats."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Mobilens lagringsutrymme är fullt"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Du kommer inte att få några nya SMS/MMS"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 6a2718f..1970ca3 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Inaisha muda:<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Ujumbe ambao haujatumwa"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Haiwezi kutuma ujumbe huu."\n"Jaribio lilifanywa:<xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Haiwezi kutuma ujumbe huu.\nJaribio lilifanywa:<xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Haiwezi kutuma ujumbe huu"</string>
     <string name="delete_thread" msgid="757258847736632791">"Futa utungo"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Sambaza"</string>
@@ -56,7 +56,7 @@
     <string name="insufficient_drm_rights" msgid="6989074725969164383">"Haki za DRM zizizotosha zimegunduliwa."</string>
     <string name="copy_message_text" msgid="4296252229544252834">"Nakili maandishi"</string>
     <string name="message_options" msgid="6838806653341967550">"Chaguo za ujumbe"</string>
-    <string name="slideshow_options" msgid="3501759384739796431">"Chaguo za Onyeshoslaidi"</string>
+    <string name="slideshow_options" msgid="3501759384739796431">"Chaguo za onyesho la slaidi"</string>
     <string name="done" msgid="7479926268660505871">"Kwisha"</string>
     <string name="move_up" msgid="7307578107431739359">"Sogea juu"</string>
     <string name="move_down" msgid="6356509518429479053">"Sogeza chini"</string>
@@ -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>
@@ -172,32 +172,25 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Mpaka wa ujumbe wa maandishi"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Kikomo cha ujumbe wa media"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Arifa"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Onyesha arifa za ujumbe kwenye mwambaa hali"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Tetema"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Pia tikisa unapoarifiwa"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Chagua mlio wa simu"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Sauti"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Weka nambari ya ujumbe wa kuhifadhi"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Kila wakati"</item>
-    <item msgid="6069709696037750627">"Kukiwa kimya tu"</item>
-    <item msgid="7486145357487111435">"Katu"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Tetema"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Kimya"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Epua kiotomatiki"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Epua kiotomatiki ujumbe"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Kurejesha kiotomatiki urandaji"</string>
     <string name="pref_summary_mms_retrieval_during_roaming" msgid="2427892806582531020">"Epua kiotomatiki ujumbe wakati wa urandaji"</string>
-    <string name="confirm_dialog_title" msgid="2187213750475782725">"Futa?"</string>
+    <string name="confirm_dialog_title" msgid="2187213750475782725">"Yafutwe?"</string>
     <string name="confirm_dialog_locked_title" msgid="8179085718150932242">"Futa ujumbe uliofungwa?"</string>
   <plurals name="confirm_delete_conversation">
-    <item quantity="one" msgid="7509396981963472833">"Zungumzo moja litafutwa."</item>
+    <item quantity="one" msgid="7509396981963472833">"Mazungumzo 1 yatafutwa."</item>
     <item quantity="other" msgid="4076915774012081811">"Mazungumzo <xliff:g id="NUMBER">%1$s</xliff:g> yatafutwa"</item>
   </plurals>
     <string name="confirm_delete_conversation" msgid="6115966687389279855">"Utungo wote utafutwa"</string>
     <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>
@@ -233,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>
@@ -259,9 +252,9 @@
     <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_title" msgid="1809829051697702810">"SIM kadi imejaa"</string>
     <string name="sim_full_body" msgid="6241664980790322925">"Futa ujumbe mwingine ili kuweka nafasi ya zaidi."</string>
     <string name="sms_full_title" msgid="6041287140125011854">"Kumbukumbu ya ujumbe wa maandishi imejaa"</string>
     <string name="sms_full_body" msgid="4885861852397651868">"Ujumbe unaoingia umekataliwa kwa sababu kumbukumbu imejaa. Futa baadhi ya ujumbe za zamani."</string>
@@ -283,6 +276,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Ujumbe haujatumwa"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Gusa ili kukagua ujumbe na ujaribu tena."</string>
     <string name="download_later" msgid="5531365714424360903">"Haiwezi kupakua sasa hizi. Jaribu tena baadaye."</string>
+    <string name="no_apn" msgid="505932916503312015">"Hakuna APN iliyobainishwa kwenye kifaa."</string>
     <string name="select_audio" msgid="3528161449756771832">"Chagua sauti"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Hifadhi kiambatisho"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Kiambatisho kimehifadhiwa"</string>
@@ -323,19 +317,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>
@@ -345,10 +339,12 @@
     <string name="disable_notifications_dialog_message" msgid="7644011234972074096">"Komesha arifa  zilizorudufishwa za ujumbe wa maandishi kutoka kwa programu msingi ya Ujumbe?"</string>
     <string name="view_more_conversations" msgid="7275920132410734671">"Ona mazungumzo zaidi"</string>
     <string name="attachment_audio" msgid="2867960243300539143">"Sauti"</string>
-    <string name="attachment_slideshow" msgid="5867689101881763318">"Onyeshoslaidi"</string>
+    <string name="attachment_slideshow" msgid="5867689101881763318">"Onyesho la slaidi"</string>
     <string name="attachment_video" msgid="4252062022156694591">"Video"</string>
     <string name="attachment_picture" msgid="3709286281567284296">"Picha"</string>
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Hifadhi ya simu yako imejaa"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Hutapokea ujumbe mpya wa SMS/MMS"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 2cc3348..f684ca6 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"หมดอายุ: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"ข้อความที่ไม่ได้ส่ง"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"ไม่สามารถส่งข้อความนี้"\n"พยายามส่ง: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"ไม่สามารถส่งข้อความนี้\nพยายามส่ง: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"ไม่สามารถส่งข้อความนี้"</string>
     <string name="delete_thread" msgid="757258847736632791">"ลบชุดข้อความ"</string>
     <string name="menu_forward" msgid="9026858380050046756">"ส่งต่อ"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"ข้อความตัวอักษรสูงสุด"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"ข้อความมัลติมีเดียสูงสุด"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"การแจ้งเตือน"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"แสดงการแจ้งเตือนข้อความบนแถบสถานะ"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"สั่น"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"สั่นด้วยเมื่อมีการแจ้งเตือน"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"เลือกเสียงเรียกเข้า"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"เสียง"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"ตั้งค่าจำนวนข้อความที่จะบันทึก"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"ทุกครั้ง"</item>
-    <item msgid="6069709696037750627">"เฉพาะเมื่อปิดเสียง"</item>
-    <item msgid="7486145357487111435">"ไม่ต้องเลย"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"สั่น"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"ปิดเสียง"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"เรียกอัตโนมัติ"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"เรียกดูข้อความอัตโนมัติ"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"เรียกอัตโนมัติขณะโรมมิ่ง"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"ไม่ได้ส่งข้อความ"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"แตะเพื่อตรวจสอบข้อความและลองอีกครั้ง"</string>
     <string name="download_later" msgid="5531365714424360903">"ไม่สามารถดาวน์โหลดได้ในขณะนี้ ลองอีกครั้งในภายหลัง"</string>
+    <string name="no_apn" msgid="505932916503312015">"ไม่มี APN ที่ระบุไว้ในอุปกรณ์"</string>
     <string name="select_audio" msgid="3528161449756771832">"เลือกเสียง"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"บันทึกเอกสารแนบ"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"บันทึกไฟล์แนบแล้ว"</string>
@@ -336,7 +330,7 @@
     <item quantity="other" msgid="1393002799298501480">"อีก <xliff:g id="NUMBER">%1$s</xliff:g> ข้อความ"</item>
   </plurals>
     <string name="error_state" msgid="5177386717626893507">"สถานะไม่คงที่"</string>
-    <string name="error_state_text" msgid="7918866106335387710">"สถานะของชุดอีเมลที่เกี่ยวข้องกันและผู้รับไม่สอดคล้องกัน โปรดสร้างรายงานข้อผิดพลาดและรายงานผ่านทาง http://go/droidanizer"</string>
+    <string name="error_state_text" msgid="7918866106335387710">"สถานะของชุดอีเมลที่เกี่ยวข้องกันและผู้รับไม่สอดคล้องกัน โปรดสร้างรายงานบั๊กและรายงานผ่านทาง http://go/droidanizer"</string>
   <plurals name="recipient_count">
     <item quantity="other" msgid="7338580381574463136">"<xliff:g id="NUMBER">%1$s</xliff:g> คน"</item>
   </plurals>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"พื้นที่จัดเก็บของโทรศัพท์ของคุณเต็ม"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"คุณจะไม่ได้รับข้อความ SMS/MMS ใหม่"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 70426f0..f5df6b6 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Mag-e-expire sa: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Hindi naipadalang mensahe"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Hindi maipadala ang mensaheng ito."\n"Pagtatangkang isinagawa: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Hindi maipadala ang mensaheng ito.\nPagtatangkang isinagawa: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Hindi maipadala ang mensaheng ito."</string>
     <string name="delete_thread" msgid="757258847736632791">"Tanggalin ang thread"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Ipasa"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Limitasyon ng text message"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Limitasyon ng multimedia na mensahe"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Mga Notification"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Ipakita ang mga notification ng mensahe sa bar ng katayuan"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"I-vibrate"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Mag-vibrate din kapag na-notify"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Pumili ng ringtone"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Tunog"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Itakda ang bilang ng mga mensaheng ise-save"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Palagi"</item>
-    <item msgid="6069709696037750627">"Kapag tahimik lang"</item>
-    <item msgid="7486145357487111435">"Hindi kailanman"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"I-vibrate"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Tahimik"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"I-auto-retrieve"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Awtomatikong bawiin ang mga mensahe"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Niro-roam ang auto-retrieve"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Hindi naipadala ang mensahe"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Pindutin upang suriin ang mensahe at subukang muli."</string>
     <string name="download_later" msgid="5531365714424360903">"Hindi ma-download ngayon. Subukang muli sa ibang pagkakataon."</string>
+    <string name="no_apn" msgid="505932916503312015">"Walang tinukoy na APN sa device."</string>
     <string name="select_audio" msgid="3528161449756771832">"Pumili ng audio"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"I-save ang attachment"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Na-save ang attachment."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Puno ang storage ng iyong telepono"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Hindi ka makakatanggap ng mga mensaheng SMS/MMS"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 214c409..6763e5e 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Sona erme tarihi: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Teslim edilmemiş ileti"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Bu mesaj gönderilemedi."\n"Son deneme zamanı: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Bu mesaj gönderilemedi.\nSon deneme zamanı: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Bu mesaj gönderilemedi."</string>
     <string name="delete_thread" msgid="757258847736632791">"İleti dizisini sil"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Yönlendir"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Kısa mesaj sınırı"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Multimedya iletisi sınırı"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Bildirimler"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Durum çubuğunda ileti bildirimleri görüntüle"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Titreşim"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Bildirildiğinde aynı zamanda titret"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Zil sesi seç"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Ses"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Kaydedilecek iletilerin sayısını ayarla"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Her zaman"</item>
-    <item msgid="6069709696037750627">"Yalnızca sessizken"</item>
-    <item msgid="7486145357487111435">"Hiçbir zaman"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Titreşim"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Sessiz"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Otomatik al"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"İletileri otomatik olarak al"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Dolaşımda otomatik al"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"İleti gönderilmedi"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"İncelemek için mesaja dokunun ve tekrar deneyin."</string>
     <string name="download_later" msgid="5531365714424360903">"Şu anda indirilemiyor. Daha sonra tekrar deneyin."</string>
+    <string name="no_apn" msgid="505932916503312015">"Cihazda APN belirtilmedi."</string>
     <string name="select_audio" msgid="3528161449756771832">"Ses seçin"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Eki kaydet"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Ek kaydedildi."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Telefonunuzun depolama alanı dolu"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Yeni SMS/MMS mesajlarını almayacaksınız"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index feeb234..62e643b 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Діє до: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"Кб"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Недоставлене повідомл."</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Не вдалося надіслати це повідомлення."\n"Спроба: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Не вдалося надіслати це повідомлення.\nСпроба: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Не вдалося надіслати це повідомлення."</string>
     <string name="delete_thread" msgid="757258847736632791">"Видал. ланцюжок"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Переслати"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Ліміт текст. повідомл."</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Ліміт мультимед. повідомл."</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Сповіщення"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Відображ. сповіщення повідомл. в рядку стану"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Вібросигнал"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Також вібр. при отрим. сповіщ."</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Вибрати сигнал дзвінка"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Звук"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Установ. к-сть повід. для зберіг."</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Завжди"</item>
-    <item msgid="6069709696037750627">"Лише в беззвуч. реж."</item>
-    <item msgid="7486145357487111435">"Ніколи"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Вібросигнал"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Без звуку"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Авто. отримання"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Автомат. отрим. повідомлення"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Автомат. отрим. роумінгу"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Повідомл. не надіслано"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Торкніться, щоб переглянути повідомлення, і повторіть спробу."</string>
     <string name="download_later" msgid="5531365714424360903">"Неможливо завантажити зараз. Повторіть спробу пізніше."</string>
+    <string name="no_apn" msgid="505932916503312015">"APN (точки доступу) на пристрої не визначено."</string>
     <string name="select_audio" msgid="3528161449756771832">"Вибір аудіо"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Зберегти вкладений файл"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Вкладений файл збережено."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> – <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Пам’ять телефона заповнено"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Ви не отримуватимете нові SMS або MMS-повідомлення"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 212134e..4a18884 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Hết hạn: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Tin nhắn chưa được gửi"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Không thể gửi tin nhắn này."\n"Đã cố gắng thực hiện: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Không thể gửi tin nhắn này.\nĐã cố gắng thực hiện: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Không thể gửi tin nhắn này."</string>
     <string name="delete_thread" msgid="757258847736632791">"Xóa chuỗi thư"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Chuyển tiếp"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Giới hạn tin nhắn văn bản"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Giới hạn tin nhắn đa phương tiện"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Thông báo"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Hiển thị thông báo tin nhắn trên thanh trạng thái"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Rung"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Cũng rung khi được thông báo"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Chọn nhạc chuông"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Âm thanh"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Đặt số tin nhắn sẽ lưu"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Luôn luôn"</item>
-    <item msgid="6069709696037750627">"Chỉ khi ở chế độ im lặng"</item>
-    <item msgid="7486145357487111435">"Chưa bao giờ"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Rung"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Im lặng"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Tự động truy xuất"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Tự động truy xuất tin nhắn"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Tự động truy xuất khi chuyển vùng"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Tin nhắn chưa được gửi"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Chọn để xem lại tin nhắn và thử lại."</string>
     <string name="download_later" msgid="5531365714424360903">"Không thể tải xuống ngay bây giờ. Hãy thử lại sau."</string>
+    <string name="no_apn" msgid="505932916503312015">"Không có APN nào được chỉ định trên thiết bị."</string>
     <string name="select_audio" msgid="3528161449756771832">"Chọn âm thanh"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Lưu tệp đính kèm"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Đã lưu tệp đính kèm."</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Bộ nhớ điện thoại của bạn đã đầy"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Bạn sẽ không nhận được tin nhắn SMS/MMS mới"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 64e8cea..edd096d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"过期时间:<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"信息未传送出去"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"无法发送此短信。"\n"尝试发送时间:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"无法发送此短信。\n尝试发送时间:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"无法发送此短信。"</string>
     <string name="delete_thread" msgid="757258847736632791">"删除会话"</string>
     <string name="menu_forward" msgid="9026858380050046756">"转发"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"短信限制"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"彩信限制"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"通知"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"在状态栏中显示信息通知"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"振动"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"收到通知时还振动提醒"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"选择铃声"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"提示音"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"设置要保存的信息数量"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"始终"</item>
-    <item msgid="6069709696037750627">"仅在静音时"</item>
-    <item msgid="7486145357487111435">"永不"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"振动"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"静音"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"自动检索"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"自动检索信息"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"漫游时自动检索"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"短信未发送"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"触摸可查看短信并重试。"</string>
     <string name="download_later" msgid="5531365714424360903">"目前无法下载。请稍后再试。"</string>
+    <string name="no_apn" msgid="505932916503312015">"设备上未指定任何 APN。"</string>
     <string name="select_audio" msgid="3528161449756771832">"选择音频"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"保存附件"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"已保存附件。"</string>
@@ -306,7 +300,7 @@
     <string name="slideshow_activity" msgid="5658249461317434432">"播放幻灯片"</string>
     <string name="class_0_message_activity" msgid="6631339964159861048">"0 类短信"</string>
     <string name="search_label" msgid="6042598929386174964">"短信"</string>
-    <string name="search_hint" msgid="7273727663577472044">"搜索“短信”"</string>
+    <string name="search_hint" msgid="7273727663577472044">"搜索短信"</string>
     <string name="search" msgid="7560238620274735199">"短信"</string>
     <string name="search_setting_description" msgid="4104004595086437572">"消息中的文字"</string>
     <string name="search_empty" msgid="2109551478056039278">"没有匹配项。"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"、 "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"您手机的存储空间已满"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"您将无法收到新的短信/彩信"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 501edc6..23fbce4 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"期限終止:<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"無法傳送的簡訊"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"無法傳送這則訊息。"\n"上次嘗試傳送時間:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"無法傳送這則訊息。\n上次嘗試傳送時間:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"無法傳送這則訊息。"</string>
     <string name="delete_thread" msgid="757258847736632791">"刪除會話串"</string>
     <string name="menu_forward" msgid="9026858380050046756">"轉寄"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"簡訊限制"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"多媒體訊息限制"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"通知"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"在狀態列顯示簡訊通知"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"震動"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"收到通知時震動提醒"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"選擇鈴聲"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"音效"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"設定儲存的訊息數量"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"一律"</item>
-    <item msgid="6069709696037750627">"靜音時才啟用"</item>
-    <item msgid="7486145357487111435">"永遠不要"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"震動"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"靜音"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"自動擷取"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"自動擷取簡訊"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"漫遊時自動擷取"</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"簡訊未送出"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"請輕觸以重新檢視訊息,然後再試一次。"</string>
     <string name="download_later" msgid="5531365714424360903">"目前無法下載,請稍後再試。"</string>
+    <string name="no_apn" msgid="505932916503312015">"尚未在裝置上指定 APN。"</string>
     <string name="select_audio" msgid="3528161449756771832">"選擇音訊"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"儲存附件"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"已儲存附件。"</string>
@@ -316,9 +310,9 @@
   </plurals>
     <string name="search_history" msgid="4127805495662693154">"<xliff:g id="COUNT">%1$s</xliff:g> 個符合「<xliff:g id="SEARCH">%2$s</xliff:g>」的搜尋結果"</string>
     <string name="confirm_clear_search_title" msgid="8510295993632032904">"清除"</string>
-    <string name="confirm_clear_search_text" msgid="8731877031837077478">"系統即將清除搜尋記錄。"</string>
-    <string name="pref_mms_clear_search_history_title" msgid="6159758850628148164">"清除搜尋記錄"</string>
-    <string name="pref_mms_clear_search_history_summary" msgid="7960005384066460035">"清除先前的訊息搜尋記錄,不要在搜尋框中顯示"</string>
+    <string name="confirm_clear_search_text" msgid="8731877031837077478">"系統即將清除搜尋紀錄。"</string>
+    <string name="pref_mms_clear_search_history_title" msgid="6159758850628148164">"清除搜尋紀錄"</string>
+    <string name="pref_mms_clear_search_history_summary" msgid="7960005384066460035">"清除先前的訊息搜尋紀錄,不要在搜尋框中顯示"</string>
     <string name="save" msgid="6847069284991531310">"儲存"</string>
     <string name="storage_limits_title" msgid="7074684882530693016">"訊息上限"</string>
     <string name="storage_limits_message" msgid="2010501485394745696">"要限制每個會話群組儲存的訊息數量上限嗎?"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">"、 "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"您的手機儲存空間已滿"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"您將無法收到新的簡訊和/或 MMS 訊息"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index cf3c011..605ddc5 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -45,7 +45,7 @@
     <string name="expire_on" msgid="4436268382742593921">"Okuphelelwa yisikhathi: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="kilobyte" msgid="534782148965716631">"KB"</string>
     <string name="undelivered_msg_dialog_title" msgid="7479433403599785755">"Umyalezo ongathunyelwanga"</string>
-    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Ayikwazanga ukuthumela lo myalezo. "\n"Ukzama okwenziwe: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+    <string name="undelivered_msg_dialog_body" msgid="4789268239627694551">"Ayikwazanga ukuthumela lo myalezo. \nUkzama okwenziwe: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
     <string name="undelivered_sms_dialog_body" msgid="4203703285563450868">"Ayikwazanga ukuthumela lo umyalezo."</string>
     <string name="delete_thread" msgid="757258847736632791">"Susa indikimba"</string>
     <string name="menu_forward" msgid="9026858380050046756">"Dlulisa"</string>
@@ -119,7 +119,7 @@
     <string name="preview" msgid="5438149136136446756">"Buka kuqala"</string>
     <string name="preview_slideshow" msgid="6824095909448728427">"Buka kuqala"</string>
     <string name="replace_image" msgid="3171240952601443619">"Buyisela isithombe"</string>
-    <string name="duration_sec" msgid="6617032324007824096">"Ubude (<xliff:g id="DURATION">%s</xliff:g> imizuzwana)"</string>
+    <string name="duration_sec" msgid="6617032324007824096">"Ubude (<xliff:g id="DURATION">%s</xliff:g> amasekhondi)"</string>
     <string name="duration_selector_title" msgid="5981097971537625852">"Ubude beslayidi "</string>
     <string name="layout_selector_title" msgid="7344600117972450771">"Ukwakheka kombukiso weslayidi "</string>
     <string name="layout_top" msgid="6811021650398972346">"Ukwakheka (ngenhla)"</string>
@@ -129,16 +129,16 @@
     <string name="duration_zero" msgid="3549377790195185977">"Ubude kumele bube bukhulu kunamasekhondi ayize"</string>
     <string name="secs" msgid="6886003523109967972">"amasekhondi"</string>
   <string-array name="select_dialog_items">
-    <item msgid="4840183714016984112">"isekhondi elingu-1"</item>
-    <item msgid="3734955613666864447">"amasekhondi angu-2"</item>
-    <item msgid="6032232473048379828">"amasekhondi angu-3"</item>
-    <item msgid="5978471781525927827">"amasekhondi angu-4"</item>
-    <item msgid="4888440297621208402">"amasekhondi angu-5"</item>
-    <item msgid="6047246213264166841">"amasekhondi angu-6"</item>
-    <item msgid="2327501250056533797">"amasekhondi angu-7"</item>
-    <item msgid="3400770414689795703">"amasekhondi angu-8"</item>
-    <item msgid="4358760614161107970">"amasekhondi angu-9"</item>
-    <item msgid="2618179407176906471">"amasekhondi angu-10"</item>
+    <item msgid="4840183714016984112">"1 isekhondi"</item>
+    <item msgid="3734955613666864447">"2 amasekhondi"</item>
+    <item msgid="6032232473048379828">"3 amasekhondi"</item>
+    <item msgid="5978471781525927827">"4 amasekhondi"</item>
+    <item msgid="4888440297621208402">"5 amasekhondi"</item>
+    <item msgid="6047246213264166841">"6 amasekhondi"</item>
+    <item msgid="2327501250056533797">"7 amasekhondi"</item>
+    <item msgid="3400770414689795703">"8 amasekhondi"</item>
+    <item msgid="4358760614161107970">"9 amasekhondi"</item>
+    <item msgid="2618179407176906471">"10 amasekhondi"</item>
     <item msgid="7786079681602275449">"Okunye"</item>
   </string-array>
     <string name="menu_view_contact" msgid="1414670584423909451">"Buka othintana naye"</string>
@@ -172,17 +172,10 @@
     <string name="pref_title_sms_delete" msgid="6890538316799296878">"Umkhawulo womyalezo wombhalo"</string>
     <string name="pref_title_mms_delete" msgid="7265878070847351664">"Umkhawulo womyalezo wokuxhumana okuxubile"</string>
     <string name="pref_title_notification_enabled" msgid="9118792213953729414">"Izaziso"</string>
-    <string name="pref_summary_notification_enabled" msgid="619729096820999905">"Bonisa izaziso zomlayezo kwibha yomumo"</string>
     <string name="pref_title_notification_vibrateWhen" msgid="4251208067092761057">"Dlidliza"</string>
-    <string name="pref_summary_notification_vibrateWhen" msgid="669655090822561447">"Iyadlidliza futhi uma yazisiwe"</string>
-    <string name="pref_title_notification_ringtone" msgid="5467265237106229952">"Khetha ithoni yokukhala"</string>
+    <string name="pref_title_notification_ringtone" msgid="8667533917574559659">"Umsindo"</string>
     <string name="pref_messages_to_save" msgid="3202539743892934926">"Hlela inani lemiyalezo ozoyigcina"</string>
-  <string-array name="prefEntries_vibrateWhen">
-    <item msgid="2520871831815631409">"Njao"</item>
-    <item msgid="6069709696037750627">"Kuphela uma ithulile"</item>
-    <item msgid="7486145357487111435">"Akusoze"</item>
-  </string-array>
-    <string name="prefDialogTitle_vibrateWhen" msgid="6419655771690242118">"Dlidliza"</string>
+    <string name="silent_ringtone" msgid="7981237991326592780">"Thulile"</string>
     <string name="pref_title_mms_auto_retrieval" msgid="4021878285474431355">"Thola ngokuzenzakalekayo"</string>
     <string name="pref_summary_mms_auto_retrieval" msgid="925163257363757048">"Ngokuzenzakalekayo ithola imiyalezo"</string>
     <string name="pref_title_mms_retrieval_during_roaming" msgid="6016227402731817804">"Izulazula ukuthola okuzenzakalekayo"</string>
@@ -210,7 +203,7 @@
     <string name="text_message" msgid="8196464345251877972">"Umyalezo wombhalo"</string>
     <string name="multimedia_message" msgid="1512644521083533071">"Umyalezo wezokuxhumana okuxubile"</string>
     <string name="multimedia_notification" msgid="4124031788554972308">"Isaziso somyalezo wokuxhumano okuxubile"</string>
-    <string name="from_label" msgid="2055117571548171397">"Ivela ku: "</string>
+    <string name="from_label" msgid="2055117571548171397">"Ibuya: "</string>
     <string name="to_address_label" msgid="5837363600471845801">"Kuya ku: "</string>
     <string name="bcc_label" msgid="530867161453958774">"Bcc: "</string>
     <string name="sent_label" msgid="2722190650145251584">"Thunyelwe: "</string>
@@ -281,6 +274,7 @@
     <string name="message_send_failed_title" msgid="3469894907399046372">"Umlayezo awuthunyelwanga"</string>
     <string name="message_failed_body" msgid="3421296112073915245">"Khetha ukubona kuqala umyalezo bese uzama futhi."</string>
     <string name="download_later" msgid="5531365714424360903">"Ayikwazi ukulanda manje. Zama futhi ngemva kwesikhashana."</string>
+    <string name="no_apn" msgid="505932916503312015">"Ayikho i-APN ecaciswe kudivayisi."</string>
     <string name="select_audio" msgid="3528161449756771832">"Khetha okulalelwayo"</string>
     <string name="copy_to_sdcard" msgid="757028609638184856">"Gcina isinamathiseli"</string>
     <string name="copy_to_sdcard_success" msgid="7948625615155992014">"Isinamathiseli silondoloziwe"</string>
@@ -349,4 +343,6 @@
     <string name="notification_separator" msgid="2334673105226536422">"  "</string>
     <string name="enumeration_comma" msgid="213598026698964628">", "</string>
     <string name="message_timestamp_format" msgid="4188999027493614617">"<xliff:g id="STRING_0">%1$s</xliff:g> - <xliff:g id="STRING_1">%2$s</xliff:g>"</string>
+    <string name="storage_warning_title" msgid="7124740686325942375">"Isitoreji sefoni yakho sigcwele"</string>
+    <string name="storage_warning_content" msgid="1100367816649962354">"Ngeke uthole imilayezo emisha ye-SMS/MMS"</string>
 </resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index d8357e5..0a1bd94 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -16,60 +16,6 @@
  * limitations under the License.
  -->
 <resources>
-    <!-- NOTE: if you change anything about this array, you must make the corresponding change
-         to the array DEFAULT_SMILEY_RES_IDS in MessageListItem.java and to default_smiley_names
-         below. -->
-    <string-array name="default_smiley_texts" translatable="false">
-      <item>:-)</item>   <!-- 0: Happy -->
-      <item>:-(</item>   <!-- 1: Sad -->
-      <item>;-)</item>   <!-- 2: Winking -->
-      <item>:-P</item>   <!-- 3: Tongue sticking out -->
-      <item>=-O</item>   <!-- 4: Surprised -->
-      <item>:-*</item>   <!-- 5: Kissing -->
-      <item>:O</item>    <!-- 6: Yelling -->
-      <item>B-)</item>   <!-- 7: Cool -->
-      <item>:-$</item>   <!-- 8: Money mouth -->
-      <item>:-!</item>   <!-- 9: Foot in mouth -->
-      <item>:-[</item>   <!-- 10: Embarrassed -->
-      <item>O:-)</item>  <!-- 11: Angel -->
-      <item>:-\\</item>  <!-- 12: Undecided -->
-      <item>:\'(</item>  <!-- 13: Crying -->
-      <item>:-X</item>   <!-- 14: Lips are sealed -->
-      <item>:-D</item>   <!-- 15: Laughing -->
-      <item>o_O</item>   <!-- 16: Confused -->
-      <item>&lt;3</item> <!-- 17: Heart -->
-      <item>x-(</item>   <!-- 18: Mad -->
-      <item>:-/</item>   <!-- 19: Smirk -->
-      <item>:-I</item>   <!-- 20: Poker face -->
-    </string-array>
-
-    <!-- NOTE: if you change anything about this array, you must make the corresponding change
-         to the array DEFAULT_SMILEY_RES_IDS in SmileyParser.java and to default_smiley_texts
-         above. -->
-    <string-array name="default_smiley_names">
-        <item>Happy</item>                   <!-- 0: :-) -->
-        <item>Sad</item>                     <!-- 1: :-( -->
-        <item>Winking</item>                 <!-- 2: ;-) -->
-        <item>Tongue sticking out</item>     <!-- 3: :-P -->
-        <item>Surprised</item>               <!-- 4: =-O -->
-        <item>Kissing</item>                 <!-- 5: :-* -->
-        <item>Yelling</item>                 <!-- 6: :O -->
-        <item>Cool</item>                    <!-- 7: B-) -->
-        <item>Money mouth</item>             <!-- 8: :-$ -->
-        <item>Foot in mouth</item>           <!-- 9: :-! -->
-        <item>Embarrassed</item>             <!-- 10: :-[ -->
-        <item>Angel</item>                   <!-- 11: O:-) -->
-        <item>Undecided</item>               <!-- 12: :-\\ -->
-        <item>Crying</item>                  <!-- 13: :\'( -->
-        <item>Lips are sealed</item>         <!-- 14: :-X -->
-        <item>Laughing</item>                <!-- 15: :-D -->
-        <item>Confused</item>                <!-- 16: o_O -->
-        <item>Heart</item>                   <!-- 17: <3 -->
-        <item>Mad</item>                     <!-- 18: x-( -->
-        <item>Smirk</item>                   <!-- 19: :-/ -->
-        <item>Poker face</item>              <!-- 20: :-I -->
-    </string-array>
-
     <!-- String to match as no subject and filter out as a subject. For example, if the
          subject string is "no subject", we won't display that. We'll pretend
          no subject string was delivered. -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 4c6b0bc..943b49f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,4 +39,13 @@
     <color name="widget_subject_text_color_read">#777777</color>
     <color name="widget_subject_text_color_unread">#555555</color>
 
+    <color name="translucent_white">#ccffffff</color>
+
+    <color name="banner_item_transparent">#00000000</color>
+    <color name="banner_item_activated">#670099cc</color>
+    <color name="banner_item_selected">#cc0099cc</color>
+    <color name="banner_item_pressed">#6733b5e5</color>
+
+    <color name="text_shadow_color_light">#33000000</color>
+    <color name="solid_white">#ffffffff</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6e6fd10..b42a857 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,4 +17,8 @@
     <dimen name="widget_margin_left">0dip</dimen>
     <dimen name="widget_margin_right">0dip</dimen>
     <dimen name="widget_margin_bottom">0dip</dimen>
+
+    <!-- Bammer item spacing -->
+    <dimen name="banner_s_space">4dip</dimen>
+    <dimen name="banner_m_space">8dip</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cfd4920..2c16f3e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -114,7 +114,7 @@
     <!-- Menu item in slideshow edit screen for moving the selected slide down in the order -->
     <string name="move_down">Move down</string>
     <!-- Menu item in slideshow edit screen for removing the selected slide from the slideshow -->
-    <string name="remove_slide">Remove slide</string>
+    <string name="remove_slide">Remove slide</string> 
     <!-- Menu item in slideshow edit screen for adding a new slide to the slideshow -->
     <string name="add_slide">Add slide</string>
     <!-- Text displayed below "Add slide" item in slideshow edit screen -->
@@ -173,6 +173,10 @@
     <string name="type_to_compose_text_enter_to_send">Type message</string>
     <!-- Hint text in message body field when keyboard is hidden -->
     <string name="open_keyboard_to_compose_message">Open keyboard to type message</string>
+    <!-- Hint text when we are not the default SMS app -->
+    <string name="sending_disabled_not_default_app">Sending disabled</string>
+    <!-- Toast text when user tries to compose while we are not the default SMS app -->
+    <string name="compose_disabled_toast">Can\'t compose. Not default SMS app.</string>
 
     <!-- Toast message while an image is being resized -->
     <string name="compressing">Picture too large. Compressing\u2026</string>
@@ -317,6 +321,15 @@
     <!-- Settings screen, section heading for storage-specific settings -->
     <string name="pref_sms_storage_title">Storage</string>
 
+    <!-- Title for SMS Disabled -->
+    <string name="pref_title_sms_disabled">SMS Disabled</string>
+    <!-- Summary for SMS Disabled -->
+    <string name="pref_summary_sms_disabled">Touch to make Messaging your default SMS app</string>
+    <!-- Title for SMS Enabled -->
+    <string name="pref_title_sms_enabled">SMS Enabled</string>
+    <!-- Summary for SMS Enabled -->
+    <string name="pref_summary_sms_enabled">Touch to change your default SMS app</string>
+
     <!-- Settings item description for entering SIM card message screen -->
     <string name="pref_summary_manage_sim_messages">Manage messages stored on your SIM card</string>
     <!-- Settings item description for boolean MMS delivery report setting -->
@@ -349,44 +362,20 @@
     <string name="pref_title_mms_delete">Multimedia message limit</string>
     <!-- Settings item for boolean option to display message notifications -->
     <string name="pref_title_notification_enabled">Notifications</string>
-    <!-- Settings item description for boolean message notification option -->
-    <string name="pref_summary_notification_enabled">Display message notifications in status bar</string>
     <!-- Settings item for boolean option to vibrate during message notification -->
     <string name="pref_title_notification_vibrateWhen">Vibrate</string>
-    <!-- Settings item description for boolean vibrate option -->
-    <string name="pref_summary_notification_vibrateWhen">Also vibrate when notified</string>
     <!-- Settings item for entering ringtone selection screen -->
-    <string name="pref_title_notification_ringtone">Choose ringtone</string>
+    <string name="pref_title_notification_ringtone">Sound</string>
     <!-- Settings item for setting the number of message to save for each conversation -->
     <string name="pref_messages_to_save">Set number of messages to save</string>
+    <!-- Settings screen, what to display for Ringtone when the user chooses "silent" [CHAR LIMIT=100]-->
+    <string name="silent_ringtone">Silent</string>
 
-    <!-- The vibrate notification modes -->
-    <string-array name="prefEntries_vibrateWhen">
-        <!-- Always -->
-        <item>Always</item>
-        <!-- Only when the phone is in Silent mode -->
-        <item>Only when silent</item>
-        <!-- Never -->
-        <item>Never</item>
-    </string-array>
-
-    <!-- The default vibrateWhen value, when none is set -->
-    <string translatable="false" name="prefDefault_vibrateWhen">never</string>
     <!-- The value to use when migrating from old versions for a true vibrate setting -->
     <string translatable="false" name="prefDefault_vibrate_true">always</string>
     <!-- The value to use when migrating from old versions for a false vibrate setting -->
     <string translatable="false" name="prefDefault_vibrate_false">never</string>
 
-    <!-- The vibrateWhen values -->
-    <string-array translatable="false" name="prefValues_vibrateWhen">
-        <item>always</item>
-        <item>silent</item>
-        <item>never</item>
-    </string-array>
-
-    <!-- Dialog title for the Vibrate dialog -->
-    <string name="prefDialogTitle_vibrateWhen">Vibrate</string>
-
     <!-- Settings item for boolean option to auto-retrieve incoming MMS messages -->
     <string name="pref_title_mms_auto_retrieval">Auto-retrieve</string>
     <!-- Settings item description for boolean MMS auto-retrieve option -->
@@ -619,6 +608,8 @@
     <string name="message_failed_body">Touch to review the message and try again.</string>
     <!-- Dialog box message when there's a problem retrieving messages. -->
     <string name="download_later">Can\'t download right now. Try again later.</string>
+    <!-- Dialog box message when there's no APN specified on the device. -->
+    <string name="no_apn">No APN specified on the device.</string>
 
     <!-- Title for audio file picker -->
     <string name="select_audio">Choose audio</string>
@@ -635,7 +626,6 @@
     <!-- Toast message on failed copying ringtone to drm provider -->
     <string name="saved_ringtone_fail">Couldn\'t save ringtone.</string>
     <!-- Menu item -->
-    <string name="menu_insert_smiley">Insert smiley</string>
     <!-- Menu item  [CHAR LIMIT=40] -->
     <string name="menu_group_participants">Group participants</string>
 
@@ -800,5 +790,15 @@
     <!-- In message list items, this string builds the timestamp line when the message is in a group [CHAR LIMIT=NONE] -->
     <string name="message_timestamp_format"><xliff:g id="string" example="Fred Flinstone">%1$s</xliff:g>\u0020-\u0020<xliff:g id="string" example="9:39AM">%2$s</xliff:g></string>
 
-</resources>
+    <string name="storage_warning_title">"Your phone's storage is full"</string>
+    <string name="storage_warning_content">"You won't receive new SMS/MMS messages"</string>
+    <!--  Strings used forh MMS push of messages in outbox -->
+    <string name="label_mms_send_outbox_msg">MMS Wakeup</string>
+    <string name="desc_mms_send_outbox_msg">Sends out all MMSs from the outbox to the network</string>
 
+    <!-- Banner in conversation list for making Messaging the default SMS app -->
+    <string name="banner_sms_promo_title_initial">Messaging is not your SMS app</string>
+    <string name="banner_sms_promo_title_application"><xliff:g id="appName">%s</xliff:g> is your SMS app</string>
+    <string name="banner_sms_promo_message">You can change this in Settings</string>
+
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 128631c..2a6501a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -44,4 +44,25 @@
         <item name="android:textColor">#999999</item>
     </style>
 
+    <style name="TextShadowLight">
+        <item name="android:textColor">@color/solid_white</item>
+        <item name="android:shadowColor">@color/text_shadow_color_light</item>
+        <item name="android:shadowDx">1</item>
+        <item name="android:shadowDy">2</item>
+        <item name="android:shadowRadius">1</item>
+    </style>
+
+    <style name="BannerTitleText" parent="TextShadowLight">
+        <item name="android:textSize">18sp</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="BannerContentText" parent="TextShadowLight">
+        <item name="android:textSize">14sp</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
 </resources>
diff --git a/res/xml/mms_config.xml b/res/xml/mms_config.xml
index 78567e2..68bb500 100644
--- a/res/xml/mms_config.xml
+++ b/res/xml/mms_config.xml
@@ -36,11 +36,11 @@
 
     <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
          This is the default value. -->
-    <int name="defaultSMSMessagesPerThread">500</int>
+    <int name="defaultSMSMessagesPerThread">10000</int>
 
     <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
          This is the default value. -->
-    <int name="defaultMMSMessagesPerThread">50</int>
+    <int name="defaultMMSMessagesPerThread">1000</int>
 
     <!-- Minimum value for the number of messages kept per conversation. The user can never
          set the limit below this value. -->
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index fd80c5a..8eec17f 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -17,8 +17,30 @@
  * limitations under the License.
  */
 -->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-  <PreferenceCategory android:title="@string/pref_sms_storage_title"
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="pref_key_root">
+    <Preference
+        android:key="pref_key_sms_disabled"
+        android:title="@string/pref_title_sms_disabled"
+        android:summary="@string/pref_summary_sms_disabled"
+        android:persistent="false">
+        <intent
+            android:action="android.provider.Telephony.ACTION_CHANGE_DEFAULT"
+            android:targetPackage="com.android.settings">
+            <extra android:name="package" android:value="com.android.mms"/>
+        </intent>
+    </Preference>
+    <Preference
+        android:key="pref_key_sms_enabled"
+        android:title="@string/pref_title_sms_enabled"
+        android:summary="@string/pref_summary_sms_enabled"
+        android:persistent="false">
+        <intent
+            android:action="android.settings.WIRELESS_SETTINGS"
+            android:targetPackage="com.android.settings">
+        </intent>
+    </Preference>
+    <PreferenceCategory android:title="@string/pref_sms_storage_title"
                       android:key="pref_key_storage_settings">
 <!--
     <Preference android:key="pref_key_mms_clear_history"
@@ -75,25 +97,19 @@
                         android:title="@string/pref_title_mms_retrieval_during_roaming"
                         android:summary="@string/pref_summary_mms_retrieval_during_roaming" />
   </PreferenceCategory>
-  <PreferenceCategory android:title="@string/pref_notification_settings_title">
+  <PreferenceCategory android:title="@string/pref_notification_settings_title"
+                      android:key="pref_key_notification_settings">
     <CheckBoxPreference android:key="pref_key_enable_notifications"
                         android:title="@string/pref_title_notification_enabled"
-                        android:summary="@string/pref_summary_notification_enabled"
                         android:defaultValue="true" />
-    <RingtonePreference android:layout="?android:attr/preferenceLayoutChild"
-                        android:dependency="pref_key_enable_notifications"
-                        android:key="pref_key_ringtone"
+    <RingtonePreference android:key="pref_key_ringtone"
                         android:title="@string/pref_title_notification_ringtone"
                         android:ringtoneType="notification"
-                        android:defaultValue="content://settings/system/notification_sound" />
-    <ListPreference     android:layout="?android:attr/preferenceLayoutChild"
                         android:dependency="pref_key_enable_notifications"
-                        android:key="pref_key_vibrateWhen"
-                        android:defaultValue="@string/prefDefault_vibrateWhen"
+                        android:defaultValue="content://settings/system/notification_sound" />
+    <CheckBoxPreference android:key="pref_key_vibrate"
                         android:title="@string/pref_title_notification_vibrateWhen"
-                        android:summary="@string/pref_summary_notification_vibrateWhen"
-                        android:entries="@array/prefEntries_vibrateWhen"
-                        android:entryValues="@array/prefValues_vibrateWhen"
-                        android:dialogTitle="@string/prefDialogTitle_vibrateWhen" />
+                        android:dependency="pref_key_enable_notifications"
+                        android:defaultValue="false" />
   </PreferenceCategory>
 </PreferenceScreen>
diff --git a/src/com/android/mms/MmsApp.java b/src/com/android/mms/MmsApp.java
index 241ae6b..5ef166e 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,11 +35,13 @@
 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;
 import com.android.mms.util.RateController;
-import com.android.mms.util.SmileyParser;
 import com.android.mms.util.ThumbnailManager;
 
 public class MmsApp extends Application {
@@ -80,7 +83,6 @@
             }
         };
         mCountryDetector.addCountryListener(mCountryListener, getMainLooper());
-        mCountryIso = mCountryDetector.detectCountry().getCountryIso();
 
         Context context = getApplicationContext();
         mPduLoaderManager = new PduLoaderManager(context);
@@ -93,8 +95,24 @@
         DownloadManager.init(this);
         RateController.init(this);
         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() {
@@ -152,7 +170,14 @@
         return mRecentSuggestions;
     }
 
+    // This function CAN return null.
     public String getCurrentCountryIso() {
+        if (mCountryIso == null) {
+            Country country = mCountryDetector.detectCountry();
+            if (country != null) {
+                mCountryIso = country.getCountryIso();
+            }
+        }
         return mCountryIso;
     }
 
diff --git a/src/com/android/mms/MmsConfig.java b/src/com/android/mms/MmsConfig.java
index 2c34af8..1ebef3b 100755
--- a/src/com/android/mms/MmsConfig.java
+++ b/src/com/android/mms/MmsConfig.java
@@ -22,10 +22,17 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.XmlResourceParser;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyProperties;
+import com.android.mms.ui.MessageUtils;
+import com.android.mms.ui.MessagingPreferenceActivity;
 
 public class MmsConfig {
     private static final String TAG = "MmsConfig";
@@ -35,6 +42,10 @@
     private static final String DEFAULT_HTTP_KEY_X_WAP_PROFILE = "x-wap-profile";
     private static final String DEFAULT_USER_AGENT = "Android-Mms/2.0";
 
+    private static final String MMS_APP_PACKAGE = "com.android.mms";
+
+    private static final String SMS_PROMO_DISMISSED_KEY = "sms_promo_dismissed_key";
+
     private static final int MAX_IMAGE_HEIGHT = 480;
     private static final int MAX_IMAGE_WIDTH = 640;
     private static final int MAX_TEXT_LENGTH = 2000;
@@ -54,8 +65,8 @@
     private static int mMaxImageHeight = MAX_IMAGE_HEIGHT;      // default value
     private static int mMaxImageWidth = MAX_IMAGE_WIDTH;        // default value
     private static int mRecipientLimit = Integer.MAX_VALUE;     // default value
-    private static int mDefaultSMSMessagesPerThread = 500;      // default value
-    private static int mDefaultMMSMessagesPerThread = 50;       // default value
+    private static int mDefaultSMSMessagesPerThread = 10000;    // default value
+    private static int mDefaultMMSMessagesPerThread = 1000;     // default value
     private static int mMinMessageCountPerThread = 2;           // default value
     private static int mMaxMessageCountPerThread = 5000;        // default value
     private static int mHttpSocketTimeout = 60*1000;            // default to 1 min
@@ -112,6 +123,33 @@
         loadMmsSettings(context);
     }
 
+    public static boolean isSmsEnabled(Context context) {
+        String defaultSmsApplication = Telephony.Sms.getDefaultSmsPackage(context);
+
+        if (defaultSmsApplication != null && defaultSmsApplication.equals(MMS_APP_PACKAGE)) {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean isSmsPromoDismissed(Context context) {
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+        return preferences.getBoolean(SMS_PROMO_DISMISSED_KEY, false);
+    }
+
+    public static void setSmsPromoDismissed(Context context) {
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putBoolean(SMS_PROMO_DISMISSED_KEY, true);
+        editor.apply();
+    }
+
+    public static Intent getRequestDefaultSmsAppActivity() {
+        final Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
+        intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, MMS_APP_PACKAGE);
+        return intent;
+    }
+
     public static int getSmsToMmsTextThreshold() {
         return mSmsToMmsTextThreshold;
     }
@@ -255,12 +293,12 @@
     public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
     {
         int type;
-        while ((type=parser.next()) != parser.START_TAG
-                   && type != parser.END_DOCUMENT) {
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                   && type != XmlPullParser.END_DOCUMENT) {
             ;
         }
 
-        if (type != parser.START_TAG) {
+        if (type != XmlPullParser.START_TAG) {
             throw new XmlPullParserException("No start tag found");
         }
 
@@ -273,8 +311,8 @@
     public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
     {
         int type;
-        while ((type=parser.next()) != parser.START_TAG
-                   && type != parser.END_DOCUMENT) {
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                   && type != XmlPullParser.END_DOCUMENT) {
             ;
         }
     }
diff --git a/src/com/android/mms/SuggestionsProvider.java b/src/com/android/mms/SuggestionsProvider.java
index 358ac06..41a3bc9 100644
--- a/src/com/android/mms/SuggestionsProvider.java
+++ b/src/com/android/mms/SuggestionsProvider.java
@@ -416,6 +416,10 @@
             mDatabaseCursor.setNotificationUri(cr, uri);
         }
 
+        public Uri getNotificationUri() {
+            return mDatabaseCursor.getNotificationUri();
+        }
+
         public void unregisterContentObserver(ContentObserver observer) {
             mDatabaseCursor.unregisterContentObserver(observer);
         }
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/data/Contact.java b/src/com/android/mms/data/Contact.java
index c29b9bd..5bc5bba 100644
--- a/src/com/android/mms/data/Contact.java
+++ b/src/com/android/mms/data/Contact.java
@@ -780,10 +780,20 @@
         private Contact getContactInfo(Contact c) {
             if (c.mIsMe) {
                 return getContactInfoForSelf();
-            } else if (Mms.isEmailAddress(c.mNumber) || isAlphaNumber(c.mNumber)) {
+            } else if (Mms.isEmailAddress(c.mNumber)) {
                 return getContactInfoForEmailAddress(c.mNumber);
-            } else {
+            } else if (isAlphaNumber(c.mNumber)) {
+                // first try to look it up in the email field
+                Contact contact = getContactInfoForEmailAddress(c.mNumber);
+                if (contact.existsInDatabase()) {
+                    return contact;
+                }
+                // then look it up in the phone field
                 return getContactInfoForPhoneNumber(c.mNumber);
+            } else {
+                // it's a real phone number, so strip out non-digits and look it up
+                final String strippedNumber = PhoneNumberUtils.stripSeparators(c.mNumber);
+                return getContactInfoForPhoneNumber(strippedNumber);
             }
         }
 
@@ -827,7 +837,6 @@
          * @return a Contact containing the caller id info corresponding to the number.
          */
         private Contact getContactInfoForPhoneNumber(String number) {
-            number = PhoneNumberUtils.stripSeparators(number);
             Contact entry = new Contact(number);
             entry.mContactMethodType = CONTACT_METHOD_TYPE_PHONE;
 
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/data/WorkingMessage.java b/src/com/android/mms/data/WorkingMessage.java
index 250a63a..e031b28 100755
--- a/src/com/android/mms/data/WorkingMessage.java
+++ b/src/com/android/mms/data/WorkingMessage.java
@@ -246,7 +246,7 @@
         return null;
     }
 
-    private void correctAttachmentState() {
+    private void correctAttachmentState(boolean showToast) {
         int slideCount = mSlideshow.size();
 
         // If we get an empty slideshow, tear down all MMS
@@ -266,7 +266,7 @@
             }
         }
 
-        updateState(HAS_ATTACHMENT, hasAttachment(), false);
+        updateState(HAS_ATTACHMENT, hasAttachment(), showToast);
     }
 
     private boolean loadFromUri(Uri uri) {
@@ -282,7 +282,7 @@
 
         // Make sure all our state is as expected.
         syncTextFromSlideshow();
-        correctAttachmentState();
+        correctAttachmentState(false);
 
         return true;
     }
@@ -458,7 +458,7 @@
         if (result == OK) {
             mAttachmentType = type;
         }
-        correctAttachmentState();   // this can remove the slideshow if there are no attachments
+        correctAttachmentState(true);   // this can remove the slideshow if there are no attachments
 
         if (mSlideshow != null && type == IMAGE) {
             // Prime the image's cache; helps A LOT when the image is coming from the network
@@ -494,9 +494,6 @@
                 int threshold = MmsConfig.getSmsToMmsTextThreshold();
                 setLengthRequiresMms(threshold > 0 && smsSegmentCount > threshold, false);
             }
-        } else {
-            // Set HAS_ATTACHMENT if we need it.
-            updateState(HAS_ATTACHMENT, hasAttachment(), true);
         }
         return result;
     }
@@ -1311,15 +1308,16 @@
                     recipientsInUI + "\" differ from recipients from conv: \"" +
                     semiSepRecipients + "\"";
 
+            // Just interrupt the process of sending message if recipient mismatch
             LogTag.warnPossibleRecipientMismatch(msg, mActivity);
+        }else {
+            // just do a regular send. We're already on a non-ui thread so no need to fire
+            // off another thread to do this work.
+            sendSmsWorker(msgText, semiSepRecipients, threadId);
+
+            // Be paranoid and clean any draft SMS up.
+            deleteDraftSmsMessage(threadId);
         }
-
-        // just do a regular send. We're already on a non-ui thread so no need to fire
-        // off another thread to do this work.
-        sendSmsWorker(msgText, semiSepRecipients, threadId);
-
-        // Be paranoid and clean any draft SMS up.
-        deleteDraftSmsMessage(threadId);
     }
 
     private void sendSmsWorker(String msgText, String semiSepRecipients, long threadId) {
@@ -1519,6 +1517,9 @@
         cursor = SqliteWrapper.query(context, cr,
                 Mms.Draft.CONTENT_URI, MMS_DRAFT_PROJECTION,
                 selection, null, null);
+        if (cursor == null) {
+            return null;
+        }
 
         Uri uri;
         try {
@@ -1580,6 +1581,9 @@
             return res;
         } catch (MmsException e) {
             return null;
+        } catch (IllegalStateException e) {
+            Log.e(TAG,"failed to create draft mms "+ e);
+            return null;
         }
     }
 
diff --git a/src/com/android/mms/exif/ByteBufferInputStream.java b/src/com/android/mms/exif/ByteBufferInputStream.java
new file mode 100644
index 0000000..8e764af
--- /dev/null
+++ b/src/com/android/mms/exif/ByteBufferInputStream.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+class ByteBufferInputStream extends InputStream {
+
+    private final ByteBuffer mBuf;
+
+    public ByteBufferInputStream(ByteBuffer buf) {
+        mBuf = buf;
+    }
+
+    @Override
+    public int read() {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+        return mBuf.get() & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] bytes, int off, int len) {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+
+        len = Math.min(len, mBuf.remaining());
+        mBuf.get(bytes, off, len);
+        return len;
+    }
+}
diff --git a/src/com/android/mms/exif/CountedDataInputStream.java b/src/com/android/mms/exif/CountedDataInputStream.java
new file mode 100644
index 0000000..cbff12e
--- /dev/null
+++ b/src/com/android/mms/exif/CountedDataInputStream.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class CountedDataInputStream extends FilterInputStream {
+
+    private int mCount = 0;
+
+    // allocate a byte buffer for a long value;
+    private final byte mByteArray[] = new byte[8];
+    private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+    protected CountedDataInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int getReadByteCount() {
+        return mCount;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        int r = in.read(b);
+        mCount += (r >= 0) ? r : 0;
+        return r;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        int r = in.read(b, off, len);
+        mCount += (r >= 0) ? r : 0;
+        return r;
+    }
+
+    @Override
+    public int read() throws IOException {
+        int r = in.read();
+        mCount += (r >= 0) ? 1 : 0;
+        return r;
+    }
+
+    @Override
+    public long skip(long length) throws IOException {
+        long skip = in.skip(length);
+        mCount += skip;
+        return skip;
+    }
+
+    public void skipOrThrow(long length) throws IOException {
+        if (skip(length) != length) throw new EOFException();
+    }
+
+    public void skipTo(long target) throws IOException {
+        long cur = mCount;
+        long diff = target - cur;
+        assert(diff >= 0);
+        skipOrThrow(diff);
+    }
+
+    public void readOrThrow(byte[] b, int off, int len) throws IOException {
+        int r = read(b, off, len);
+        if (r != len) throw new EOFException();
+    }
+
+    public void readOrThrow(byte[] b) throws IOException {
+        readOrThrow(b, 0, b.length);
+    }
+
+    public void setByteOrder(ByteOrder order) {
+        mByteBuffer.order(order);
+    }
+
+    public ByteOrder getByteOrder() {
+        return mByteBuffer.order();
+    }
+
+    public short readShort() throws IOException {
+        readOrThrow(mByteArray, 0 ,2);
+        mByteBuffer.rewind();
+        return mByteBuffer.getShort();
+    }
+
+    public int readUnsignedShort() throws IOException {
+        return readShort() & 0xffff;
+    }
+
+    public int readInt() throws IOException {
+        readOrThrow(mByteArray, 0 , 4);
+        mByteBuffer.rewind();
+        return mByteBuffer.getInt();
+    }
+
+    public long readUnsignedInt() throws IOException {
+        return readInt() & 0xffffffffL;
+    }
+
+    public long readLong() throws IOException {
+        readOrThrow(mByteArray, 0 , 8);
+        mByteBuffer.rewind();
+        return mByteBuffer.getLong();
+    }
+
+    public String readString(int n) throws IOException {
+        byte buf[] = new byte[n];
+        readOrThrow(buf);
+        return new String(buf, "UTF8");
+    }
+
+    public String readString(int n, Charset charset) throws IOException {
+        byte buf[] = new byte[n];
+        readOrThrow(buf);
+        return new String(buf, charset);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/mms/exif/ExifData.java b/src/com/android/mms/exif/ExifData.java
new file mode 100644
index 0000000..bfac7b2
--- /dev/null
+++ b/src/com/android/mms/exif/ExifData.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class stores the EXIF header in IFDs according to the JPEG
+ * specification. It is the result produced by {@link ExifReader}.
+ *
+ * @see ExifReader
+ * @see IfdData
+ */
+class ExifData {
+    private static final String TAG = "ExifData";
+    private static final byte[] USER_COMMENT_ASCII = {
+            0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_JIS = {
+            0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_UNICODE = {
+            0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+    };
+
+    private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
+    private byte[] mThumbnail;
+    private final ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
+    private final ByteOrder mByteOrder;
+
+    ExifData(ByteOrder order) {
+        mByteOrder = order;
+    }
+
+    /**
+     * Gets the compressed thumbnail. Returns null if there is no compressed
+     * thumbnail.
+     *
+     * @see #hasCompressedThumbnail()
+     */
+    protected byte[] getCompressedThumbnail() {
+        return mThumbnail;
+    }
+
+    /**
+     * Sets the compressed thumbnail.
+     */
+    protected void setCompressedThumbnail(byte[] thumbnail) {
+        mThumbnail = thumbnail;
+    }
+
+    /**
+     * Returns true it this header contains a compressed thumbnail.
+     */
+    protected boolean hasCompressedThumbnail() {
+        return mThumbnail != null;
+    }
+
+    /**
+     * Adds an uncompressed strip.
+     */
+    protected void setStripBytes(int index, byte[] strip) {
+        if (index < mStripBytes.size()) {
+            mStripBytes.set(index, strip);
+        } else {
+            for (int i = mStripBytes.size(); i < index; i++) {
+                mStripBytes.add(null);
+            }
+            mStripBytes.add(strip);
+        }
+    }
+
+    /**
+     * Gets the strip count.
+     */
+    protected int getStripCount() {
+        return mStripBytes.size();
+    }
+
+    /**
+     * Gets the strip at the specified index.
+     *
+     * @exceptions #IndexOutOfBoundException
+     */
+    protected byte[] getStrip(int index) {
+        return mStripBytes.get(index);
+    }
+
+    /**
+     * Returns true if this header contains uncompressed strip.
+     */
+    protected boolean hasUncompressedStrip() {
+        return mStripBytes.size() != 0;
+    }
+
+    /**
+     * Gets the byte order.
+     */
+    protected ByteOrder getByteOrder() {
+        return mByteOrder;
+    }
+
+    /**
+     * Returns the {@link IfdData} object corresponding to a given IFD if it
+     * exists or null.
+     */
+    protected IfdData getIfdData(int ifdId) {
+        if (ExifTag.isValidIfd(ifdId)) {
+            return mIfdDatas[ifdId];
+        }
+        return null;
+    }
+
+    /**
+     * Adds IFD data. If IFD data of the same type already exists, it will be
+     * replaced by the new data.
+     */
+    protected void addIfdData(IfdData data) {
+        mIfdDatas[data.getId()] = data;
+    }
+
+    /**
+     * Returns the {@link IfdData} object corresponding to a given IFD or
+     * generates one if none exist.
+     */
+    protected IfdData getOrCreateIfdData(int ifdId) {
+        IfdData ifdData = mIfdDatas[ifdId];
+        if (ifdData == null) {
+            ifdData = new IfdData(ifdId);
+            mIfdDatas[ifdId] = ifdData;
+        }
+        return ifdData;
+    }
+
+    /**
+     * Returns the tag with a given TID in the given IFD if the tag exists.
+     * Otherwise returns null.
+     */
+    protected ExifTag getTag(short tag, int ifd) {
+        IfdData ifdData = mIfdDatas[ifd];
+        return (ifdData == null) ? null : ifdData.getTag(tag);
+    }
+
+    /**
+     * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
+     */
+    protected ExifTag addTag(ExifTag tag) {
+        if (tag != null) {
+            int ifd = tag.getIfd();
+            return addTag(tag, ifd);
+        }
+        return null;
+    }
+
+    /**
+     * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
+     */
+    protected ExifTag addTag(ExifTag tag, int ifdId) {
+        if (tag != null && ExifTag.isValidIfd(ifdId)) {
+            IfdData ifdData = getOrCreateIfdData(ifdId);
+            return ifdData.setTag(tag);
+        }
+        return null;
+    }
+
+    protected void clearThumbnailAndStrips() {
+        mThumbnail = null;
+        mStripBytes.clear();
+    }
+
+    /**
+     * Removes the thumbnail and its related tags. IFD1 will be removed.
+     */
+    protected void removeThumbnailData() {
+        clearThumbnailAndStrips();
+        mIfdDatas[IfdId.TYPE_IFD_1] = null;
+    }
+
+    /**
+     * Removes the tag with a given TID and IFD.
+     */
+    protected void removeTag(short tagId, int ifdId) {
+        IfdData ifdData = mIfdDatas[ifdId];
+        if (ifdData == null) {
+            return;
+        }
+        ifdData.removeTag(tagId);
+    }
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    protected String getUserComment() {
+        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
+        if (ifdData == null) {
+            return null;
+        }
+        ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+        if (tag == null) {
+            return null;
+        }
+        if (tag.getComponentCount() < 8) {
+            return null;
+        }
+
+        byte[] buf = new byte[tag.getComponentCount()];
+        tag.getBytes(buf);
+
+        byte[] code = new byte[8];
+        System.arraycopy(buf, 0, code, 0, 8);
+
+        try {
+            if (Arrays.equals(code, USER_COMMENT_ASCII)) {
+                return new String(buf, 8, buf.length - 8, "US-ASCII");
+            } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
+                return new String(buf, 8, buf.length - 8, "EUC-JP");
+            } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
+                return new String(buf, 8, buf.length - 8, "UTF-16");
+            } else {
+                return null;
+            }
+        } catch (UnsupportedEncodingException e) {
+            Log.w(TAG, "Failed to decode the user comment");
+            return null;
+        }
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTags() {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag[] tags = d.getAllTags();
+                if (tags != null) {
+                    for (ExifTag t : tags) {
+                        ret.add(t);
+                    }
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForIfd(int ifd) {
+        IfdData d = mIfdDatas[ifd];
+        if (d == null) {
+            return null;
+        }
+        ExifTag[] tags = d.getAllTags();
+        if (tags == null) {
+            return null;
+        }
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+        for (ExifTag t : tags) {
+            ret.add(t);
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s with a given TID or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForTagId(short tag) {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag t = d.getTag(tag);
+                if (t != null) {
+                    ret.add(t);
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof ExifData) {
+            ExifData data = (ExifData) obj;
+            if (data.mByteOrder != mByteOrder ||
+                    data.mStripBytes.size() != mStripBytes.size() ||
+                    !Arrays.equals(data.mThumbnail, mThumbnail)) {
+                return false;
+            }
+            for (int i = 0; i < mStripBytes.size(); i++) {
+                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+                    return false;
+                }
+            }
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                IfdData ifd1 = data.getIfdData(i);
+                IfdData ifd2 = getIfdData(i);
+                if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/android/mms/exif/ExifInterface.java b/src/com/android/mms/exif/ExifInterface.java
new file mode 100644
index 0000000..a48a30d
--- /dev/null
+++ b/src/com/android/mms/exif/ExifInterface.java
@@ -0,0 +1,2407 @@
+/*
+ * Copyright (C) 2013 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.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+    public static final int TAG_NULL = -1;
+    public static final int IFD_NULL = -1;
+    public static final int DEFINITION_NULL = 0;
+
+    /**
+     * Tag constants for Jeita EXIF 2.2
+     */
+
+    // IFD 0
+    public static final int TAG_IMAGE_WIDTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+    public static final int TAG_IMAGE_LENGTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+    public static final int TAG_BITS_PER_SAMPLE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+    public static final int TAG_COMPRESSION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+    public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+    public static final int TAG_IMAGE_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+    public static final int TAG_MAKE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+    public static final int TAG_MODEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+    public static final int TAG_STRIP_OFFSETS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+    public static final int TAG_ORIENTATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+    public static final int TAG_SAMPLES_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+    public static final int TAG_ROWS_PER_STRIP =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+    public static final int TAG_STRIP_BYTE_COUNTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+    public static final int TAG_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+    public static final int TAG_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+    public static final int TAG_PLANAR_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+    public static final int TAG_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+    public static final int TAG_TRANSFER_FUNCTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+    public static final int TAG_SOFTWARE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+    public static final int TAG_DATE_TIME =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+    public static final int TAG_ARTIST =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+    public static final int TAG_WHITE_POINT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+    public static final int TAG_PRIMARY_CHROMATICITIES =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+    public static final int TAG_Y_CB_CR_COEFFICIENTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+    public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+    public static final int TAG_Y_CB_CR_POSITIONING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+    public static final int TAG_REFERENCE_BLACK_WHITE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+    public static final int TAG_COPYRIGHT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+    public static final int TAG_EXIF_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+    public static final int TAG_GPS_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+    // IFD 1
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+    // IFD Exif Tags
+    public static final int TAG_EXPOSURE_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+    public static final int TAG_F_NUMBER =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+    public static final int TAG_EXPOSURE_PROGRAM =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+    public static final int TAG_SPECTRAL_SENSITIVITY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+    public static final int TAG_ISO_SPEED_RATINGS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+    public static final int TAG_OECF =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+    public static final int TAG_EXIF_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+    public static final int TAG_DATE_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+    public static final int TAG_DATE_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+    public static final int TAG_COMPONENTS_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+    public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+    public static final int TAG_SHUTTER_SPEED_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+    public static final int TAG_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+    public static final int TAG_BRIGHTNESS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+    public static final int TAG_EXPOSURE_BIAS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+    public static final int TAG_MAX_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+    public static final int TAG_SUBJECT_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+    public static final int TAG_METERING_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+    public static final int TAG_LIGHT_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+    public static final int TAG_FLASH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+    public static final int TAG_FOCAL_LENGTH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+    public static final int TAG_SUBJECT_AREA =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+    public static final int TAG_MAKER_NOTE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+    public static final int TAG_USER_COMMENT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+    public static final int TAG_SUB_SEC_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+    public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+    public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+    public static final int TAG_FLASHPIX_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+    public static final int TAG_COLOR_SPACE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+    public static final int TAG_PIXEL_X_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+    public static final int TAG_PIXEL_Y_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+    public static final int TAG_RELATED_SOUND_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+    public static final int TAG_INTEROPERABILITY_IFD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+    public static final int TAG_FLASH_ENERGY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+    public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+    public static final int TAG_SUBJECT_LOCATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+    public static final int TAG_EXPOSURE_INDEX =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+    public static final int TAG_SENSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+    public static final int TAG_FILE_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+    public static final int TAG_SCENE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+    public static final int TAG_CFA_PATTERN =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+    public static final int TAG_CUSTOM_RENDERED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+    public static final int TAG_EXPOSURE_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+    public static final int TAG_WHITE_BALANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+    public static final int TAG_DIGITAL_ZOOM_RATIO =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+    public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+    public static final int TAG_SCENE_CAPTURE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+    public static final int TAG_GAIN_CONTROL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+    public static final int TAG_CONTRAST =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+    public static final int TAG_SATURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+    public static final int TAG_SHARPNESS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+    public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+    public static final int TAG_SUBJECT_DISTANCE_RANGE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+    public static final int TAG_IMAGE_UNIQUE_ID =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+    // IFD GPS tags
+    public static final int TAG_GPS_VERSION_ID =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+    public static final int TAG_GPS_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+    public static final int TAG_GPS_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+    public static final int TAG_GPS_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+    public static final int TAG_GPS_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+    public static final int TAG_GPS_ALTITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+    public static final int TAG_GPS_ALTITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+    public static final int TAG_GPS_TIME_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+    public static final int TAG_GPS_SATTELLITES =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+    public static final int TAG_GPS_STATUS =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+    public static final int TAG_GPS_MEASURE_MODE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+    public static final int TAG_GPS_DOP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+    public static final int TAG_GPS_SPEED_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+    public static final int TAG_GPS_SPEED =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+    public static final int TAG_GPS_TRACK_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+    public static final int TAG_GPS_TRACK =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+    public static final int TAG_GPS_IMG_DIRECTION_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+    public static final int TAG_GPS_IMG_DIRECTION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+    public static final int TAG_GPS_MAP_DATUM =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+    public static final int TAG_GPS_DEST_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+    public static final int TAG_GPS_DEST_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+    public static final int TAG_GPS_DEST_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+    public static final int TAG_GPS_DEST_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+    public static final int TAG_GPS_DEST_BEARING_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+    public static final int TAG_GPS_DEST_BEARING =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+    public static final int TAG_GPS_DEST_DISTANCE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+    public static final int TAG_GPS_DEST_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+    public static final int TAG_GPS_PROCESSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+    public static final int TAG_GPS_AREA_INFORMATION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+    public static final int TAG_GPS_DATE_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+    public static final int TAG_GPS_DIFFERENTIAL =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+    // IFD Interoperability tags
+    public static final int TAG_INTEROPERABILITY_INDEX =
+        defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+    /**
+     * Tags that contain offset markers. These are included in the banned
+     * defines.
+     */
+    private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+    static {
+        sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+        sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+    }
+
+    /**
+     * Tags with definitions that cannot be overridden (banned defines).
+     */
+    protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+    static {
+        sBannedDefines.add(getTrueTagKey(TAG_NULL));
+        sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+    }
+
+    /**
+     * Returns the constant representing a tag with a given TID and default IFD.
+     */
+    public static int defineTag(int ifdId, short tagId) {
+        return (tagId & 0x0000ffff) | (ifdId << 16);
+    }
+
+    /**
+     * Returns the TID for a tag constant.
+     */
+    public static short getTrueTagKey(int tag) {
+        // Truncate
+        return (short) tag;
+    }
+
+    /**
+     * Returns the default IFD for a tag constant.
+     */
+    public static int getTrueIfd(int tag) {
+        return tag >>> 16;
+    }
+
+    /**
+     * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+     * follows:
+     * <ul>
+     * <li>TOP_LEFT is the normal orientation.</li>
+     * <li>TOP_RIGHT is a left-right mirror.</li>
+     * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+     * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+     * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+     * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+     * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+     * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+     * </ul>
+     */
+    public static interface Orientation {
+        public static final short TOP_LEFT = 1;
+        public static final short TOP_RIGHT = 2;
+        public static final short BOTTOM_LEFT = 3;
+        public static final short BOTTOM_RIGHT = 4;
+        public static final short LEFT_TOP = 5;
+        public static final short RIGHT_TOP = 6;
+        public static final short LEFT_BOTTOM = 7;
+        public static final short RIGHT_BOTTOM = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+     */
+    public static interface YCbCrPositioning {
+        public static final short CENTERED = 1;
+        public static final short CO_SITED = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPRESSION}
+     */
+    public static interface Compression {
+        public static final short UNCOMPRESSION = 1;
+        public static final short JPEG = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_RESOLUTION_UNIT}
+     */
+    public static interface ResolutionUnit {
+        public static final short INCHES = 2;
+        public static final short CENTIMETERS = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+     */
+    public static interface PhotometricInterpretation {
+        public static final short RGB = 2;
+        public static final short YCBCR = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_PLANAR_CONFIGURATION}
+     */
+    public static interface PlanarConfiguration {
+        public static final short CHUNKY = 1;
+        public static final short PLANAR = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_PROGRAM}
+     */
+    public static interface ExposureProgram {
+        public static final short NOT_DEFINED = 0;
+        public static final short MANUAL = 1;
+        public static final short NORMAL_PROGRAM = 2;
+        public static final short APERTURE_PRIORITY = 3;
+        public static final short SHUTTER_PRIORITY = 4;
+        public static final short CREATIVE_PROGRAM = 5;
+        public static final short ACTION_PROGRAM = 6;
+        public static final short PROTRAIT_MODE = 7;
+        public static final short LANDSCAPE_MODE = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_METERING_MODE}
+     */
+    public static interface MeteringMode {
+        public static final short UNKNOWN = 0;
+        public static final short AVERAGE = 1;
+        public static final short CENTER_WEIGHTED_AVERAGE = 2;
+        public static final short SPOT = 3;
+        public static final short MULTISPOT = 4;
+        public static final short PATTERN = 5;
+        public static final short PARTAIL = 6;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+     * standard, we can treat this constant as bitwise flag.
+     * <p>
+     * e.g.
+     * <p>
+     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+     * MODE_AUTO_MODE
+     */
+    public static interface Flash {
+        // LSB
+        public static final short DID_NOT_FIRED = 0;
+        public static final short FIRED = 1;
+        // 1st~2nd bits
+        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+        // 3rd~4th bits
+        public static final short MODE_UNKNOWN = 0 << 3;
+        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+        public static final short MODE_AUTO_MODE = 3 << 3;
+        // 5th bit
+        public static final short FUNCTION_PRESENT = 0 << 5;
+        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+        // 6th bit
+        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+    }
+
+    /**
+     * Constants for {@link TAG_COLOR_SPACE}
+     */
+    public static interface ColorSpace {
+        public static final short SRGB = 1;
+        public static final short UNCALIBRATED = (short) 0xFFFF;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_MODE}
+     */
+    public static interface ExposureMode {
+        public static final short AUTO_EXPOSURE = 0;
+        public static final short MANUAL_EXPOSURE = 1;
+        public static final short AUTO_BRACKET = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_WHITE_BALANCE}
+     */
+    public static interface WhiteBalance {
+        public static final short AUTO = 0;
+        public static final short MANUAL = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+     */
+    public static interface SceneCapture {
+        public static final short STANDARD = 0;
+        public static final short LANDSCAPE = 1;
+        public static final short PROTRAIT = 2;
+        public static final short NIGHT_SCENE = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+     */
+    public static interface ComponentsConfiguration {
+        public static final short NOT_EXIST = 0;
+        public static final short Y = 1;
+        public static final short CB = 2;
+        public static final short CR = 3;
+        public static final short R = 4;
+        public static final short G = 5;
+        public static final short B = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_LIGHT_SOURCE}
+     */
+    public static interface LightSource {
+        public static final short UNKNOWN = 0;
+        public static final short DAYLIGHT = 1;
+        public static final short FLUORESCENT = 2;
+        public static final short TUNGSTEN = 3;
+        public static final short FLASH = 4;
+        public static final short FINE_WEATHER = 9;
+        public static final short CLOUDY_WEATHER = 10;
+        public static final short SHADE = 11;
+        public static final short DAYLIGHT_FLUORESCENT = 12;
+        public static final short DAY_WHITE_FLUORESCENT = 13;
+        public static final short COOL_WHITE_FLUORESCENT = 14;
+        public static final short WHITE_FLUORESCENT = 15;
+        public static final short STANDARD_LIGHT_A = 17;
+        public static final short STANDARD_LIGHT_B = 18;
+        public static final short STANDARD_LIGHT_C = 19;
+        public static final short D55 = 20;
+        public static final short D65 = 21;
+        public static final short D75 = 22;
+        public static final short D50 = 23;
+        public static final short ISO_STUDIO_TUNGSTEN = 24;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_SENSING_METHOD}
+     */
+    public static interface SensingMethod {
+        public static final short NOT_DEFINED = 1;
+        public static final short ONE_CHIP_COLOR = 2;
+        public static final short TWO_CHIP_COLOR = 3;
+        public static final short THREE_CHIP_COLOR = 4;
+        public static final short COLOR_SEQUENTIAL_AREA = 5;
+        public static final short TRILINEAR = 7;
+        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_FILE_SOURCE}
+     */
+    public static interface FileSource {
+        public static final short DSC = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_TYPE}
+     */
+    public static interface SceneType {
+        public static final short DIRECT_PHOTOGRAPHED = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GAIN_CONTROL}
+     */
+    public static interface GainControl {
+        public static final short NONE = 0;
+        public static final short LOW_UP = 1;
+        public static final short HIGH_UP = 2;
+        public static final short LOW_DOWN = 3;
+        public static final short HIGH_DOWN = 4;
+    }
+
+    /**
+     * Constants for {@link TAG_CONTRAST}
+     */
+    public static interface Contrast {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SATURATION}
+     */
+    public static interface Saturation {
+        public static final short NORMAL = 0;
+        public static final short LOW = 1;
+        public static final short HIGH = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SHARPNESS}
+     */
+    public static interface Sharpness {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SUBJECT_DISTANCE}
+     */
+    public static interface SubjectDistance {
+        public static final short UNKNOWN = 0;
+        public static final short MACRO = 1;
+        public static final short CLOSE_VIEW = 2;
+        public static final short DISTANT_VIEW = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LATITUDE_REF},
+     * {@link TAG_GPS_DEST_LATITUDE_REF}
+     */
+    public static interface GpsLatitudeRef {
+        public static final String NORTH = "N";
+        public static final String SOUTH = "S";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LONGITUDE_REF},
+     * {@link TAG_GPS_DEST_LONGITUDE_REF}
+     */
+    public static interface GpsLongitudeRef {
+        public static final String EAST = "E";
+        public static final String WEST = "W";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_ALTITUDE_REF}
+     */
+    public static interface GpsAltitudeRef {
+        public static final short SEA_LEVEL = 0;
+        public static final short SEA_LEVEL_NEGATIVE = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_STATUS}
+     */
+    public static interface GpsStatus {
+        public static final String IN_PROGRESS = "A";
+        public static final String INTEROPERABILITY = "V";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_MEASURE_MODE}
+     */
+    public static interface GpsMeasureMode {
+        public static final String MODE_2_DIMENSIONAL = "2";
+        public static final String MODE_3_DIMENSIONAL = "3";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_SPEED_REF},
+     * {@link TAG_GPS_DEST_DISTANCE_REF}
+     */
+    public static interface GpsSpeedRef {
+        public static final String KILOMETERS = "K";
+        public static final String MILES = "M";
+        public static final String KNOTS = "N";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_TRACK_REF},
+     * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+     */
+    public static interface GpsTrackRef {
+        public static final String TRUE_DIRECTION = "T";
+        public static final String MAGNETIC_DIRECTION = "M";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_DIFFERENTIAL}
+     */
+    public static interface GpsDifferential {
+        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+    }
+
+    private static final String NULL_ARGUMENT_STRING = "Argument is null";
+    private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+    public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+    public ExifInterface() {
+        mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    /**
+     * Reads the exif tags from a byte array, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(byte[] jpeg) throws IOException {
+        readExif(new ByteArrayInputStream(jpeg));
+    }
+
+    /**
+     * Reads the exif tags from an InputStream, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param inStream an InputStream containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(InputStream inStream) throws IOException {
+        if (inStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifData d = null;
+        try {
+            d = new ExifReader(this).read(inStream);
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+        mData = d;
+    }
+
+    /**
+     * Reads the exif tags from a file, clearing this ExifInterface object's
+     * existing exif tags.
+     *
+     * @param inFileName a string representing the filepath to jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void readExif(String inFileName) throws FileNotFoundException, IOException {
+        if (inFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(inFileName));
+            readExif(is);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Sets the exif tags, clearing this ExifInterface object's existing exif
+     * tags.
+     *
+     * @param tags a collection of exif tags to set.
+     */
+    public void setExif(Collection<ExifTag> tags) {
+        clearExif();
+        setTags(tags);
+    }
+
+    /**
+     * Clears this ExifInterface object's existing exif tags.
+     */
+    public void clearExif() {
+        mData = new ExifData(DEFAULT_BYTE_ORDER);
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+        if (jpeg == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        s.write(jpeg, 0, jpeg.length);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutStream the OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+        if (bmap == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+        if (jpegStream == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        doExifStreamIO(jpegStream, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (jpeg == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            s.write(jpeg, 0, jpeg.length);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (bmap == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegStream == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            doExifStreamIO(jpegStream, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg file, removing
+     * prior exif tags.
+     *
+     * @param jpegFileName a String containing the filepath for a jpeg file.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(String jpegFileName, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegFileName == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = new FileInputStream(jpegFileName);
+            writeExif(is, exifOutFileName);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param outStream an OutputStream to wrap.
+     * @return an OutputStream that wraps the outStream parameter, and adds exif
+     *         metadata. A jpeg image should be written to this stream.
+     */
+    public OutputStream getExifWriterStream(OutputStream outStream) {
+        if (outStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifOutputStream eos = new ExifOutputStream(outStream, this);
+        eos.setExifData(mData);
+        return eos;
+    }
+
+    /**
+     * Returns an OutputStream object that writes to a file. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param exifOutFileName an String containing a filepath for a jpeg file.
+     * @return an OutputStream that writes to the exifOutFileName file, and adds
+     *         exif metadata. A jpeg image should be written to this stream.
+     * @throws FileNotFoundException
+     */
+    public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+        if (exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream out = null;
+        try {
+            out = new FileOutputStream(exifOutFileName);
+        } catch (FileNotFoundException e) {
+            closeSilently(out);
+            throw e;
+        }
+        return getExifWriterStream(out);
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a file for the
+     * given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param filename a String containing a filepath for a jpeg file with exif
+     *            tags to rewrite.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException, IOException {
+        RandomAccessFile file = null;
+        InputStream is = null;
+        boolean ret;
+        try {
+            File temp = new File(filename);
+            is = new BufferedInputStream(new FileInputStream(temp));
+
+            // Parse beginning of APP1 in exif to find size of exif header.
+            ExifParser parser = null;
+            try {
+                parser = ExifParser.parse(is, this);
+            } catch (ExifInvalidFormatException e) {
+                throw new IOException("Invalid exif format : ", e);
+            }
+            long exifSize = parser.getOffsetToExifEndFromSOF();
+
+            // Free up resources
+            is.close();
+            is = null;
+
+            // Open file for memory mapping.
+            file = new RandomAccessFile(temp, "rw");
+            long fileLength = file.length();
+            if (fileLength < exifSize) {
+                throw new IOException("Filesize changed during operation");
+            }
+
+            // Map only exif header into memory.
+            ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+            // Attempt to overwrite tag values without changing lengths (avoids
+            // file copy).
+            ret = rewriteExif(buf, tags);
+        } catch (IOException e) {
+            closeSilently(file);
+            throw e;
+        } finally {
+            closeSilently(is);
+        }
+        file.close();
+        return ret;
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+     * the given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+     *            rewrite.
+     * @param tags tags that will be written into the jpeg ByteBuffer over
+     *            existing tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the ByteBuffer.
+     * @throws IOException
+     */
+    public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+        ExifModifier mod = null;
+        try {
+            mod = new ExifModifier(buf, this);
+            for (ExifTag t : tags) {
+                mod.modifyTag(t);
+            }
+            return mod.commit();
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+     * fall back to overwriting file. This preserves tags that are not being
+     * rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException,
+            IOException {
+        // Attempt in-place write
+        if (!rewriteExif(filename, tags)) {
+            // Fall back to doing a copy
+            ExifData tempData = mData;
+            mData = new ExifData(DEFAULT_BYTE_ORDER);
+            FileInputStream is = null;
+            ByteArrayOutputStream bytes = null;
+            try {
+                is = new FileInputStream(filename);
+                bytes = new ByteArrayOutputStream();
+                doExifStreamIO(is, bytes);
+                byte[] imageBytes = bytes.toByteArray();
+                readExif(imageBytes);
+                setTags(tags);
+                writeExif(imageBytes, filename);
+            } catch (IOException e) {
+                closeSilently(is);
+                throw e;
+            } finally {
+                is.close();
+                // Prevent clobbering of mData
+                mData = tempData;
+            }
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata using the tags in
+     * this ExifInterface object. If this fails, fall back to overwriting file.
+     * This preserves tags that are not being rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+        forceRewriteExif(filename, getAllTags());
+    }
+
+    /**
+     * Get the exif tags in this ExifInterface object or null if none exist.
+     *
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getAllTags() {
+        return mData.getAllTags();
+    }
+
+    /**
+     * Returns a list of ExifTags that share a TID (which can be obtained by
+     * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param tagId a TID as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForTagId(short tagId) {
+        return mData.getAllTagsForTagId(tagId);
+    }
+
+    /**
+     * Returns a list of ExifTags that share an IFD (which can be obtained by
+     * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param ifdId an IFD as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForIfdId(int ifdId) {
+        return mData.getAllTagsForIfd(ifdId);
+    }
+
+    /**
+     * Gets an ExifTag for an IFD other than the tag's default.
+     *
+     * @see #getTag
+     */
+    public ExifTag getTag(int tagId, int ifdId) {
+        if (!ExifTag.isValidIfd(ifdId)) {
+            return null;
+        }
+        return mData.getTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Returns the ExifTag in that tag's default IFD for a defined tag constant
+     * or null if none exists.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return an {@link ExifTag} or null if none exists.
+     */
+    public ExifTag getTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTag(tagId, ifdId);
+    }
+
+    /**
+     * Gets a tag value for an IFD other than the tag's default.
+     *
+     * @see #getTagValue
+     */
+    public Object getTagValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        return (t == null) ? null : t.getValue();
+    }
+
+    /**
+     * Returns the value of the ExifTag in that tag's default IFD for a defined
+     * tag constant or null if none exists or the value could not be cast into
+     * the return type.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the value of the ExifTag or null if none exists.
+     */
+    public Object getTagValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagValue(tagId, ifdId);
+    }
+
+    /*
+     * Getter methods that are similar to getTagValue. Null is returned if the
+     * tag value cannot be cast into the return type.
+     */
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsString();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagStringValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId, int ifdId) {
+        long[] l = getTagLongValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Long(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId, int ifdId) {
+        int[] l = getTagIntValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Integer(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId, int ifdId) {
+        byte[] l = getTagByteValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Byte(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId, int ifdId) {
+        Rational[] l = getTagRationalValues(tagId, ifdId);
+        if (l == null || l.length == 0) {
+            return null;
+        }
+        return new Rational(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsLongs();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsInts();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsBytes();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsRationals();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValues(tagId, ifdId);
+    }
+
+    /**
+     * Checks whether a tag has a defined number of elements.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return true if the tag has a defined number of elements.
+     */
+    public boolean isTagCountDefined(int tagId) {
+        int info = getTagInfo().get(tagId);
+        // No value in info can be zero, as all tags have a non-zero type
+        if (info == 0) {
+            return false;
+        }
+        return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+    }
+
+    /**
+     * Gets the defined number of elements for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+     *         tag or the number of elements is not defined.
+     */
+    public int getDefinedTagCount(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return ExifTag.SIZE_UNDEFINED;
+        }
+        return getComponentCountFromInfo(info);
+    }
+
+    /**
+     * Gets the number of elements for an ExifTag in a given IFD.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD containing the ExifTag to check.
+     * @return the number of elements in the ExifTag, if the tag's size is
+     *         undefined this will return the actual number of elements that is
+     *         in the ExifTag's value.
+     */
+    public int getActualTagCount(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return 0;
+        }
+        return t.getComponentCount();
+    }
+
+    /**
+     * Gets the default IFD for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+     *         definition exists.
+     */
+    public int getDefinedTagDefaultIfd(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == DEFINITION_NULL) {
+            return IFD_NULL;
+        }
+        return getTrueIfd(tagId);
+    }
+
+    /**
+     * Gets the defined type for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the type.
+     * @see ExifTag#getDataType()
+     */
+    public short getDefinedTagType(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return -1;
+        }
+        return getTypeFromInfo(info);
+    }
+
+    /**
+     * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+     * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+     * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+     * <p>
+     * Note: defining tags with these TID's is disallowed.
+     *
+     * @param tag a tag's TID (can be obtained from a defined tag constant with
+     *            {@link #getTrueTagKey}).
+     * @return true if the TID is that of an offset tag.
+     */
+    protected static boolean isOffsetTag(short tag) {
+        return sOffsetTags.contains(tag);
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in a given IFD if that IFD is
+     * allowed for the tag.  This method will fail anytime the appropriate
+     * {@link ExifTag#setValue} for this tag's datatype would fail.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the tag should be in.
+     * @param val the value of the tag to set.
+     * @return an ExifTag object or null if one could not be constructed.
+     * @see #buildTag
+     */
+    public ExifTag buildTag(int tagId, int ifdId, Object val) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0 || val == null) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+            return null;
+        }
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        if (!t.setValue(val)) {
+            return null;
+        }
+        return t;
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in the tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the tag's value.
+     * @return an ExifTag object.
+     */
+    public ExifTag buildTag(int tagId, Object val) {
+        int ifdId = getTrueIfd(tagId);
+        return buildTag(tagId, ifdId, val);
+    }
+
+    protected ExifTag buildUninitializedTag(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        int ifdId = getTrueIfd(tagId);
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        return t;
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists in the given IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the ExifTag is in.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     * @see #setTagValue
+     */
+    public boolean setTagValue(int tagId, int ifdId, Object val) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return false;
+        }
+        return t.setValue(val);
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists it's default IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     */
+    public boolean setTagValue(int tagId, Object val) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return setTagValue(tagId, ifdId, val);
+    }
+
+    /**
+     * Puts an ExifTag into this ExifInterface object's tags, removing a
+     * previous ExifTag with the same TID and IFD. The IFD it is put into will
+     * be the one the tag was created with in {@link #buildTag}.
+     *
+     * @param tag an ExifTag to put into this ExifInterface's tags.
+     * @return the previous ExifTag with the same TID and IFD or null if none
+     *         exists.
+     */
+    public ExifTag setTag(ExifTag tag) {
+        return mData.addTag(tag);
+    }
+
+    /**
+     * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+     * previous ExifTags with the same TID and IFDs will be removed.
+     *
+     * @param tags a Collection of ExifTags.
+     * @see #setTag
+     */
+    public void setTags(Collection<ExifTag> tags) {
+        for (ExifTag t : tags) {
+            setTag(t);
+        }
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from the given IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD of the ExifTag to remove.
+     */
+    public void deleteTag(int tagId, int ifdId) {
+        mData.removeTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from that tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void deleteTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        deleteTag(tagId, ifdId);
+    }
+
+    /**
+     * Creates a new tag definition in this ExifInterface object for a given TID
+     * and default IFD. Creating a definition with the same TID and default IFD
+     * as a previous definition will override it.
+     *
+     * @param tagId the TID for the tag.
+     * @param defaultIfd the default IFD for the tag.
+     * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+     * @param defaultComponentCount the number of elements of this tag's type in
+     *            the tags value.
+     * @param allowedIfds the IFD's this tag is allowed to be put in.
+     * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+     *         {@link #TAG_NULL} if the definition could not be made.
+     */
+    public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+            short defaultComponentCount, int[] allowedIfds) {
+        if (sBannedDefines.contains(tagId)) {
+            return TAG_NULL;
+        }
+        if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+            int tagDef = defineTag(defaultIfd, tagId);
+            if (tagDef == TAG_NULL) {
+                return TAG_NULL;
+            }
+            int[] otherDefs = getTagDefinitionsForTagId(tagId);
+            SparseIntArray infos = getTagInfo();
+            // Make sure defaultIfd is in allowedIfds
+            boolean defaultCheck = false;
+            for (int i : allowedIfds) {
+                if (defaultIfd == i) {
+                    defaultCheck = true;
+                }
+                if (!ExifTag.isValidIfd(i)) {
+                    return TAG_NULL;
+                }
+            }
+            if (!defaultCheck) {
+                return TAG_NULL;
+            }
+
+            int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+            // Make sure no identical tags can exist in allowedIfds
+            if (otherDefs != null) {
+                for (int def : otherDefs) {
+                    int tagInfo = infos.get(def);
+                    int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+                    if ((ifdFlags & allowedFlags) != 0) {
+                        return TAG_NULL;
+                    }
+                }
+            }
+            getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+            return tagDef;
+        }
+        return TAG_NULL;
+    }
+
+    protected int getTagDefinition(short tagId, int defaultIfd) {
+        return getTagInfo().get(defineTag(defaultIfd, tagId));
+    }
+
+    protected int[] getTagDefinitionsForTagId(short tagId) {
+        int[] ifds = IfdData.getIfds();
+        int[] defs = new int[ifds.length];
+        int counter = 0;
+        SparseIntArray infos = getTagInfo();
+        for (int i : ifds) {
+            int def = defineTag(i, tagId);
+            if (infos.get(def) != DEFINITION_NULL) {
+                defs[counter++] = def;
+            }
+        }
+        if (counter == 0) {
+            return null;
+        }
+
+        return Arrays.copyOfRange(defs, 0, counter);
+    }
+
+    protected int getTagDefinitionForTag(ExifTag tag) {
+        short type = tag.getDataType();
+        int count = tag.getComponentCount();
+        int ifd = tag.getIfd();
+        return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+    }
+
+    protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+        int[] defs = getTagDefinitionsForTagId(tagId);
+        if (defs == null) {
+            return TAG_NULL;
+        }
+        SparseIntArray infos = getTagInfo();
+        int ret = TAG_NULL;
+        for (int i : defs) {
+            int info = infos.get(i);
+            short def_type = getTypeFromInfo(info);
+            int def_count = getComponentCountFromInfo(info);
+            int[] def_ifds = getAllowedIfdsFromInfo(info);
+            boolean valid_ifd = false;
+            for (int j : def_ifds) {
+                if (j == ifd) {
+                    valid_ifd = true;
+                    break;
+                }
+            }
+            if (valid_ifd && type == def_type
+                    && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
+                ret = i;
+                break;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Removes a tag definition for given defined tag constant.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void removeTagDefinition(int tagId) {
+        getTagInfo().delete(tagId);
+    }
+
+    /**
+     * Resets tag definitions to the default ones.
+     */
+    public void resetTagDefinitions() {
+        mTagInfo = null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+     *
+     * @return the thumbnail as a bitmap.
+     */
+    public Bitmap getThumbnailBitmap() {
+        if (mData.hasCompressedThumbnail()) {
+            byte[] thumb = mData.getCompressedThumbnail();
+            return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement uncompressed
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+     * The bytes may either be an uncompressed strip as specified in the exif
+     * standard or a jpeg compressed image.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnailBytes() {
+        if (mData.hasCompressedThumbnail()) {
+            return mData.getCompressedThumbnail();
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement this
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnail() {
+        return mData.getCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail is compressed.
+     *
+     * @return true if the thumbnail is compressed.
+     */
+    public boolean isThumbnailCompressed() {
+        return mData.hasCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail exists.
+     *
+     * @return true if a compressed thumbnail exists.
+     */
+    public boolean hasThumbnail() {
+        // TODO: add back in uncompressed strip
+        return mData.hasCompressedThumbnail();
+    }
+
+    // TODO: uncompressed thumbnail setters
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a byte array containing a jpeg compressed image.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(byte[] thumb) {
+        mData.clearThumbnailAndStrips();
+        mData.setCompressedThumbnail(thumb);
+        return true;
+    }
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a bitmap to compress to a jpeg thumbnail.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(Bitmap thumb) {
+        ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+        if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+            return false;
+        }
+        return setCompressedThumbnail(thumbnail.toByteArray());
+    }
+
+    /**
+     * Clears the compressed thumbnail if it exists.
+     */
+    public void removeCompressedThumbnail() {
+        mData.setCompressedThumbnail(null);
+    }
+
+    // Convenience methods:
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    public String getUserComment() {
+        return mData.getUserComment();
+    }
+
+    /**
+     * Returns the Orientation ExifTag value for a given number of degrees.
+     *
+     * @param degrees the amount an image is rotated in degrees.
+     */
+    public static short getOrientationValueForRotation(int degrees) {
+        degrees %= 360;
+        if (degrees < 0) {
+            degrees += 360;
+        }
+        if (degrees < 90) {
+            return Orientation.TOP_LEFT; // 0 degrees
+        } else if (degrees < 180) {
+            return Orientation.RIGHT_TOP; // 90 degrees cw
+        } else if (degrees < 270) {
+            return Orientation.BOTTOM_LEFT; // 180 degrees
+        } else {
+            return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+        }
+    }
+
+    /**
+     * Returns the rotation degrees corresponding to an ExifTag Orientation
+     * value.
+     *
+     * @param orientation the ExifTag Orientation value.
+     */
+    public static int getRotationForOrientationValue(short orientation) {
+        switch (orientation) {
+            case Orientation.TOP_LEFT:
+                return 0;
+            case Orientation.RIGHT_TOP:
+                return 90;
+            case Orientation.BOTTOM_LEFT:
+                return 180;
+            case Orientation.RIGHT_BOTTOM:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Gets the double representation of the GPS latitude or longitude
+     * coordinate.
+     *
+     * @param coordinate an array of 3 Rationals representing the degrees,
+     *            minutes, and seconds of the GPS location as defined in the
+     *            exif specification.
+     * @param reference a GPS reference reperesented by a String containing "N",
+     *            "S", "E", or "W".
+     * @return the GPS coordinate represented as degrees + minutes/60 +
+     *         seconds/3600
+     */
+    public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+        try {
+            double degrees = coordinate[0].toDouble();
+            double minutes = coordinate[1].toDouble();
+            double seconds = coordinate[2].toDouble();
+            double result = degrees + minutes / 60.0 + seconds / 3600.0;
+            if ((reference.equals("S") || reference.equals("W"))) {
+                return -result;
+            }
+            return result;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Gets the GPS latitude and longitude as a pair of doubles from this
+     * ExifInterface object's tags, or null if the necessary tags do not exist.
+     *
+     * @return an array of 2 doubles containing the latitude, and longitude
+     *         respectively.
+     * @see #convertLatOrLongToDouble
+     */
+    public double[] getLatLongAsDoubles() {
+        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+        if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+                || latitude.length < 3 || longitude.length < 3) {
+            return null;
+        }
+        double[] latLon = new double[2];
+        latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+        latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+        return latLon;
+    }
+
+    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+    private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+    private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+    private final Calendar mGPSTimeStampCalendar = Calendar
+            .getInstance(TimeZone.getTimeZone("UTC"));
+
+    /**
+     * Creates, formats, and sets the DateTimeStamp tag for one of:
+     * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+     * {@link #TAG_DATE_TIME_ORIGINAL}.
+     *
+     * @param tagId one of the DateTimeStamp tags.
+     * @param timestamp a timestamp to format.
+     * @param timezone a TimeZone object.
+     * @return true if success, false if the tag could not be set.
+     */
+    public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+        if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+                || tagId == TAG_DATE_TIME_ORIGINAL) {
+            mDateTimeStampFormat.setTimeZone(timezone);
+            ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+            if (t == null) {
+                return false;
+            }
+            setTag(t);
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Creates and sets all to the GPS tags for a give latitude and longitude.
+     *
+     * @param latitude a GPS latitude coordinate.
+     * @param longitude a GPS longitude coordinate.
+     * @return true if success, false if they could not be created or set.
+     */
+    public boolean addGpsTags(double latitude, double longitude) {
+        ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+        ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+        ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+                latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
+                        : ExifInterface.GpsLatitudeRef.SOUTH);
+        ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+                longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
+                        : ExifInterface.GpsLongitudeRef.WEST);
+        if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+            return false;
+        }
+        setTag(latTag);
+        setTag(longTag);
+        setTag(latRefTag);
+        setTag(longRefTag);
+        return true;
+    }
+
+    /**
+     * Creates and sets the GPS timestamp tag.
+     *
+     * @param timestamp a GPS timestamp.
+     * @return true if success, false if could not be created or set.
+     */
+    public boolean addGpsDateTimeStampTag(long timestamp) {
+        ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+        t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+                new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+        });
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        return true;
+    }
+
+    private static Rational[] toExifLatLong(double value) {
+        // convert to the format dd/1 mm/1 ssss/100
+        value = Math.abs(value);
+        int degrees = (int) value;
+        value = (value - degrees) * 60;
+        int minutes = (int) value;
+        value = (value - minutes) * 6000;
+        int seconds = (int) value;
+        return new Rational[] {
+                new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+        };
+    }
+
+    private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+        byte[] buf = new byte[1024];
+        int ret = is.read(buf, 0, 1024);
+        while (ret != -1) {
+            os.write(buf, 0, ret);
+            ret = is.read(buf, 0, 1024);
+        }
+    }
+
+    protected static void closeSilently(Closeable c) {
+        if (c != null) {
+            try {
+                c.close();
+            } catch (Throwable e) {
+                // ignored
+            }
+        }
+    }
+
+    private SparseIntArray mTagInfo = null;
+
+    protected SparseIntArray getTagInfo() {
+        if (mTagInfo == null) {
+            mTagInfo = new SparseIntArray();
+            initTagInfo();
+        }
+        return mTagInfo;
+    }
+
+    private void initTagInfo() {
+        /**
+         * We put tag information in a 4-bytes integer. The first byte a bitmask
+         * representing the allowed IFDs of the tag, the second byte is the data
+         * type, and the last two byte are a short value indicating the default
+         * component count of this tag.
+         */
+        // IFD0 tags
+        int[] ifdAllowedIfds = {
+                IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+        };
+        int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+                | 1);
+        mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+        mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MODEL,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ARTIST,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // IFD1 tags
+        int[] ifd1AllowedIfds = {
+            IfdId.TYPE_IFD_1
+        };
+        int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // Exif tags
+        int[] exifAllowedIfds = {
+            IfdId.TYPE_IFD_EXIF
+        };
+        int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_OECF,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FLASH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CONTRAST,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SATURATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+                | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // GPS tag
+        int[] gpsAllowedIfds = {
+            IfdId.TYPE_IFD_GPS
+        };
+        int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+        mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+        // Interoperability tag
+        int[] interopAllowedIfds = {
+            IfdId.TYPE_IFD_INTEROPERABILITY
+        };
+        int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+        mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+                | ExifTag.SIZE_UNDEFINED);
+    }
+
+    protected static int getAllowedIfdFlagsFromInfo(int info) {
+        return info >>> 24;
+    }
+
+    protected static int[] getAllowedIfdsFromInfo(int info) {
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        int[] ifds = IfdData.getIfds();
+        ArrayList<Integer> l = new ArrayList<Integer>();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            int flag = (ifdFlags >> i) & 1;
+            if (flag == 1) {
+                l.add(ifds[i]);
+            }
+        }
+        if (l.size() <= 0) {
+            return null;
+        }
+        int[] ret = new int[l.size()];
+        int j = 0;
+        for (int i : l) {
+            ret[j++] = i;
+        }
+        return ret;
+    }
+
+    protected static boolean isIfdAllowed(int info, int ifd) {
+        int[] ifds = IfdData.getIfds();
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        for (int i = 0; i < ifds.length; i++) {
+            if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+        if (allowedIfds == null || allowedIfds.length == 0) {
+            return 0;
+        }
+        int flags = 0;
+        int[] ifds = IfdData.getIfds();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            for (int j : allowedIfds) {
+                if (ifds[i] == j) {
+                    flags |= 1 << i;
+                    break;
+                }
+            }
+        }
+        return flags;
+    }
+
+    protected static short getTypeFromInfo(int info) {
+        return (short) ((info >> 16) & 0x0ff);
+    }
+
+    protected static int getComponentCountFromInfo(int info) {
+        return info & 0x0ffff;
+    }
+
+}
diff --git a/src/com/android/mms/exif/ExifInvalidFormatException.java b/src/com/android/mms/exif/ExifInvalidFormatException.java
new file mode 100644
index 0000000..ad13235
--- /dev/null
+++ b/src/com/android/mms/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+public class ExifInvalidFormatException extends Exception {
+    public ExifInvalidFormatException(String meg) {
+        super(meg);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/mms/exif/ExifModifier.java b/src/com/android/mms/exif/ExifModifier.java
new file mode 100644
index 0000000..f5e5098
--- /dev/null
+++ b/src/com/android/mms/exif/ExifModifier.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+class ExifModifier {
+    public static final String TAG = "ExifModifier";
+    public static final boolean DEBUG = false;
+    private final ByteBuffer mByteBuffer;
+    private final ExifData mTagToModified;
+    private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+    private final ExifInterface mInterface;
+    private int mOffsetBase;
+
+    private static class TagOffset {
+        final int mOffset;
+        final ExifTag mTag;
+
+        TagOffset(ExifTag tag, int offset) {
+            mTag = tag;
+            mOffset = offset;
+        }
+    }
+
+    protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+            ExifInvalidFormatException {
+        mByteBuffer = byteBuffer;
+        mOffsetBase = byteBuffer.position();
+        mInterface = iRef;
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(byteBuffer);
+            // Do not require any IFD;
+            ExifParser parser = ExifParser.parse(is, mInterface);
+            mTagToModified = new ExifData(parser.getByteOrder());
+            mOffsetBase += parser.getTiffStartPosition();
+            mByteBuffer.position(0);
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+    }
+
+    protected ByteOrder getByteOrder() {
+        return mTagToModified.getByteOrder();
+    }
+
+    protected boolean commit() throws IOException, ExifInvalidFormatException {
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(mByteBuffer);
+            int flag = 0;
+            IfdData[] ifdDatas = new IfdData[] {
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
+            };
+
+            if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+                flag |= ExifParser.OPTION_IFD_0;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+                flag |= ExifParser.OPTION_IFD_1;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+                flag |= ExifParser.OPTION_IFD_EXIF;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+                flag |= ExifParser.OPTION_IFD_GPS;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
+                flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
+            }
+
+            ExifParser parser = ExifParser.parse(is, flag, mInterface);
+            int event = parser.next();
+            IfdData currIfd = null;
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        currIfd = ifdDatas[parser.getCurrentIfd()];
+                        if (currIfd == null) {
+                            parser.skipRemainingTagsInCurrentIfd();
+                        }
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag oldTag = parser.getTag();
+                        ExifTag newTag = currIfd.getTag(oldTag.getTagId());
+                        if (newTag != null) {
+                            if (newTag.getComponentCount() != oldTag.getComponentCount()
+                                    || newTag.getDataType() != oldTag.getDataType()) {
+                                return false;
+                            } else {
+                                mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
+                                currIfd.removeTag(oldTag.getTagId());
+                                if (currIfd.getTagCount() == 0) {
+                                    parser.skipRemainingTagsInCurrentIfd();
+                                }
+                            }
+                        }
+                        break;
+                }
+                event = parser.next();
+            }
+            for (IfdData ifd : ifdDatas) {
+                if (ifd != null && ifd.getTagCount() > 0) {
+                    return false;
+                }
+            }
+            modify();
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+        return true;
+    }
+
+    private void modify() {
+        mByteBuffer.order(getByteOrder());
+        for (TagOffset tagOffset : mTagOffsets) {
+            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
+        }
+    }
+
+    private void writeTagValue(ExifTag tag, int offset) {
+        if (DEBUG) {
+            Log.v(TAG, "modifying tag to: \n" + tag.toString());
+            Log.v(TAG, "at offset: " + offset);
+        }
+        mByteBuffer.position(offset + mOffsetBase);
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    mByteBuffer.put(buf);
+                } else {
+                    mByteBuffer.put(buf);
+                    mByteBuffer.put((byte) 0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    Rational v = tag.getRational(i);
+                    mByteBuffer.putInt((int) v.getNumerator());
+                    mByteBuffer.putInt((int) v.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                mByteBuffer.put(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+
+    public void modifyTag(ExifTag tag) {
+        mTagToModified.addTag(tag);
+    }
+}
diff --git a/src/com/android/mms/exif/ExifOutputStream.java b/src/com/android/mms/exif/ExifOutputStream.java
new file mode 100644
index 0000000..8e8f145
--- /dev/null
+++ b/src/com/android/mms/exif/ExifOutputStream.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * This class provides a way to replace the Exif header of a JPEG image.
+ * <p>
+ * Below is an example of writing EXIF data into a file
+ *
+ * <pre>
+ * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
+ *     OutputStream os = null;
+ *     try {
+ *         os = new FileOutputStream(path);
+ *         ExifOutputStream eos = new ExifOutputStream(os);
+ *         // Set the exif header
+ *         eos.setExifData(exif);
+ *         // Write the original jpeg out, the header will be add into the file.
+ *         eos.write(jpeg);
+ *     } catch (FileNotFoundException e) {
+ *         e.printStackTrace();
+ *     } catch (IOException e) {
+ *         e.printStackTrace();
+ *     } finally {
+ *         if (os != null) {
+ *             try {
+ *                 os.close();
+ *             } catch (IOException e) {
+ *                 e.printStackTrace();
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+class ExifOutputStream extends FilterOutputStream {
+    private static final String TAG = "ExifOutputStream";
+    private static final boolean DEBUG = false;
+    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+    private static final int STATE_SOI = 0;
+    private static final int STATE_FRAME_HEADER = 1;
+    private static final int STATE_JPEG_DATA = 2;
+
+    private static final int EXIF_HEADER = 0x45786966;
+    private static final short TIFF_HEADER = 0x002A;
+    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+    private static final short TAG_SIZE = 12;
+    private static final short TIFF_HEADER_SIZE = 8;
+    private static final int MAX_EXIF_SIZE = 65535;
+
+    private ExifData mExifData;
+    private int mState = STATE_SOI;
+    private int mByteToSkip;
+    private int mByteToCopy;
+    private final byte[] mSingleByteArray = new byte[1];
+    private final ByteBuffer mBuffer = ByteBuffer.allocate(4);
+    private final ExifInterface mInterface;
+
+    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
+        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+        mInterface = iRef;
+    }
+
+    /**
+     * Sets the ExifData to be written into the JPEG file. Should be called
+     * before writing image data.
+     */
+    protected void setExifData(ExifData exifData) {
+        mExifData = exifData;
+    }
+
+    /**
+     * Gets the Exif header to be written into the JPEF file.
+     */
+    protected ExifData getExifData() {
+        return mExifData;
+    }
+
+    private int requestByteToBuffer(int requestByteCount, byte[] buffer
+            , int offset, int length) {
+        int byteNeeded = requestByteCount - mBuffer.position();
+        int byteToRead = length > byteNeeded ? byteNeeded : length;
+        mBuffer.put(buffer, offset, byteToRead);
+        return byteToRead;
+    }
+
+    /**
+     * Writes the image out. The input data should be a valid JPEG format. After
+     * writing, it's Exif header will be replaced by the given header.
+     */
+    @Override
+    public void write(byte[] buffer, int offset, int length) throws IOException {
+        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+                && length > 0) {
+            if (mByteToSkip > 0) {
+                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+                length -= byteToProcess;
+                mByteToSkip -= byteToProcess;
+                offset += byteToProcess;
+            }
+            if (mByteToCopy > 0) {
+                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+                out.write(buffer, offset, byteToProcess);
+                length -= byteToProcess;
+                mByteToCopy -= byteToProcess;
+                offset += byteToProcess;
+            }
+            if (length == 0) {
+                return;
+            }
+            switch (mState) {
+                case STATE_SOI:
+                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    if (mBuffer.position() < 2) {
+                        return;
+                    }
+                    mBuffer.rewind();
+                    if (mBuffer.getShort() != JpegHeader.SOI) {
+                        throw new IOException("Not a valid jpeg image, cannot write exif");
+                    }
+                    out.write(mBuffer.array(), 0, 2);
+                    mState = STATE_FRAME_HEADER;
+                    mBuffer.rewind();
+                    writeExifData();
+                    break;
+                case STATE_FRAME_HEADER:
+                    // We ignore the APP1 segment and copy all other segments
+                    // until SOF tag.
+                    byteRead = requestByteToBuffer(4, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    // Check if this image data doesn't contain SOF.
+                    if (mBuffer.position() == 2) {
+                        short tag = mBuffer.getShort();
+                        if (tag == JpegHeader.EOI) {
+                            out.write(mBuffer.array(), 0, 2);
+                            mBuffer.rewind();
+                        }
+                    }
+                    if (mBuffer.position() < 4) {
+                        return;
+                    }
+                    mBuffer.rewind();
+                    short marker = mBuffer.getShort();
+                    if (marker == JpegHeader.APP1) {
+                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
+                        mState = STATE_JPEG_DATA;
+                    } else if (!JpegHeader.isSofMarker(marker)) {
+                        out.write(mBuffer.array(), 0, 4);
+                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+                    } else {
+                        out.write(mBuffer.array(), 0, 4);
+                        mState = STATE_JPEG_DATA;
+                    }
+                    mBuffer.rewind();
+            }
+        }
+        if (length > 0) {
+            out.write(buffer, offset, length);
+        }
+    }
+
+    /**
+     * Writes the one bytes out. The input data should be a valid JPEG format.
+     * After writing, it's Exif header will be replaced by the given header.
+     */
+    @Override
+    public void write(int oneByte) throws IOException {
+        mSingleByteArray[0] = (byte) (0xff & oneByte);
+        write(mSingleByteArray);
+    }
+
+    /**
+     * Equivalent to calling write(buffer, 0, buffer.length).
+     */
+    @Override
+    public void write(byte[] buffer) throws IOException {
+        write(buffer, 0, buffer.length);
+    }
+
+    private void writeExifData() throws IOException {
+        if (mExifData == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.v(TAG, "Writing exif data...");
+        }
+        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
+        createRequiredIfdAndTag();
+        int exifSize = calculateAllOffset();
+        if (exifSize + 8 > MAX_EXIF_SIZE) {
+            throw new IOException("Exif header is too large (>64Kb)");
+        }
+        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+        dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+        dataOutputStream.writeShort(JpegHeader.APP1);
+        dataOutputStream.writeShort((short) (exifSize + 8));
+        dataOutputStream.writeInt(EXIF_HEADER);
+        dataOutputStream.writeShort((short) 0x0000);
+        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+        } else {
+            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+        }
+        dataOutputStream.setByteOrder(mExifData.getByteOrder());
+        dataOutputStream.writeShort(TIFF_HEADER);
+        dataOutputStream.writeInt(8);
+        writeAllTags(dataOutputStream);
+        writeThumbnail(dataOutputStream);
+        for (ExifTag t : nullTags) {
+            mExifData.addTag(t);
+        }
+    }
+
+    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
+        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
+        for(ExifTag t : data.getAllTags()) {
+            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
+                data.removeTag(t.getTagId(), t.getIfd());
+                nullTags.add(t);
+            }
+        }
+        return nullTags;
+    }
+
+    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+        if (mExifData.hasCompressedThumbnail()) {
+            dataOutputStream.write(mExifData.getCompressedThumbnail());
+        } else if (mExifData.hasUncompressedStrip()) {
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                dataOutputStream.write(mExifData.getStrip(i));
+            }
+        }
+    }
+
+    private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interoperabilityIfd != null) {
+            writeIfd(interoperabilityIfd, dataOutputStream);
+        }
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            writeIfd(gpsIfd, dataOutputStream);
+        }
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+        }
+    }
+
+    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        ExifTag[] tags = ifd.getAllTags();
+        dataOutputStream.writeShort((short) tags.length);
+        for (ExifTag tag : tags) {
+            dataOutputStream.writeShort(tag.getTagId());
+            dataOutputStream.writeShort(tag.getDataType());
+            dataOutputStream.writeInt(tag.getComponentCount());
+            if (DEBUG) {
+                Log.v(TAG, "\n" + tag.toString());
+            }
+            if (tag.getDataSize() > 4) {
+                dataOutputStream.writeInt(tag.getOffset());
+            } else {
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
+                for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+                    dataOutputStream.write(0);
+                }
+            }
+        }
+        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+        for (ExifTag tag : tags) {
+            if (tag.getDataSize() > 4) {
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
+            }
+        }
+    }
+
+    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+        ExifTag[] tags = ifd.getAllTags();
+        for (ExifTag tag : tags) {
+            if (tag.getDataSize() > 4) {
+                tag.setOffset(offset);
+                offset += tag.getDataSize();
+            }
+        }
+        return offset;
+    }
+
+    private void createRequiredIfdAndTag() throws IOException {
+        // IFD0 is required for all file
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        if (ifd0 == null) {
+            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+            mExifData.addIfdData(ifd0);
+        }
+        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+        if (exifOffsetTag == null) {
+            throw new IOException("No definition for crucial exif tag: "
+                    + ExifInterface.TAG_EXIF_IFD);
+        }
+        ifd0.setTag(exifOffsetTag);
+
+        // Exif IFD is required for all files.
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        if (exifIfd == null) {
+            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+            mExifData.addIfdData(exifIfd);
+        }
+
+        // GPS IFD
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+            if (gpsOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_GPS_IFD);
+            }
+            ifd0.setTag(gpsOffsetTag);
+        }
+
+        // Interoperability IFD
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            ExifTag interOffsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+            if (interOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
+            }
+            exifIfd.setTag(interOffsetTag);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+
+            ExifTag offsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            }
+
+            ifd1.setTag(offsetTag);
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            }
+
+            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+            ifd1.setTag(lengthTag);
+
+            // Get rid of tags for uncompressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+        } else if (mExifData.hasUncompressedStrip()) {
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+            int stripCount = mExifData.getStripCount();
+            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_OFFSETS);
+            }
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            }
+            long[] lengths = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                lengths[i] = mExifData.getStrip(i).length;
+            }
+            lengthTag.setValue(lengths);
+            ifd1.setTag(offsetTag);
+            ifd1.setTag(lengthTag);
+            // Get rid of tags for compressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        } else if (ifd1 != null) {
+            // Get rid of offset and length tags if there is no thumbnail.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        }
+    }
+
+    private int calculateAllOffset() {
+        int offset = TIFF_HEADER_SIZE;
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        offset = calculateOffsetOfIfd(ifd0, offset);
+        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
+
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        offset = calculateOffsetOfIfd(exifIfd, offset);
+
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+                    .setValue(offset);
+            offset = calculateOffsetOfIfd(interIfd, offset);
+        }
+
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
+            offset = calculateOffsetOfIfd(gpsIfd, offset);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            ifd0.setOffsetToNextIfd(offset);
+            offset = calculateOffsetOfIfd(ifd1, offset);
+        }
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+                    .setValue(offset);
+            offset += mExifData.getCompressedThumbnail().length;
+        } else if (mExifData.hasUncompressedStrip()) {
+            int stripCount = mExifData.getStripCount();
+            long[] offsets = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                offsets[i] = offset;
+                offset += mExifData.getStrip(i).length;
+            }
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+                    offsets);
+        }
+        return offset;
+    }
+
+    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    dataOutputStream.write(buf);
+                } else {
+                    dataOutputStream.write(buf);
+                    dataOutputStream.write(0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeRational(tag.getRational(i));
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                dataOutputStream.write(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+}
diff --git a/src/com/android/mms/exif/ExifParser.java b/src/com/android/mms/exif/ExifParser.java
new file mode 100644
index 0000000..669cd3c
--- /dev/null
+++ b/src/com/android/mms/exif/ExifParser.java
@@ -0,0 +1,916 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format
+ * InputStream, the caller can request which IFD's to read via
+ * {@link #parse(InputStream, int)} with given options.
+ * <p>
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
+ * <pre>
+ * void parse() {
+ *     ExifParser parser = ExifParser.parse(mImageInputStream,
+ *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ *     int event = parser.next();
+ *     while (event != ExifParser.EVENT_END) {
+ *         switch (event) {
+ *             case ExifParser.EVENT_START_OF_IFD:
+ *                 break;
+ *             case ExifParser.EVENT_NEW_TAG:
+ *                 ExifTag tag = parser.getTag();
+ *                 if (!tag.hasValue()) {
+ *                     parser.registerForTagValue(tag);
+ *                 } else {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ *                 tag = parser.getTag();
+ *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *         }
+ *         event = parser.next();
+ *     }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ *     // process the tag as you like.
+ * }
+ * </pre>
+ */
+public class ExifParser {
+    private static final boolean LOGV = false;
+    private static final String TAG = "ExifParser";
+    /**
+     * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+     * know which IFD we are in.
+     */
+    public static final int EVENT_START_OF_IFD = 0;
+    /**
+     * When the parser reaches a new tag. Call {@link #getTag()}to get the
+     * corresponding tag.
+     */
+    public static final int EVENT_NEW_TAG = 1;
+    /**
+     * When the parser reaches the value area of tag that is registered by
+     * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+     * to get the corresponding tag.
+     */
+    public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+
+    /**
+     * When the parser reaches the compressed image area.
+     */
+    public static final int EVENT_COMPRESSED_IMAGE = 3;
+    /**
+     * When the parser reaches the uncompressed image strip. Call
+     * {@link #getStripIndex()} to get the index of the strip.
+     *
+     * @see #getStripIndex()
+     * @see #getStripCount()
+     */
+    public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+    /**
+     * When there is nothing more to parse.
+     */
+    public static final int EVENT_END = 5;
+
+    /**
+     * Option bit to request to parse IFD0.
+     */
+    public static final int OPTION_IFD_0 = 1 << 0;
+    /**
+     * Option bit to request to parse IFD1.
+     */
+    public static final int OPTION_IFD_1 = 1 << 1;
+    /**
+     * Option bit to request to parse Exif-IFD.
+     */
+    public static final int OPTION_IFD_EXIF = 1 << 2;
+    /**
+     * Option bit to request to parse GPS-IFD.
+     */
+    public static final int OPTION_IFD_GPS = 1 << 3;
+    /**
+     * Option bit to request to parse Interoperability-IFD.
+     */
+    public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+    /**
+     * Option bit to request to parse thumbnail.
+     */
+    public static final int OPTION_THUMBNAIL = 1 << 5;
+
+    protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+    protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+    // TIFF header
+    protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+    protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+    protected static final short TIFF_HEADER_TAIL = 0x002A;
+
+    protected static final int TAG_SIZE = 12;
+    protected static final int OFFSET_SIZE = 2;
+
+    private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+    protected static final int DEFAULT_IFD0_OFFSET = 8;
+
+    private final CountedDataInputStream mTiffStream;
+    private final int mOptions;
+    private int mIfdStartOffset = 0;
+    private int mNumOfTagInIfd = 0;
+    private int mIfdType;
+    private ExifTag mTag;
+    private ImageEvent mImageEvent;
+    private int mStripCount;
+    private ExifTag mStripSizeTag;
+    private ExifTag mJpegSizeTag;
+    private boolean mNeedToParseOffsetsInCurrentIfd;
+    private boolean mContainExifData = false;
+    private int mApp1End;
+    private int mOffsetToApp1EndFromSOF = 0;
+    private byte[] mDataAboveIfd0;
+    private int mIfd0Position;
+    private int mTiffStartPosition;
+    private final ExifInterface mInterface;
+
+    private static final short TAG_EXIF_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+    private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+    private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+    private static final short TAG_STRIP_OFFSETS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+    private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+
+    private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
+
+    private boolean isIfdRequested(int ifdType) {
+        switch (ifdType) {
+            case IfdId.TYPE_IFD_0:
+                return (mOptions & OPTION_IFD_0) != 0;
+            case IfdId.TYPE_IFD_1:
+                return (mOptions & OPTION_IFD_1) != 0;
+            case IfdId.TYPE_IFD_EXIF:
+                return (mOptions & OPTION_IFD_EXIF) != 0;
+            case IfdId.TYPE_IFD_GPS:
+                return (mOptions & OPTION_IFD_GPS) != 0;
+            case IfdId.TYPE_IFD_INTEROPERABILITY:
+                return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+        }
+        return false;
+    }
+
+    private boolean isThumbnailRequested() {
+        return (mOptions & OPTION_THUMBNAIL) != 0;
+    }
+
+    private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        if (inputStream == null) {
+            throw new IOException("Null argument inputStream to ExifParser");
+        }
+        if (LOGV) {
+            Log.v(TAG, "Reading exif...");
+        }
+        mInterface = iRef;
+        mContainExifData = seekTiffData(inputStream);
+        mTiffStream = new CountedDataInputStream(inputStream);
+        mOptions = options;
+        if (!mContainExifData) {
+            return;
+        }
+
+        parseTiffHeader();
+        long offset = mTiffStream.readUnsignedInt();
+        if (offset > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException("Invalid offset " + offset);
+        }
+        mIfd0Position = (int) offset;
+        mIfdType = IfdId.TYPE_IFD_0;
+        if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
+            registerIfd(IfdId.TYPE_IFD_0, offset);
+            if (offset != DEFAULT_IFD0_OFFSET) {
+                mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+                read(mDataAboveIfd0);
+            }
+        }
+    }
+
+    /**
+     * Parses the the given InputStream with the given options
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     */
+    protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, options, iRef);
+    }
+
+    /**
+     * Parses the the given InputStream with default options; that is, every IFD
+     * and thumbnaill will be parsed.
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #parse(InputStream, int)
+     */
+    protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+                | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+                | OPTION_THUMBNAIL, iRef);
+    }
+
+    /**
+     * Moves the parser forward and returns the next parsing event
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #EVENT_START_OF_IFD
+     * @see #EVENT_NEW_TAG
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     * @see #EVENT_COMPRESSED_IMAGE
+     * @see #EVENT_UNCOMPRESSED_STRIP
+     * @see #EVENT_END
+     */
+    protected int next() throws IOException, ExifInvalidFormatException {
+        if (!mContainExifData) {
+            return EVENT_END;
+        }
+        int offset = mTiffStream.getReadByteCount();
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        if (offset < endOfTags) {
+            mTag = readTag();
+            if (mTag == null) {
+                return next();
+            }
+            if (mNeedToParseOffsetsInCurrentIfd) {
+                checkOffsetOrImageTag(mTag);
+            }
+            return EVENT_NEW_TAG;
+        } else if (offset == endOfTags) {
+            // There is a link to ifd1 at the end of ifd0
+            if (mIfdType == IfdId.TYPE_IFD_0) {
+                long ifdOffset = readUnsignedLong();
+                if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+                    if (ifdOffset != 0) {
+                        registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+                    }
+                }
+            } else {
+                int offsetSize = 4;
+                // Some camera models use invalid length of the offset
+                if (mCorrespondingEvent.size() > 0) {
+                    offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+                            mTiffStream.getReadByteCount();
+                }
+                if (offsetSize < 4) {
+                    Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+                } else {
+                    long ifdOffset = readUnsignedLong();
+                    if (ifdOffset != 0) {
+                        Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+                    }
+                }
+            }
+        }
+        while (mCorrespondingEvent.size() != 0) {
+            Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+            Object event = entry.getValue();
+            try {
+                skipTo(entry.getKey());
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+                        " for " + event.getClass().getName() + ", the file may be broken.");
+                continue;
+            }
+            if (event instanceof IfdEvent) {
+                mIfdType = ((IfdEvent) event).ifd;
+                mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+                mIfdStartOffset = entry.getKey();
+
+                if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+                    Log.w(TAG, "Invalid size of IFD " + mIfdType);
+                    return EVENT_END;
+                }
+
+                mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+                if (((IfdEvent) event).isRequested) {
+                    return EVENT_START_OF_IFD;
+                } else {
+                    skipRemainingTagsInCurrentIfd();
+                }
+            } else if (event instanceof ImageEvent) {
+                mImageEvent = (ImageEvent) event;
+                return mImageEvent.type;
+            } else {
+                ExifTagEvent tagEvent = (ExifTagEvent) event;
+                mTag = tagEvent.tag;
+                if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+                    readFullTagValue(mTag);
+                    checkOffsetOrImageTag(mTag);
+                }
+                if (tagEvent.isRequested) {
+                    return EVENT_VALUE_OF_REGISTERED_TAG;
+                }
+            }
+        }
+        return EVENT_END;
+    }
+
+    /**
+     * Skips the tags area of current IFD, if the parser is not in the tag area,
+     * nothing will happen.
+     *
+     * @throws IOException
+     * @throws ExifInvalidFormatException
+     */
+    protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        int offset = mTiffStream.getReadByteCount();
+        if (offset > endOfTags) {
+            return;
+        }
+        if (mNeedToParseOffsetsInCurrentIfd) {
+            while (offset < endOfTags) {
+                mTag = readTag();
+                offset += TAG_SIZE;
+                if (mTag == null) {
+                    continue;
+                }
+                checkOffsetOrImageTag(mTag);
+            }
+        } else {
+            skipTo(endOfTags);
+        }
+        long ifdOffset = readUnsignedLong();
+        // For ifd0, there is a link to ifd1 in the end of all tags
+        if (mIfdType == IfdId.TYPE_IFD_0
+                && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+            if (ifdOffset > 0) {
+                registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+            }
+        }
+    }
+
+    private boolean needToParseOffsetsInCurrentIfd() {
+        switch (mIfdType) {
+            case IfdId.TYPE_IFD_0:
+                return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+                        || isIfdRequested(IfdId.TYPE_IFD_1);
+            case IfdId.TYPE_IFD_1:
+                return isThumbnailRequested();
+            case IfdId.TYPE_IFD_EXIF:
+                // The offset to interoperability IFD is located in Exif IFD
+                return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+     * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+     * corresponding tag.
+     * <p>
+     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+     * of the value is greater than 4 bytes. One should call
+     * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+     * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+     * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * pointed by the offset.
+     * <p>
+     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+     * tag will have already been read except for tags of undefined type. For
+     * tags of undefined type, call one of the read methods to get the value.
+     *
+     * @see #registerForTagValue(ExifTag)
+     * @see #read(byte[])
+     * @see #read(byte[], int, int)
+     * @see #readLong()
+     * @see #readRational()
+     * @see #readString(int)
+     * @see #readString(int, Charset)
+     */
+    protected ExifTag getTag() {
+        return mTag;
+    }
+
+    /**
+     * Gets number of tags in the current IFD area.
+     */
+    protected int getTagCountInCurrentIfd() {
+        return mNumOfTagInIfd;
+    }
+
+    /**
+     * Gets the ID of current IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     * @see IfdId#TYPE_IFD_EXIF
+     */
+    protected int getCurrentIfd() {
+        return mIfdType;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the index of this strip.
+     *
+     * @see #getStripCount()
+     */
+    protected int getStripIndex() {
+        return mImageEvent.stripIndex;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the number of strip data.
+     *
+     * @see #getStripIndex()
+     */
+    protected int getStripCount() {
+        return mStripCount;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the strip size.
+     */
+    protected int getStripSize() {
+        if (mStripSizeTag == null)
+            return 0;
+        return (int) mStripSizeTag.getValueAt(0);
+    }
+
+    /**
+     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+     * the image data size.
+     */
+    protected int getCompressedImageSize() {
+        if (mJpegSizeTag == null) {
+            return 0;
+        }
+        return (int) mJpegSizeTag.getValueAt(0);
+    }
+
+    private void skipTo(int offset) throws IOException {
+        mTiffStream.skipTo(offset);
+        while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+            mCorrespondingEvent.pollFirstEntry();
+        }
+    }
+
+    /**
+     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+     * not contain the value if the size of the value is greater than 4 bytes.
+     * When the value is not available here, call this method so that the parser
+     * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * where the value is located.
+     *
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     */
+    protected void registerForTagValue(ExifTag tag) {
+        if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
+            mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+        }
+    }
+
+    private void registerIfd(int ifdType, long offset) {
+        // Cast unsigned int to int since the offset is always smaller
+        // than the size of APP1 (65536)
+        mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+    }
+
+    private void registerCompressedImage(long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+    }
+
+    private void registerUncompressedStrip(int stripIndex, long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+                , stripIndex));
+    }
+
+    private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+        short tagId = mTiffStream.readShort();
+        short dataFormat = mTiffStream.readShort();
+        long numOfComp = mTiffStream.readUnsignedInt();
+        if (numOfComp > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException(
+                    "Number of component is larger then Integer.MAX_VALUE");
+        }
+        // Some invalid image file contains invalid data type. Ignore those tags
+        if (!ExifTag.isValidType(dataFormat)) {
+            Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+            mTiffStream.skip(4);
+            return null;
+        }
+        // TODO: handle numOfComp overflow
+        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+                ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
+        int dataSize = tag.getDataSize();
+        if (dataSize > 4) {
+            long offset = mTiffStream.readUnsignedInt();
+            if (offset > Integer.MAX_VALUE) {
+                throw new ExifInvalidFormatException(
+                        "offset is larger then Integer.MAX_VALUE");
+            }
+            // Some invalid images put some undefined data before IFD0.
+            // Read the data here.
+            if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+                byte[] buf = new byte[(int) numOfComp];
+                System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+                        buf, 0, (int) numOfComp);
+                tag.setValue(buf);
+            } else {
+                tag.setOffset((int) offset);
+            }
+        } else {
+            boolean defCount = tag.hasDefinedCount();
+            // Set defined count to 0 so we can add \0 to non-terminated strings
+            tag.setHasDefinedCount(false);
+            // Read value
+            readFullTagValue(tag);
+            tag.setHasDefinedCount(defCount);
+            mTiffStream.skip(4 - dataSize);
+            // Set the offset to the position of value.
+            tag.setOffset(mTiffStream.getReadByteCount() - 4);
+        }
+        return tag;
+    }
+
+    /**
+     * Check the tag, if the tag is one of the offset tag that points to the IFD
+     * or image the caller is interested in, register the IFD or image.
+     */
+    private void checkOffsetOrImageTag(ExifTag tag) {
+        // Some invalid formattd image contains tag with 0 size.
+        if (tag.getComponentCount() == 0) {
+            return;
+        }
+        short tid = tag.getTagId();
+        int ifd = tag.getIfd();
+        if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+                    || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+                registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_INTEROPERABILITY_IFD
+                && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+            if (isThumbnailRequested()) {
+                registerCompressedImage(tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+            if (isThumbnailRequested()) {
+                mJpegSizeTag = tag;
+            }
+        } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+            if (isThumbnailRequested()) {
+                if (tag.hasValue()) {
+                    for (int i = 0; i < tag.getComponentCount(); i++) {
+                        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
+                        } else {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
+                        }
+                    }
+                } else {
+                    mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+                }
+            }
+        } else if (tid == TAG_STRIP_BYTE_COUNTS
+                && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+                &&isThumbnailRequested() && tag.hasValue()) {
+            mStripSizeTag = tag;
+        }
+    }
+
+    private boolean checkAllowed(int ifd, int tagId) {
+        int info = mInterface.getTagInfo().get(tagId);
+        if (info == ExifInterface.DEFINITION_NULL) {
+            return false;
+        }
+        return ExifInterface.isIfdAllowed(info, ifd);
+    }
+
+    protected void readFullTagValue(ExifTag tag) throws IOException {
+        // Some invalid images contains tags with wrong size, check it here
+        short type = tag.getDataType();
+        if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+                type == ExifTag.TYPE_UNSIGNED_BYTE) {
+            int size = tag.getComponentCount();
+            if (mCorrespondingEvent.size() > 0) {
+                if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+                        + size) {
+                    Object event = mCorrespondingEvent.firstEntry().getValue();
+                    if (event instanceof ImageEvent) {
+                        // Tag value overlaps thumbnail, ignore thumbnail.
+                        Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
+                        Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+                        Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+                    } else {
+                        // Tag value overlaps another tag, shorten count
+                        if (event instanceof IfdEvent) {
+                            Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        } else if (event instanceof ExifTagEvent) {
+                            Log.w(TAG, "Tag value for tag: \n"
+                                    + ((ExifTagEvent) event).tag.toString()
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        }
+                        size = mCorrespondingEvent.firstEntry().getKey()
+                                - mTiffStream.getReadByteCount();
+                        Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+                                + " setting count to: " + size);
+                        tag.forceSetComponentCount(size);
+                    }
+                }
+            }
+        }
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+            case ExifTag.TYPE_UNDEFINED: {
+                byte buf[] = new byte[tag.getComponentCount()];
+                read(buf);
+                tag.setValue(buf);
+            }
+                break;
+            case ExifTag.TYPE_ASCII:
+                tag.setValue(readString(tag.getComponentCount()));
+                break;
+            case ExifTag.TYPE_UNSIGNED_LONG: {
+                long value[] = new long[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedLong();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedShort();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_LONG: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readLong();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+        }
+        if (LOGV) {
+            Log.v(TAG, "\n" + tag.toString());
+        }
+    }
+
+    private void parseTiffHeader() throws IOException,
+            ExifInvalidFormatException {
+        short byteOrder = mTiffStream.readShort();
+        if (LITTLE_ENDIAN_TAG == byteOrder) {
+            mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+        } else if (BIG_ENDIAN_TAG == byteOrder) {
+            mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+        } else {
+            throw new ExifInvalidFormatException("Invalid TIFF header");
+        }
+
+        if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
+            throw new ExifInvalidFormatException("Invalid TIFF header");
+        }
+    }
+
+    private boolean seekTiffData(InputStream inputStream) throws IOException,
+            ExifInvalidFormatException {
+        CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
+        if (dataStream.readShort() != JpegHeader.SOI) {
+            throw new ExifInvalidFormatException("Invalid JPEG format");
+        }
+
+        short marker = dataStream.readShort();
+        while (marker != JpegHeader.EOI
+                && !JpegHeader.isSofMarker(marker)) {
+            int length = dataStream.readUnsignedShort();
+            // Some invalid formatted image contains multiple APP1,
+            // try to find the one with Exif data.
+            if (marker == JpegHeader.APP1) {
+                int header = 0;
+                short headerTail = 0;
+                if (length >= 8) {
+                    header = dataStream.readInt();
+                    headerTail = dataStream.readShort();
+                    length -= 6;
+                    if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+                        mTiffStartPosition = dataStream.getReadByteCount();
+                        mApp1End = length;
+                        mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
+                        return true;
+                    }
+                }
+            }
+            if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+                Log.w(TAG, "Invalid JPEG format.");
+                return false;
+            }
+            marker = dataStream.readShort();
+        }
+        return false;
+    }
+
+    protected int getOffsetToExifEndFromSOF() {
+        return mOffsetToApp1EndFromSOF;
+    }
+
+    protected int getTiffStartPosition() {
+        return mTiffStartPosition;
+    }
+
+    /**
+     * Reads bytes from the InputStream.
+     */
+    protected int read(byte[] buffer, int offset, int length) throws IOException {
+        return mTiffStream.read(buffer, offset, length);
+    }
+
+    /**
+     * Equivalent to read(buffer, 0, buffer.length).
+     */
+    protected int read(byte[] buffer) throws IOException {
+        return mTiffStream.read(buffer);
+    }
+
+    /**
+     * Reads a String from the InputStream with US-ASCII charset. The parser
+     * will read n bytes and convert it to ascii string. This is used for
+     * reading values of type {@link ExifTag#TYPE_ASCII}.
+     */
+    protected String readString(int n) throws IOException {
+        return readString(n, US_ASCII);
+    }
+
+    /**
+     * Reads a String from the InputStream with the given charset. The parser
+     * will read n bytes and convert it to string. This is used for reading
+     * values of type {@link ExifTag#TYPE_ASCII}.
+     */
+    protected String readString(int n, Charset charset) throws IOException {
+        if (n > 0) {
+            return mTiffStream.readString(n, charset);
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+     * InputStream.
+     */
+    protected int readUnsignedShort() throws IOException {
+        return mTiffStream.readShort() & 0xffff;
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+     * InputStream.
+     */
+    protected long readUnsignedLong() throws IOException {
+        return readLong() & 0xffffffffL;
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+     * InputStream.
+     */
+    protected Rational readUnsignedRational() throws IOException {
+        long nomi = readUnsignedLong();
+        long denomi = readUnsignedLong();
+        return new Rational(nomi, denomi);
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+     */
+    protected int readLong() throws IOException {
+        return mTiffStream.readInt();
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+     */
+    protected Rational readRational() throws IOException {
+        int nomi = readLong();
+        int denomi = readLong();
+        return new Rational(nomi, denomi);
+    }
+
+    private static class ImageEvent {
+        int stripIndex;
+        int type;
+
+        ImageEvent(int type) {
+            this.stripIndex = 0;
+            this.type = type;
+        }
+
+        ImageEvent(int type, int stripIndex) {
+            this.type = type;
+            this.stripIndex = stripIndex;
+        }
+    }
+
+    private static class IfdEvent {
+        int ifd;
+        boolean isRequested;
+
+        IfdEvent(int ifd, boolean isInterestedIfd) {
+            this.ifd = ifd;
+            this.isRequested = isInterestedIfd;
+        }
+    }
+
+    private static class ExifTagEvent {
+        ExifTag tag;
+        boolean isRequested;
+
+        ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+            this.tag = tag;
+            this.isRequested = isRequireByUser;
+        }
+    }
+
+    /**
+     * Gets the byte order of the current InputStream.
+     */
+    protected ByteOrder getByteOrder() {
+        return mTiffStream.getByteOrder();
+    }
+}
diff --git a/src/com/android/mms/exif/ExifReader.java b/src/com/android/mms/exif/ExifReader.java
new file mode 100644
index 0000000..8477788
--- /dev/null
+++ b/src/com/android/mms/exif/ExifReader.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
+ */
+class ExifReader {
+    private static final String TAG = "ExifReader";
+
+    private final ExifInterface mInterface;
+
+    ExifReader(ExifInterface iRef) {
+        mInterface = iRef;
+    }
+
+    /**
+     * Parses the inputStream and and returns the EXIF data in an
+     * {@link ExifData}.
+     *
+     * @throws ExifInvalidFormatException
+     * @throws IOException
+     */
+    protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+            IOException {
+        ExifParser parser = ExifParser.parse(inputStream, mInterface);
+        ExifData exifData = new ExifData(parser.getByteOrder());
+        ExifTag tag = null;
+
+        int event = parser.next();
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    tag = parser.getTag();
+                    if (!tag.hasValue()) {
+                        parser.registerForTagValue(tag);
+                    } else {
+                        exifData.getIfdData(tag.getIfd()).setTag(tag);
+                    }
+                    break;
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                        parser.readFullTagValue(tag);
+                    }
+                    exifData.getIfdData(tag.getIfd()).setTag(tag);
+                    break;
+                case ExifParser.EVENT_COMPRESSED_IMAGE:
+                    byte buf[] = new byte[parser.getCompressedImageSize()];
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setCompressedThumbnail(buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the compressed thumbnail");
+                    }
+                    break;
+                case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+                    buf = new byte[parser.getStripSize()];
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setStripBytes(parser.getStripIndex(), buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the strip bytes");
+                    }
+                    break;
+            }
+            event = parser.next();
+        }
+        return exifData;
+    }
+}
diff --git a/src/com/android/mms/exif/ExifTag.java b/src/com/android/mms/exif/ExifTag.java
new file mode 100644
index 0000000..7a7b41c
--- /dev/null
+++ b/src/com/android/mms/exif/ExifTag.java
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
+ */
+public class ExifTag {
+    /**
+     * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+     */
+    public static final short TYPE_UNSIGNED_BYTE = 1;
+    /**
+     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+     * ASCII code. The final byte is terminated with NULL.
+     */
+    public static final short TYPE_ASCII = 2;
+    /**
+     * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+     */
+    public static final short TYPE_UNSIGNED_SHORT = 3;
+    /**
+     * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+     */
+    public static final short TYPE_UNSIGNED_LONG = 4;
+    /**
+     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+     * one is the numerator and the second one expresses the denominator.
+     */
+    public static final short TYPE_UNSIGNED_RATIONAL = 5;
+    /**
+     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+     * value depending on the field definition.
+     */
+    public static final short TYPE_UNDEFINED = 7;
+    /**
+     * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+     * (2's complement notation).
+     */
+    public static final short TYPE_LONG = 9;
+    /**
+     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+     * one is the numerator and the second one is the denominator.
+     */
+    public static final short TYPE_RATIONAL = 10;
+
+    private static Charset US_ASCII = Charset.forName("US-ASCII");
+    private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+    private static final int UNSIGNED_SHORT_MAX = 65535;
+    private static final long UNSIGNED_LONG_MAX = 4294967295L;
+    private static final long LONG_MAX = Integer.MAX_VALUE;
+    private static final long LONG_MIN = Integer.MIN_VALUE;
+
+    static {
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+        TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+        TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+    }
+
+    static final int SIZE_UNDEFINED = 0;
+
+    // Exif TagId
+    private final short mTagId;
+    // Exif Tag Type
+    private final short mDataType;
+    // If tag has defined count
+    private boolean mHasDefinedDefaultComponentCount;
+    // Actual data count in tag (should be number of elements in value array)
+    private int mComponentCountActual;
+    // The ifd that this tag should be put in
+    private int mIfd;
+    // The value (array of elements of type Tag Type)
+    private Object mValue;
+    // Value offset in exif header.
+    private int mOffset;
+
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
+    /**
+     * Returns true if the given IFD is a valid IFD.
+     */
+    public static boolean isValidIfd(int ifdId) {
+        return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+                || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+                || ifdId == IfdId.TYPE_IFD_GPS;
+    }
+
+    /**
+     * Returns true if a given type is a valid tag type.
+     */
+    public static boolean isValidType(short type) {
+        return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+                type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+                type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+                type == TYPE_LONG || type == TYPE_RATIONAL;
+    }
+
+    // Use builtTag in ExifInterface instead of constructor.
+    ExifTag(short tagId, short type, int componentCount, int ifd,
+            boolean hasDefinedComponentCount) {
+        mTagId = tagId;
+        mDataType = type;
+        mComponentCountActual = componentCount;
+        mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+        mIfd = ifd;
+        mValue = null;
+    }
+
+    /**
+     * Gets the element size of the given data type in bytes.
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_LONG
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_LONG
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
+    public static int getElementSize(short type) {
+        return TYPE_TO_SIZE_MAP[type];
+    }
+
+    /**
+     * Returns the ID of the IFD this tag belongs to.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public int getIfd() {
+        return mIfd;
+    }
+
+    protected void setIfd(int ifdId) {
+        mIfd = ifdId;
+    }
+
+    /**
+     * Gets the TID of this tag.
+     */
+    public short getTagId() {
+        return mTagId;
+    }
+
+    /**
+     * Gets the data type of this tag
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_LONG
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_LONG
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
+    public short getDataType() {
+        return mDataType;
+    }
+
+    /**
+     * Gets the total data size in bytes of the value of this tag.
+     */
+    public int getDataSize() {
+        return getComponentCount() * getElementSize(getDataType());
+    }
+
+    /**
+     * Gets the component count of this tag.
+     */
+
+    // TODO: fix integer overflows with this
+    public int getComponentCount() {
+        return mComponentCountActual;
+    }
+
+    /**
+     * Sets the component count of this tag. Call this function before
+     * setValue() if the length of value does not match the component count.
+     */
+    protected void forceSetComponentCount(int count) {
+        mComponentCountActual = count;
+    }
+
+    /**
+     * Returns true if this ExifTag contains value; otherwise, this tag will
+     * contain an offset value that is determined when the tag is written.
+     */
+    public boolean hasValue() {
+        return mValue != null;
+    }
+
+    /**
+     * Sets integer values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(int[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+                mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+            return false;
+        } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
+        }
+        mValue = data;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets integer value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition of this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(int value) {
+        return setValue(new int[] {
+                value
+        });
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(long[] value) {
+        if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(long value) {
+        return setValue(new long[] {
+                value
+        });
+    }
+
+    /**
+     * Sets a string value into this tag. This method should be used for tags of
+     * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+     * Characters that cannot be converted are replaced with '?'. The length of
+     * the string must be equal to either (component count -1) or (component
+     * count). The final byte will be set to the string null terminator '\0',
+     * overwriting the last character in the string if the value.length is equal
+     * to the component count. This method will fail if:
+     * <ul>
+     * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+     * <li>The length of the string is not equal to (component count -1) or
+     * (component count) in the definition for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(String value) {
+        if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+
+        byte[] buf = value.getBytes(US_ASCII);
+        byte[] finalBuf = buf;
+        if (buf.length > 0) {
+            finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+                .copyOf(buf, buf.length + 1);
+        } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+            finalBuf = new byte[] { 0 };
+        }
+        int count = finalBuf.length;
+        if (checkBadComponentCount(count)) {
+            return false;
+        }
+        mComponentCountActual = count;
+        mValue = finalBuf;
+        return true;
+    }
+
+    /**
+     * Sets Rational values into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+            return false;
+        } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+            return false;
+        }
+
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets a Rational value into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational value) {
+        return setValue(new Rational[] {
+                value
+        });
+    }
+
+    /**
+     * Sets byte values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The length does NOT match the component count in the definition for
+     * this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(byte[] value, int offset, int length) {
+        if (checkBadComponentCount(length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+        mValue = new byte[length];
+        System.arraycopy(value, offset, mValue, 0, length);
+        mComponentCountActual = length;
+        return true;
+    }
+
+    /**
+     * Equivalent to setValue(value, 0, value.length).
+     */
+    public boolean setValue(byte[] value) {
+        return setValue(value, 0, value.length);
+    }
+
+    /**
+     * Sets byte value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(byte value) {
+        return setValue(new byte[] {
+                value
+        });
+    }
+
+    /**
+     * Sets the value for this tag using an appropriate setValue method for the
+     * given object. This method will fail if:
+     * <ul>
+     * <li>The corresponding setValue method for the class of the object passed
+     * in would fail.</li>
+     * <li>There is no obvious way to cast the object passed in into an EXIF tag
+     * type.</li>
+     * </ul>
+     */
+    public boolean setValue(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (obj instanceof Short) {
+            return setValue(((Short) obj).shortValue() & 0x0ffff);
+        } else if (obj instanceof String) {
+            return setValue((String) obj);
+        } else if (obj instanceof int[]) {
+            return setValue((int[]) obj);
+        } else if (obj instanceof long[]) {
+            return setValue((long[]) obj);
+        } else if (obj instanceof Rational) {
+            return setValue((Rational) obj);
+        } else if (obj instanceof Rational[]) {
+            return setValue((Rational[]) obj);
+        } else if (obj instanceof byte[]) {
+            return setValue((byte[]) obj);
+        } else if (obj instanceof Integer) {
+            return setValue(((Integer) obj).intValue());
+        } else if (obj instanceof Long) {
+            return setValue(((Long) obj).longValue());
+        } else if (obj instanceof Byte) {
+            return setValue(((Byte) obj).byteValue());
+        } else if (obj instanceof Short[]) {
+            // Nulls in this array are treated as zeroes.
+            Short[] arr = (Short[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+            }
+            return setValue(fin);
+        } else if (obj instanceof Integer[]) {
+            // Nulls in this array are treated as zeroes.
+            Integer[] arr = (Integer[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Long[]) {
+            // Nulls in this array are treated as zeroes.
+            Long[] arr = (Long[]) obj;
+            long[] fin = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Byte[]) {
+            // Nulls in this array are treated as zeroes.
+            Byte[] arr = (Byte[]) obj;
+            byte[] fin = new byte[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+            }
+            return setValue(fin);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Sets a timestamp to this tag. The method converts the timestamp with the
+     * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+     * method will fail if the data type is not {@link #TYPE_ASCII} or the
+     * component count of this tag is not 20 or undefined.
+     *
+     * @param time the number of milliseconds since Jan. 1, 1970 GMT
+     * @return true on success
+     */
+    public boolean setTimeValue(long time) {
+        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+        synchronized (TIME_FORMAT) {
+            return setValue(TIME_FORMAT.format(new Date(time)));
+        }
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @return the value as a String, or null if the tag's value does not exist
+     *         or cannot be converted to a String.
+     */
+    public String getValueAsString() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof String) {
+            return (String) mValue;
+        } else if (mValue instanceof byte[]) {
+            return new String((byte[]) mValue, US_ASCII);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @param defaultValue the String to return if the tag's value does not
+     *            exist or cannot be converted to a String.
+     * @return the tag's value as a String, or the defaultValue.
+     */
+    public String getValueAsString(String defaultValue) {
+        String s = getValueAsString();
+        if (s == null) {
+            return defaultValue;
+        }
+        return s;
+    }
+
+    /**
+     * Gets the value as a byte array. This method should be used for tags of
+     * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @return the value as a byte array, or null if the tag's value does not
+     *         exist or cannot be converted to a byte array.
+     */
+    public byte[] getValueAsBytes() {
+        if (mValue instanceof byte[]) {
+            return (byte[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a byte. If there are more than 1 bytes in this value,
+     * gets the first byte. This method should be used for tags of type
+     * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @param defaultValue the byte to return if tag's value does not exist or
+     *            cannot be converted to a byte.
+     * @return the tag's value as a byte, or the defaultValue.
+     */
+    public byte getValueAsByte(byte defaultValue) {
+        byte[] b = getValueAsBytes();
+        if (b == null || b.length < 1) {
+            return defaultValue;
+        }
+        return b[0];
+    }
+
+    /**
+     * Gets the value as an array of Rationals. This method should be used for
+     * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @return the value as as an array of Rationals, or null if the tag's value
+     *         does not exist or cannot be converted to an array of Rationals.
+     */
+    public Rational[] getValueAsRationals() {
+        if (mValue instanceof Rational[]) {
+            return (Rational[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the Rational to return if tag's value does not exist
+     *            or cannot be converted to a Rational.
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(Rational defaultValue) {
+        Rational[] r = getValueAsRationals();
+        if (r == null || r.length < 1) {
+            return defaultValue;
+        }
+        return r[0];
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the numerator of the Rational to return if tag's
+     *            value does not exist or cannot be converted to a Rational (the
+     *            denominator will be 1).
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(long defaultValue) {
+        Rational defaultVal = new Rational(defaultValue, 1);
+        return getValueAsRational(defaultVal);
+    }
+
+    /**
+     * Gets the value as an array of ints. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of ints, or null if the tag's value does
+     *         not exist or cannot be converted to an array of ints.
+     */
+    public int[] getValueAsInts() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof long[]) {
+            long[] val = (long[]) mValue;
+            int[] arr = new int[val.length];
+            for (int i = 0; i < val.length; i++) {
+                arr[i] = (int) val[i]; // Truncates
+            }
+            return arr;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as an int. If there are more than 1 ints in this value,
+     * gets the first one. This method should be used for tags of type
+     * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the int to return if tag's value does not exist or
+     *            cannot be converted to an int.
+     * @return the tag's value as a int, or the defaultValue.
+     */
+    public int getValueAsInt(int defaultValue) {
+        int[] i = getValueAsInts();
+        if (i == null || i.length < 1) {
+            return defaultValue;
+        }
+        return i[0];
+    }
+
+    /**
+     * Gets the value as an array of longs. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of longs, or null if the tag's value
+     *         does not exist or cannot be converted to an array of longs.
+     */
+    public long[] getValueAsLongs() {
+        if (mValue instanceof long[]) {
+            return (long[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value or null if none exists. If there are more than 1 longs in
+     * this value, gets the first one. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the long to return if tag's value does not exist or
+     *            cannot be converted to a long.
+     * @return the tag's value as a long, or the defaultValue.
+     */
+    public long getValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l == null || l.length < 1) {
+            return defaultValue;
+        }
+        return l[0];
+    }
+
+    /**
+     * Gets the tag's value or null if none exists.
+     */
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Gets a long representation of the value.
+     *
+     * @param defaultValue value to return if there is no value or value is a
+     *            rational with a denominator of 0.
+     * @return the tag's value as a long, or defaultValue if no representation
+     *         exists.
+     */
+    public long forceGetValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l != null && l.length >= 1) {
+            return l[0];
+        }
+        byte[] b = getValueAsBytes();
+        if (b != null && b.length >= 1) {
+            return b[0];
+        }
+        Rational[] r = getValueAsRationals();
+        if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+            return (long) r[0].toDouble();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets a string representation of the value.
+     */
+    public String forceGetValueAsString() {
+        if (mValue == null) {
+            return "";
+        } else if (mValue instanceof byte[]) {
+            if (mDataType == TYPE_ASCII) {
+                return new String((byte[]) mValue, US_ASCII);
+            } else {
+                return Arrays.toString((byte[]) mValue);
+            }
+        } else if (mValue instanceof long[]) {
+            if (((long[]) mValue).length == 1) {
+                return String.valueOf(((long[]) mValue)[0]);
+            } else {
+                return Arrays.toString((long[]) mValue);
+            }
+        } else if (mValue instanceof Object[]) {
+            if (((Object[]) mValue).length == 1) {
+                Object val = ((Object[]) mValue)[0];
+                if (val == null) {
+                    return "";
+                } else {
+                    return val.toString();
+                }
+            } else {
+                return Arrays.toString((Object[]) mValue);
+            }
+        } else {
+            return mValue.toString();
+        }
+    }
+
+    /**
+     * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+     * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+     * {@link #getRational(int)} instead.
+     *
+     * @exception IllegalArgumentException if the data type is
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected long getValueAt(int index) {
+        if (mValue instanceof long[]) {
+            return ((long[]) mValue)[index];
+        } else if (mValue instanceof byte[]) {
+            return ((byte[]) mValue)[index];
+        }
+        throw new IllegalArgumentException("Cannot get integer value from "
+                + convertTypeToString(mDataType));
+    }
+
+    /**
+     * Gets the {@link #TYPE_ASCII} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_ASCII}.
+     */
+    protected String getString() {
+        if (mDataType != TYPE_ASCII) {
+            throw new IllegalArgumentException("Cannot get ASCII value from "
+                    + convertTypeToString(mDataType));
+        }
+        return new String((byte[]) mValue, US_ASCII);
+    }
+
+    /*
+     * Get the converted ascii byte. Used by ExifOutputStream.
+     */
+    protected byte[] getStringByte() {
+        return (byte[]) mValue;
+    }
+
+    /**
+     * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected Rational getRational(int index) {
+        if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+            throw new IllegalArgumentException("Cannot get RATIONAL value from "
+                    + convertTypeToString(mDataType));
+        }
+        return ((Rational[]) mValue)[index];
+    }
+
+    /**
+     * Equivalent to getBytes(buffer, 0, buffer.length).
+     */
+    protected void getBytes(byte[] buf) {
+        getBytes(buf, 0, buf.length);
+    }
+
+    /**
+     * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+     *
+     * @param buf the byte array in which to store the bytes read.
+     * @param offset the initial position in buffer to store the bytes.
+     * @param length the maximum number of bytes to store in buffer. If length >
+     *            component count, only the valid bytes will be stored.
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     */
+    protected void getBytes(byte[] buf, int offset, int length) {
+        if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+            throw new IllegalArgumentException("Cannot get BYTE value from "
+                    + convertTypeToString(mDataType));
+        }
+        System.arraycopy(mValue, 0, buf, offset,
+                (length > mComponentCountActual) ? mComponentCountActual : length);
+    }
+
+    /**
+     * Gets the offset of this tag. This is only valid if this data size > 4 and
+     * contains an offset to the location of the actual value.
+     */
+    protected int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Sets the offset of this tag.
+     */
+    protected void setOffset(int offset) {
+        mOffset = offset;
+    }
+
+    protected void setHasDefinedCount(boolean d) {
+        mHasDefinedDefaultComponentCount = d;
+    }
+
+    protected boolean hasDefinedCount() {
+        return mHasDefinedDefaultComponentCount;
+    }
+
+    private boolean checkBadComponentCount(int count) {
+        if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+            return true;
+        }
+        return false;
+    }
+
+    private static String convertTypeToString(short type) {
+        switch (type) {
+            case TYPE_UNSIGNED_BYTE:
+                return "UNSIGNED_BYTE";
+            case TYPE_ASCII:
+                return "ASCII";
+            case TYPE_UNSIGNED_SHORT:
+                return "UNSIGNED_SHORT";
+            case TYPE_UNSIGNED_LONG:
+                return "UNSIGNED_LONG";
+            case TYPE_UNSIGNED_RATIONAL:
+                return "UNSIGNED_RATIONAL";
+            case TYPE_UNDEFINED:
+                return "UNDEFINED";
+            case TYPE_LONG:
+                return "LONG";
+            case TYPE_RATIONAL:
+                return "RATIONAL";
+            default:
+                return "";
+        }
+    }
+
+    private boolean checkOverflowForUnsignedShort(int[] value) {
+        for (int v : value) {
+            if (v > UNSIGNED_SHORT_MAX || v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(long[] value) {
+        for (long v : value) {
+            if (v < 0 || v > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(int[] value) {
+        for (int v : value) {
+            if (v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < 0 || v.getDenominator() < 0
+                    || v.getNumerator() > UNSIGNED_LONG_MAX
+                    || v.getDenominator() > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+                    || v.getNumerator() > LONG_MAX
+                    || v.getDenominator() > LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof ExifTag) {
+            ExifTag tag = (ExifTag) obj;
+            if (tag.mTagId != this.mTagId
+                    || tag.mComponentCountActual != this.mComponentCountActual
+                    || tag.mDataType != this.mDataType) {
+                return false;
+            }
+            if (mValue != null) {
+                if (tag.mValue == null) {
+                    return false;
+                } else if (mValue instanceof long[]) {
+                    if (!(tag.mValue instanceof long[])) {
+                        return false;
+                    }
+                    return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+                } else if (mValue instanceof Rational[]) {
+                    if (!(tag.mValue instanceof Rational[])) {
+                        return false;
+                    }
+                    return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+                } else if (mValue instanceof byte[]) {
+                    if (!(tag.mValue instanceof byte[])) {
+                        return false;
+                    }
+                    return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
+                } else {
+                    return mValue.equals(tag.mValue);
+                }
+            } else {
+                return tag.mValue == null;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+                + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+                + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+    }
+
+}
diff --git a/src/com/android/mms/exif/IfdData.java b/src/com/android/mms/exif/IfdData.java
new file mode 100644
index 0000000..992cc6f
--- /dev/null
+++ b/src/com/android/mms/exif/IfdData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+    private final int mIfdId;
+    private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
+    private int mOffsetToNextIfd = 0;
+    private static final int[] sIfds = {
+            IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+            IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+    };
+    /**
+     * Creates an IfdData with given IFD ID.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    IfdData(int ifdId) {
+        mIfdId = ifdId;
+    }
+
+    static protected int[] getIfds() {
+        return sIfds;
+    }
+
+    /**
+     * Get a array the contains all {@link ExifTag} in this IFD.
+     */
+    protected ExifTag[] getAllTags() {
+        return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
+    }
+
+    /**
+     * Gets the ID of this IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    protected int getId() {
+        return mIfdId;
+    }
+
+    /**
+     * Gets the {@link ExifTag} with given tag id. Return null if there is no
+     * such tag.
+     */
+    protected ExifTag getTag(short tagId) {
+        return mExifTags.get(tagId);
+    }
+
+    /**
+     * Adds or replaces a {@link ExifTag}.
+     */
+    protected ExifTag setTag(ExifTag tag) {
+        tag.setIfd(mIfdId);
+        return mExifTags.put(tag.getTagId(), tag);
+    }
+
+    protected boolean checkCollision(short tagId) {
+        return mExifTags.get(tagId) != null;
+    }
+
+    /**
+     * Removes the tag of the given ID
+     */
+    protected void removeTag(short tagId) {
+        mExifTags.remove(tagId);
+    }
+
+    /**
+     * Gets the tags count in the IFD.
+     */
+    protected int getTagCount() {
+        return mExifTags.size();
+    }
+
+    /**
+     * Sets the offset of next IFD.
+     */
+    protected void setOffsetToNextIfd(int offset) {
+        mOffsetToNextIfd = offset;
+    }
+
+    /**
+     * Gets the offset of next IFD.
+     */
+    protected int getOffsetToNextIfd() {
+        return mOffsetToNextIfd;
+    }
+
+    /**
+     * Returns true if all tags in this two IFDs are equal. Note that tags of
+     * IFDs offset or thumbnail offset will be ignored.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof IfdData) {
+            IfdData data = (IfdData) obj;
+            if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+                ExifTag[] tags = data.getAllTags();
+                for (ExifTag tag : tags) {
+                    if (ExifInterface.isOffsetTag(tag.getTagId())) {
+                        continue;
+                    }
+                    ExifTag tag2 = mExifTags.get(tag.getTagId());
+                    if (!tag.equals(tag2)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/mms/exif/IfdId.java b/src/com/android/mms/exif/IfdId.java
new file mode 100644
index 0000000..e9180a5
--- /dev/null
+++ b/src/com/android/mms/exif/IfdId.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+public interface IfdId {
+    public static final int TYPE_IFD_0 = 0;
+    public static final int TYPE_IFD_1 = 1;
+    public static final int TYPE_IFD_EXIF = 2;
+    public static final int TYPE_IFD_INTEROPERABILITY = 3;
+    public static final int TYPE_IFD_GPS = 4;
+    /* This is used in ExifData to allocate enough IfdData */
+    static final int TYPE_IFD_COUNT = 5;
+
+}
diff --git a/src/com/android/mms/exif/JpegHeader.java b/src/com/android/mms/exif/JpegHeader.java
new file mode 100644
index 0000000..d41b0ad
--- /dev/null
+++ b/src/com/android/mms/exif/JpegHeader.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+class JpegHeader {
+    public static final short SOI =  (short) 0xFFD8;
+    public static final short APP1 = (short) 0xFFE1;
+    public static final short APP0 = (short) 0xFFE0;
+    public static final short EOI = (short) 0xFFD9;
+
+    /**
+     *  SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
+     *  and DAC marker.
+     */
+    public static final short SOF0 = (short) 0xFFC0;
+    public static final short SOF15 = (short) 0xFFCF;
+    public static final short DHT = (short) 0xFFC4;
+    public static final short JPG = (short) 0xFFC8;
+    public static final short DAC = (short) 0xFFCC;
+
+    public static final boolean isSofMarker(short marker) {
+        return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
+                && marker != DAC;
+    }
+}
diff --git a/src/com/android/mms/exif/OrderedDataOutputStream.java b/src/com/android/mms/exif/OrderedDataOutputStream.java
new file mode 100644
index 0000000..961c537
--- /dev/null
+++ b/src/com/android/mms/exif/OrderedDataOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+    private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+    public OrderedDataOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public OrderedDataOutputStream setByteOrder(ByteOrder order) {
+        mByteBuffer.order(order);
+        return this;
+    }
+
+    public OrderedDataOutputStream writeShort(short value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putShort(value);
+        out.write(mByteBuffer.array(), 0, 2);
+        return this;
+    }
+
+    public OrderedDataOutputStream writeInt(int value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putInt(value);
+        out.write(mByteBuffer.array());
+        return this;
+    }
+
+    public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+        writeInt((int) rational.getNumerator());
+        writeInt((int) rational.getDenominator());
+        return this;
+    }
+}
diff --git a/src/com/android/mms/exif/Rational.java b/src/com/android/mms/exif/Rational.java
new file mode 100644
index 0000000..d4b2bd5
--- /dev/null
+++ b/src/com/android/mms/exif/Rational.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
+public class Rational {
+
+    private final long mNumerator;
+    private final long mDenominator;
+
+    /**
+     * Create a Rational with a given numerator and denominator.
+     *
+     * @param nominator
+     * @param denominator
+     */
+    public Rational(long nominator, long denominator) {
+        mNumerator = nominator;
+        mDenominator = denominator;
+    }
+
+    /**
+     * Create a copy of a Rational.
+     */
+    public Rational(Rational r) {
+        mNumerator = r.mNumerator;
+        mDenominator = r.mDenominator;
+    }
+
+    /**
+     * Gets the numerator of the rational.
+     */
+    public long getNumerator() {
+        return mNumerator;
+    }
+
+    /**
+     * Gets the denominator of the rational
+     */
+    public long getDenominator() {
+        return mDenominator;
+    }
+
+    /**
+     * Gets the rational value as type double. Will cause a divide-by-zero error
+     * if the denominator is 0.
+     */
+    public double toDouble() {
+        return mNumerator / (double) mDenominator;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof Rational) {
+            Rational data = (Rational) obj;
+            return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return mNumerator + "/" + mDenominator;
+    }
+}
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/SlideshowModel.java b/src/com/android/mms/model/SlideshowModel.java
index 722ee9d..3e79e3e 100755
--- a/src/com/android/mms/model/SlideshowModel.java
+++ b/src/com/android/mms/model/SlideshowModel.java
@@ -60,6 +60,7 @@
 import com.google.android.mms.pdu.PduHeaders;
 import com.google.android.mms.pdu.PduPart;
 import com.google.android.mms.pdu.PduPersister;
+import com.android.mms.UnsupportContentTypeException;
 
 public class SlideshowModel extends Model
         implements List<SlideModel>, IModelChangedObserver {
@@ -214,6 +215,8 @@
                     Log.e(TAG, e.getMessage(), e);
                 } catch (IllegalArgumentException e) {
                     Log.e(TAG, e.getMessage(), e);
+                } catch (UnsupportContentTypeException e) {
+                    Log.e(TAG, e.getMessage(), e);
                 }
             }
 
diff --git a/src/com/android/mms/model/TextModel.java b/src/com/android/mms/model/TextModel.java
index 15ff5eb..d6f2663 100644
--- a/src/com/android/mms/model/TextModel.java
+++ b/src/com/android/mms/model/TextModel.java
@@ -90,7 +90,7 @@
     }
 
     public void cloneText() {
-        mText = new String(mText.toString());
+        mText = new String((mText != null ? mText.toString() : ""));
     }
 
     public int getCharset() {
diff --git a/src/com/android/mms/transaction/MessagingNotification.java b/src/com/android/mms/transaction/MessagingNotification.java
index 334707e..1a8d0db 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;
@@ -292,6 +292,9 @@
         if (delivery != null) {
             delivery.deliver(context, isStatusMessage);
         }
+
+        notificationSet.clear();
+        threads.clear();
     }
 
     /**
@@ -307,9 +310,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 +795,7 @@
             return;
         }
 
-        sToastHandler.post(new Runnable() {
+        sHandler.post(new Runnable() {
             @Override
             public void run() {
                 Toast.makeText(context, message, (int)timeMillis).show();
@@ -893,27 +904,24 @@
 
         if (isNew) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
-            String vibrateWhen;
-            if (sp.contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN)) {
-                vibrateWhen =
-                    sp.getString(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN, null);
-            } else if (sp.contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE)) {
-                vibrateWhen =
-                        sp.getBoolean(MessagingPreferenceActivity.NOTIFICATION_VIBRATE, false) ?
-                    context.getString(R.string.prefDefault_vibrate_true) :
-                    context.getString(R.string.prefDefault_vibrate_false);
-            } else {
-                vibrateWhen = context.getString(R.string.prefDefault_vibrateWhen);
+
+            boolean vibrate = false;
+            if (sp.contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE)) {
+                // The most recent change to the vibrate preference is to store a boolean
+                // value in NOTIFICATION_VIBRATE. If prefs contain that preference, use that
+                // first.
+                vibrate = sp.getBoolean(MessagingPreferenceActivity.NOTIFICATION_VIBRATE,
+                        false);
+            } else if (sp.contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN)) {
+                // This is to support the pre-JellyBean MR1.1 version of vibrate preferences
+                // when vibrate was a tri-state setting. As soon as the user opens the Messaging
+                // app's settings, it will migrate this setting from NOTIFICATION_VIBRATE_WHEN
+                // to the boolean value stored in NOTIFICATION_VIBRATE.
+                String vibrateWhen =
+                        sp.getString(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN, null);
+                vibrate = "always".equals(vibrateWhen);
             }
-
-            boolean vibrateAlways = vibrateWhen.equals("always");
-            boolean vibrateSilent = vibrateWhen.equals("silent");
-            AudioManager audioManager =
-                (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
-            boolean nowSilent =
-                audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
-
-            if (vibrateAlways || vibrateSilent && nowSilent) {
+            if (vibrate) {
                 defaults |= Notification.DEFAULT_VIBRATE;
             }
 
@@ -1022,6 +1030,10 @@
                     inboxStyle.addLine(info.formatInboxMessage(context));
                 }
                 notification = inboxStyle.build();
+
+                uniqueThreads.clear();
+                mostRecentNotifPerThread.clear();
+
                 if (DEBUG) {
                     Log.d(TAG, "updateNotification: multi messages," +
                             " showing inboxStyle notification");
@@ -1293,7 +1305,15 @@
 
         try {
             if (cursor.moveToFirst()) {
-                long threadId = cursor.getLong(cursor.getColumnIndex(Sms.THREAD_ID));
+                int columnIndex = cursor.getColumnIndex(Sms.THREAD_ID);
+                if (columnIndex < 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "getSmsThreadId uri: " + uri +
+                                " Couldn't read row 0, col -1! returning THREAD_NONE");
+                    }
+                    return THREAD_NONE;
+                }
+                long threadId = cursor.getLong(columnIndex);
                 if (DEBUG) {
                     Log.d(TAG, "getSmsThreadId uri: " + uri +
                             " returning threadId: " + threadId);
@@ -1336,7 +1356,15 @@
 
         try {
             if (cursor.moveToFirst()) {
-                long threadId = cursor.getLong(cursor.getColumnIndex(Mms.THREAD_ID));
+                int columnIndex = cursor.getColumnIndex(Mms.THREAD_ID);
+                if (columnIndex < 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "getThreadId uri: " + uri +
+                                " Couldn't read row 0, col -1! returning THREAD_NONE");
+                    }
+                    return THREAD_NONE;
+                }
+                long threadId = cursor.getLong(columnIndex);
                 if (DEBUG) {
                     Log.d(TAG, "getThreadId uri: " + uri +
                             " returning threadId: " + threadId);
diff --git a/src/com/android/mms/transaction/MmsPushOutboxMessages.java b/src/com/android/mms/transaction/MmsPushOutboxMessages.java
new file mode 100644
index 0000000..7eb245c
--- /dev/null
+++ b/src/com/android/mms/transaction/MmsPushOutboxMessages.java
@@ -0,0 +1,47 @@
+/*
+* Copyright (C) 2013 Samsung System LSI
+* 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.transaction;
+
+import com.android.mms.LogTag;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * MmsPushOutboxMessages listens for MMS_SEND_OUTBOX_MSG intent .
+ * {@link android.intent.action.MMS_SEND_OUTBOX_MSG},
+ * and wakes up the mms service when it receives it.
+ * This will tricker the mms service to send any messages stored
+ * in the outbox.
+ */
+public class MmsPushOutboxMessages extends BroadcastReceiver {
+    private static final String INTENT_MMS_SEND_OUTBOX_MSG = "android.intent.action.MMS_SEND_OUTBOX_MSG";
+    private static final String TAG = "MmsPushOutboxMessages";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+            Log.v(TAG, "Received the MMS_SEND_OUTBOX_MSG intent: " + intent);
+        }
+        String action = intent.getAction();
+        if(action.equalsIgnoreCase(INTENT_MMS_SEND_OUTBOX_MSG)){
+            Log.d(TAG,"Now waking up the MMS service");
+            context.startService(new Intent(context, TransactionService.class));
+        }
+    }
+
+}
diff --git a/src/com/android/mms/transaction/MmsSystemEventReceiver.java b/src/com/android/mms/transaction/MmsSystemEventReceiver.java
index b8eb917..9083339 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,32 @@
         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);
+            }
+            if (!mConnMgr.getMobileDataEnabled()) {
+                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                    Log.v(TAG, "mobile data turned off, bailing");
+                }
+                return;
+            }
+            NetworkInfo mmsNetworkInfo = mConnMgr
+                    .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+            if (mmsNetworkInfo == null) {
+                return;
+            }
+            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 +96,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/NotificationTransaction.java b/src/com/android/mms/transaction/NotificationTransaction.java
index 647f6e2..286bbb9 100644
--- a/src/com/android/mms/transaction/NotificationTransaction.java
+++ b/src/com/android/mms/transaction/NotificationTransaction.java
@@ -92,8 +92,8 @@
             throw new IllegalArgumentException();
         }
 
-        mId = new String(mNotificationInd.getTransactionId());
         mContentLocation = new String(mNotificationInd.getContentLocation());
+        mId = mContentLocation;
 
         // Attach the transaction to the instance of RetryScheduler.
         attach(RetryScheduler.getInstance(context));
@@ -119,7 +119,7 @@
         }
 
         mNotificationInd = ind;
-        mId = new String(ind.getTransactionId());
+        mId = new String(mNotificationInd.getContentLocation());
     }
 
     /*
diff --git a/src/com/android/mms/transaction/PushReceiver.java b/src/com/android/mms/transaction/PushReceiver.java
index b0d083c..25e0767 100644
--- a/src/com/android/mms/transaction/PushReceiver.java
+++ b/src/com/android/mms/transaction/PushReceiver.java
@@ -17,7 +17,7 @@
 
 package com.android.mms.transaction;
 
-import static android.provider.Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION;
+import static android.provider.Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
@@ -158,7 +158,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (intent.getAction().equals(WAP_PUSH_RECEIVED_ACTION)
+        if (intent.getAction().equals(WAP_PUSH_DELIVER_ACTION)
                 && ContentType.MMS_MESSAGE.equals(intent.getType())) {
             if (LOCAL_LOGV) {
                 Log.v(TAG, "Received PUSH Intent: " + intent);
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/SendTransaction.java b/src/com/android/mms/transaction/SendTransaction.java
index a94307f..5b4ba0a 100644
--- a/src/com/android/mms/transaction/SendTransaction.java
+++ b/src/com/android/mms/transaction/SendTransaction.java
@@ -58,7 +58,7 @@
     private static final String TAG = "SendTransaction";
 
     private Thread mThread;
-    private final Uri mSendReqURI;
+    public final Uri mSendReqURI;
 
     public SendTransaction(Context context,
             int transId, TransactionSettings connectionSettings, String uri) {
diff --git a/src/com/android/mms/transaction/SmsReceiver.java b/src/com/android/mms/transaction/SmsReceiver.java
index 22eb4e6..5999b17 100644
--- a/src/com/android/mms/transaction/SmsReceiver.java
+++ b/src/com/android/mms/transaction/SmsReceiver.java
@@ -49,7 +49,7 @@
         // no-permissions receiver class.  If we get an SMS_RECEIVED message that way, it
         // means someone has tried to spoof the message by delivering it outside the normal
         // permission-checked route, so we just ignore it.
-        if (!privileged && intent.getAction().equals(Intents.SMS_RECEIVED_ACTION)) {
+        if (!privileged && intent.getAction().equals(Intents.SMS_DELIVER_ACTION)) {
             return;
         }
 
diff --git a/src/com/android/mms/transaction/SmsReceiverService.java b/src/com/android/mms/transaction/SmsReceiverService.java
index 724e863..e1fe400 100755
--- a/src/com/android/mms/transaction/SmsReceiverService.java
+++ b/src/com/android/mms/transaction/SmsReceiverService.java
@@ -18,7 +18,7 @@
 package com.android.mms.transaction;
 
 import static android.content.Intent.ACTION_BOOT_COMPLETED;
-import static android.provider.Telephony.Sms.Intents.SMS_RECEIVED_ACTION;
+import static android.provider.Telephony.Sms.Intents.SMS_DELIVER_ACTION;
 
 import java.util.Calendar;
 import java.util.GregorianCalendar;
@@ -53,6 +53,7 @@
 
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.mms.LogTag;
+import com.android.mms.MmsConfig;
 import com.android.mms.R;
 import com.android.mms.data.Contact;
 import com.android.mms.data.Conversation;
@@ -81,7 +82,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[] {
@@ -190,7 +193,7 @@
             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                 Log.v(TAG, "handleMessage serviceId: " + serviceId + " intent: " + intent);
             }
-            if (intent != null) {
+            if (intent != null && MmsConfig.isSmsEnabled(getApplicationContext())) {
                 String action = intent.getAction();
 
                 int error = intent.getIntExtra("errorCode", 0);
@@ -201,7 +204,7 @@
 
                 if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
                     handleSmsSent(intent, error);
-                } else if (SMS_RECEIVED_ACTION.equals(action)) {
+                } else if (SMS_DELIVER_ACTION.equals(action)) {
                     handleSmsReceived(intent, error);
                 } else if (ACTION_BOOT_COMPLETED.equals(action)) {
                     handleBootCompleted();
@@ -209,6 +212,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 +236,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 +283,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 {
@@ -327,6 +344,7 @@
                 }
             });
         } else if (mResultCode == SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE) {
+            messageFailedToSend(uri, mResultCode);
             mToastHandler.post(new Runnable() {
                 public void run() {
                     Toast.makeText(SmsReceiverService.this, getString(R.string.fdn_check_failure),
@@ -388,6 +406,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/Transaction.java b/src/com/android/mms/transaction/Transaction.java
index adc1722..10ebd3e 100644
--- a/src/com/android/mms/transaction/Transaction.java
+++ b/src/com/android/mms/transaction/Transaction.java
@@ -89,8 +89,7 @@
      * @return true if transaction is equivalent to this instance, false otherwise.
      */
     public boolean isEquivalent(Transaction transaction) {
-        return getClass().equals(transaction.getClass())
-                && mId.equals(transaction.mId);
+        return mId.equals(transaction.mId);
     }
 
     /**
@@ -211,57 +210,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 7fd2715..a02f34f 100644
--- a/src/com/android/mms/transaction/TransactionService.java
+++ b/src/com/android/mms/transaction/TransactionService.java
@@ -23,10 +23,12 @@
 import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.ContentUris;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
@@ -38,6 +40,7 @@
 import android.os.PowerManager;
 import android.provider.Telephony.Mms;
 import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Mms.Sent;
 import android.provider.Telephony.MmsSms.PendingMessages;
 import android.text.TextUtils;
 import android.util.Log;
@@ -46,7 +49,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.mms.LogTag;
+import com.android.mms.MmsConfig;
 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;
@@ -127,6 +132,7 @@
 
     private static final int TOAST_MSG_QUEUED = 1;
     private static final int TOAST_DOWNLOAD_LATER = 2;
+    private static final int TOAST_NO_APN = 3;
     private static final int TOAST_NONE = -1;
 
     // How often to extend the use of the MMS APN while a transaction
@@ -151,6 +157,8 @@
                 str = getString(R.string.message_queued);
             } else if (msg.what == TOAST_DOWNLOAD_LATER) {
                 str = getString(R.string.download_later);
+            } else if (msg.what == TOAST_NO_APN) {
+                str = getString(R.string.no_apn);
             }
 
             if (str != null) {
@@ -194,7 +202,14 @@
 
     public void onNewIntent(Intent intent, int serviceId) {
         mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-        boolean noNetwork = !isNetworkAvailable();
+        if (mConnMgr == null || !mConnMgr.getMobileDataEnabled()
+                || !MmsConfig.isSmsEnabled(getApplicationContext())) {
+            endMmsConnectivity();
+            stopSelf(serviceId);
+            return;
+        }
+        NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+        boolean noNetwork = ni == null || !ni.isAvailable();
 
         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
             Log.v(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() +
@@ -213,7 +228,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) {
@@ -229,18 +244,13 @@
                     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);
+                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                            Log.v(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +
+                                    transactionType);
+                        }
                         if (noNetwork) {
                             onNetworkUnavailable(serviceId, transactionType);
                             return;
@@ -256,12 +266,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,
@@ -269,6 +303,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;
                         }
@@ -299,11 +336,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);
             }
@@ -311,12 +343,7 @@
     }
 
     private static boolean isTransientFailure(int type) {
-        return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR);
-    }
-
-    private boolean isNetworkAvailable() {
-        NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
-        return (ni == null ? false : ni.isAvailable());
+        return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
     }
 
     private int getTransactionType(int msgType) {
@@ -410,11 +437,15 @@
                             transaction.getConnectionSettings());
                     mServiceHandler.sendMessage(msg);
                 }
-                else {
+                else if (mProcessing.isEmpty()) {
                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                         Log.v(TAG, "update: endMmsConnectivity");
                     }
                     endMmsConnectivity();
+                } else {
+                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+                        Log.v(TAG, "update: mProcessing is not empty");
+                    }
                 }
             }
 
@@ -469,8 +500,7 @@
             sendBroadcast(intent);
         } finally {
             transaction.detach(this);
-            MmsSystemEventReceiver.unRegisterForConnectionStateChanges(getApplicationContext());
-            stopSelf(serviceId);
+            stopSelfIfIdle(serviceId);
         }
     }
 
@@ -486,17 +516,22 @@
     private void acquireWakeLock() {
         // It's okay to double-acquire this because we are not using it
         // in reference-counted mode.
+        Log.v(TAG, "mms acquireWakeLock");
         mWakeLock.acquire();
     }
 
     private void releaseWakeLock() {
         // Don't release the wake lock if it hasn't been created and acquired.
         if (mWakeLock != null && mWakeLock.isHeld()) {
+            Log.v(TAG, "mms releaseWakeLock");
             mWakeLock.release();
         }
     }
 
     protected int beginMmsConnectivity() throws IOException {
+        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
+            Log.v(TAG, "beginMmsConnectivity");
+        }
         // Take a wake lock so we don't fall asleep before the message is downloaded.
         createWakeLock();
 
@@ -741,6 +776,27 @@
             }
         }
 
+        public void markAllPendingTransactionsAsFailed() {
+            synchronized (mProcessing) {
+                while (mPending.size() != 0) {
+                    Transaction transaction = mPending.remove(0);
+                    transaction.mTransactionState.setState(TransactionState.FAILED);
+                    if (transaction instanceof SendTransaction) {
+                        Uri uri = ((SendTransaction)transaction).mSendReqURI;
+                        transaction.mTransactionState.setContentUri(uri);
+                        int respStatus = PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM;
+                        ContentValues values = new ContentValues(1);
+                        values.put(Mms.RESPONSE_STATUS, respStatus);
+
+                        SqliteWrapper.update(TransactionService.this,
+                                TransactionService.this.getContentResolver(),
+                                uri, values, null, null);
+                    }
+                    transaction.notifyObservers();
+                }
+            }
+        }
+
         public void processPendingTransaction(Transaction transaction,
                                                TransactionSettings settings) {
 
@@ -881,11 +937,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 && mConnMgr.getMobileDataEnabled()) {
+                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 +954,53 @@
              */
 
             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 or mobile data is turned off, 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, mark everything as failed and bail.
+                    if (TextUtils.isEmpty(settings.getMmscUrl())) {
+                        Log.v(TAG, "   empty MMSC url, bail");
+                        mToastHandler.sendEmptyMessage(TOAST_NO_APN);
+                        mServiceHandler.markAllPendingTransactionsAsFailed();
+                        endMmsConnectivity();
+                        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/transaction/TransactionSettings.java b/src/com/android/mms/transaction/TransactionSettings.java
index 728f03a..d8df9a5 100644
--- a/src/com/android/mms/transaction/TransactionSettings.java
+++ b/src/com/android/mms/transaction/TransactionSettings.java
@@ -89,8 +89,13 @@
                 // Read values from APN settings
                 if (isValidApnType(cursor.getString(COLUMN_TYPE), PhoneConstants.APN_TYPE_MMS)) {
                     sawValidApn = true;
-                    mServiceCenter = NetworkUtils.trimV4AddrZeros(
-                            cursor.getString(COLUMN_MMSC).trim());
+
+                    String mmsc = cursor.getString(COLUMN_MMSC);
+                    if (mmsc == null) {
+                        continue;
+                    }
+
+                    mServiceCenter = NetworkUtils.trimV4AddrZeros(mmsc.trim());
                     mProxyAddress = NetworkUtils.trimV4AddrZeros(
                             cursor.getString(COLUMN_MMSPROXY));
                     if (isProxySet()) {
diff --git a/src/com/android/mms/ui/ClassZeroActivity.java b/src/com/android/mms/ui/ClassZeroActivity.java
index 0abcdda..90d155d 100644
--- a/src/com/android/mms/ui/ClassZeroActivity.java
+++ b/src/com/android/mms/ui/ClassZeroActivity.java
@@ -24,6 +24,7 @@
 import android.content.ContentValues;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
@@ -41,6 +42,8 @@
 import com.android.mms.R;
 import com.android.mms.transaction.MessagingNotification;
 
+import java.util.ArrayList;
+
 /**
  * Display a class-zero SMS message to the user. Wait for the user to dismiss
  * it.
@@ -69,6 +72,8 @@
     private long mTimerSet = 0;
     private AlertDialog mDialog = null;
 
+    private ArrayList<SmsMessage> mMessageQueue = null;
+
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -77,11 +82,35 @@
                 mRead = false;
                 mDialog.dismiss();
                 saveMessage();
-                finish();
+                processNextMessage();
             }
         }
     };
 
+    private boolean queueMsgFromIntent(Intent msgIntent) {
+        byte[] pdu = msgIntent.getByteArrayExtra("pdu");
+        String format = msgIntent.getStringExtra("format");
+        SmsMessage rawMessage = SmsMessage.createFromPdu(pdu, format);
+        String message = rawMessage.getMessageBody();
+        if (TextUtils.isEmpty(message)) {
+            if (mMessageQueue.size() == 0) {
+                finish();
+            }
+            return false;
+        }
+        mMessageQueue.add(rawMessage);
+        return true;
+    }
+
+    private void processNextMessage() {
+        mMessageQueue.remove(0);
+        if (mMessageQueue.size() == 0) {
+            finish();
+        } else {
+            displayZeroMessage(mMessageQueue.get(0));
+        }
+    }
+
     private void saveMessage() {
         Uri messageUri = null;
         if (mMessage.isReplace()) {
@@ -98,35 +127,53 @@
     }
 
     @Override
+    protected void onNewIntent(Intent msgIntent) {
+        /* Running with another visible message, queue this one */
+        queueMsgFromIntent(msgIntent);
+    }
+
+    @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         getWindow().setBackgroundDrawableResource(
                 R.drawable.class_zero_background);
 
-        byte[] pdu = getIntent().getByteArrayExtra("pdu");
-        String format = getIntent().getStringExtra("format");
-        mMessage = SmsMessage.createFromPdu(pdu, format);
-        CharSequence messageChars = mMessage.getMessageBody();
-        String message = messageChars.toString();
-        if (TextUtils.isEmpty(message)) {
-            finish();
+        if (mMessageQueue == null) {
+            mMessageQueue = new ArrayList<SmsMessage>();
+        }
+
+        if (!queueMsgFromIntent(getIntent())) {
             return;
         }
+
+        if (mMessageQueue.size() == 1) {
+            displayZeroMessage(mMessageQueue.get(0));
+        }
+
+        if (icicle != null) {
+            mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
+        }
+    }
+
+    private void displayZeroMessage(SmsMessage rawMessage) {
+        CharSequence messageChars = rawMessage.getMessageBody();
+        /* This'll be used by the save action */
+        mMessage = rawMessage;
+        String message = messageChars.toString();
+
         // TODO: The following line adds an emptry string before and after a message.
         // This is not the correct way to layout a message. This is more of a hack
         // to work-around a bug in AlertDialog. This needs to be fixed later when
         // Android fixes the bug in AlertDialog.
         if (message.length() < BUFFER_OFFSET) messageChars = BUFFER + message + BUFFER;
-        long now = SystemClock.uptimeMillis();
-        mDialog = new AlertDialog.Builder(this).setMessage(messageChars)
+
+        mDialog = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK).setMessage(message)
                 .setPositiveButton(R.string.save, mSaveListener)
                 .setNegativeButton(android.R.string.cancel, mCancelListener)
                 .setCancelable(false).show();
+        long now = SystemClock.uptimeMillis();
         mTimerSet = now + DEFAULT_TIMER;
-        if (icicle != null) {
-            mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
-        }
     }
 
     @Override
@@ -168,7 +215,7 @@
     private final OnClickListener mCancelListener = new OnClickListener() {
         public void onClick(DialogInterface dialog, int whichButton) {
             dialog.dismiss();
-            finish();
+            processNextMessage();
         }
     };
 
@@ -177,7 +224,7 @@
             mRead = true;
             saveMessage();
             dialog.dismiss();
-            finish();
+            processNextMessage();
         }
     };
 
diff --git a/src/com/android/mms/ui/ComposeMessageActivity.java b/src/com/android/mms/ui/ComposeMessageActivity.java
index 42bda28..eaa5671 100644
--- a/src/com/android/mms/ui/ComposeMessageActivity.java
+++ b/src/com/android/mms/ui/ComposeMessageActivity.java
@@ -141,7 +141,6 @@
 import com.android.mms.util.DraftCache;
 import com.android.mms.util.PhoneNumberFormatter;
 import com.android.mms.util.SendingProgressTokenManager;
-import com.android.mms.util.SmileyParser;
 import com.android.mms.widget.MmsWidgetProvider;
 import com.google.android.mms.ContentType;
 import com.google.android.mms.MmsException;
@@ -209,7 +208,6 @@
     private static final int MENU_SEND_EMAIL            = 23;
     private static final int MENU_COPY_MESSAGE_TEXT     = 24;
     private static final int MENU_COPY_TO_SDCARD        = 25;
-    private static final int MENU_INSERT_SMILEY         = 26;
     private static final int MENU_ADD_ADDRESS_TO_CONTACTS = 27;
     private static final int MENU_LOCK_MESSAGE          = 28;
     private static final int MENU_UNLOCK_MESSAGE        = 29;
@@ -228,6 +226,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 +257,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.
@@ -293,8 +296,6 @@
 
     private WorkingMessage mWorkingMessage;         // The message currently being composed.
 
-    private AlertDialog mSmileyDialog;
-
     private boolean mWaitingForSubActivity;
     private int mLastRecipientCount;            // Used for warning the user on too many recipients.
     private AttachmentTypeSelectorAdapter mAttachmentTypeSelectorAdapter;
@@ -333,6 +334,10 @@
     // we should not load the draft.
     private boolean mShouldLoadDraft;
 
+    // Whether or not we are currently enabled for SMS. This field is updated in onStart to make
+    // sure we notice if the user has changed the default SMS app.
+    private boolean mIsSmsEnabled;
+
     private Handler mHandler = new Handler();
 
     // keys for extras and icicles
@@ -1073,7 +1078,8 @@
             addCallAndContactMenuItems(menu, l, msgItem);
 
             // Forward is not available for undownloaded messages.
-            if (msgItem.isDownloaded() && (msgItem.isSms() || isForwardable(msgId))) {
+            if (msgItem.isDownloaded() && (msgItem.isSms() || isForwardable(msgId))
+                    && mIsSmsEnabled) {
                 menu.add(0, MENU_FORWARD_MESSAGE, 0, R.string.menu_forward)
                         .setOnMenuItemClickListener(l);
             }
@@ -1119,10 +1125,10 @@
                 }
             }
 
-            if (msgItem.mLocked) {
+            if (msgItem.mLocked && mIsSmsEnabled) {
                 menu.add(0, MENU_UNLOCK_MESSAGE, 0, R.string.menu_unlock)
                     .setOnMenuItemClickListener(l);
-            } else {
+            } else if (mIsSmsEnabled) {
                 menu.add(0, MENU_LOCK_MESSAGE, 0, R.string.menu_lock)
                     .setOnMenuItemClickListener(l);
             }
@@ -1135,8 +1141,10 @@
                         .setOnMenuItemClickListener(l);
             }
 
-            menu.add(0, MENU_DELETE_MESSAGE, 0, R.string.delete_message)
-                .setOnMenuItemClickListener(l);
+            if (mIsSmsEnabled) {
+                menu.add(0, MENU_DELETE_MESSAGE, 0, R.string.delete_message)
+                    .setOnMenuItemClickListener(l);
+            }
         }
     };
 
@@ -1250,8 +1258,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 +1619,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.
@@ -1852,6 +1863,7 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        mIsSmsEnabled = MmsConfig.isSmsEnabled(this);
         super.onCreate(savedInstanceState);
 
         resetConfiguration(getResources().getConfiguration());
@@ -1967,7 +1979,7 @@
             drawBottomPanel();
         }
 
-        onKeyboardStateChanged(mIsKeyboardOpen);
+        onKeyboardStateChanged();
 
         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
             log("update title, mConversation=" + mConversation.toString());
@@ -2090,7 +2102,7 @@
                 mWorkingMessage.unDiscard();    // it was discarded in onStop().
 
                 sanityCheckConversation();
-            } else if (isRecipientsEditorVisible()) {
+            } else if (isRecipientsEditorVisible() && recipientCount() > 0) {
                 if (LogTag.VERBOSE) {
                     log("onRestart: goToConversationList");
                 }
@@ -2102,6 +2114,11 @@
     @Override
     protected void onStart() {
         super.onStart();
+        boolean isSmsEnabled = MmsConfig.isSmsEnabled(this);
+        if (isSmsEnabled != mIsSmsEnabled) {
+            mIsSmsEnabled = isSmsEnabled;
+            invalidateOptionsMenu();
+        }
 
         initFocus();
 
@@ -2175,7 +2192,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 +2234,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);
         }
     }
 
@@ -2350,7 +2370,7 @@
             Log.v(TAG, "CMA.onConfigurationChanged: " + newConfig +
                     ", mIsKeyboardOpen=" + mIsKeyboardOpen);
         }
-        onKeyboardStateChanged(mIsKeyboardOpen);
+        onKeyboardStateChanged();
     }
 
     // returns true if landscape/portrait configuration has changed
@@ -2364,10 +2384,20 @@
         return false;
     }
 
-    private void onKeyboardStateChanged(boolean isKeyboardOpen) {
+    private void onKeyboardStateChanged() {
         // If the keyboard is hidden, don't show focus highlights for
         // things that cannot receive input.
-        if (isKeyboardOpen) {
+        mTextEditor.setEnabled(mIsSmsEnabled);
+        if (!mIsSmsEnabled) {
+            if (mRecipientsEditor != null) {
+                mRecipientsEditor.setFocusableInTouchMode(false);
+            }
+            if (mSubjectTextEditor != null) {
+                mSubjectTextEditor.setFocusableInTouchMode(false);
+            }
+            mTextEditor.setFocusableInTouchMode(false);
+            mTextEditor.setHint(R.string.sending_disabled_not_default_app);
+        } else if (mIsKeyboardOpen) {
             if (mRecipientsEditor != null) {
                 mRecipientsEditor.setFocusableInTouchMode(true);
             }
@@ -2610,6 +2640,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)
@@ -2620,7 +2656,7 @@
             }
         }
 
-        if (MmsConfig.getMmsEnabled()) {
+        if (MmsConfig.getMmsEnabled() && mIsSmsEnabled) {
             if (!isSubjectEditorVisible()) {
                 menu.add(0, MENU_ADD_SUBJECT, 0, R.string.add_subject).setIcon(
                         R.drawable.ic_menu_edit);
@@ -2633,20 +2669,15 @@
             }
         }
 
-        if (isPreparedForSending()) {
+        if (isPreparedForSending() && mIsSmsEnabled) {
             menu.add(0, MENU_SEND, 0, R.string.send).setIcon(android.R.drawable.ic_menu_send);
         }
 
-        if (!mWorkingMessage.hasSlideshow()) {
-            menu.add(0, MENU_INSERT_SMILEY, 0, R.string.menu_insert_smiley).setIcon(
-                    R.drawable.ic_menu_emoticons);
-        }
-
         if (getRecipients().size() > 1) {
             menu.add(0, MENU_GROUP_PARTICIPANTS, 0, R.string.menu_group_participants);
         }
 
-        if (mMsgListAdapter.getCount() > 0) {
+        if (mMsgListAdapter.getCount() > 0 && mIsSmsEnabled) {
             // Removed search as part of b/1205708
             //menu.add(0, MENU_SEARCH, 0, R.string.menu_search).setIcon(
             //        R.drawable.ic_menu_search);
@@ -2655,7 +2686,7 @@
                 menu.add(0, MENU_DELETE_THREAD, 0, R.string.delete_thread).setIcon(
                     android.R.drawable.ic_menu_delete);
             }
-        } else {
+        } else if (mIsSmsEnabled) {
             menu.add(0, MENU_DISCARD, 0, R.string.discard).setIcon(android.R.drawable.ic_menu_delete);
         }
 
@@ -2732,9 +2763,6 @@
             case MENU_CALL_RECIPIENT:
                 dialRecipient();
                 break;
-            case MENU_INSERT_SMILEY:
-                showSmileyDialog();
-                break;
             case MENU_GROUP_PARTICIPANTS:
             {
                 Intent intent = new Intent(this, RecipientListActivity.class);
@@ -3194,7 +3222,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;
         }
 
@@ -3322,7 +3350,7 @@
         CharSequence text = mWorkingMessage.getText();
 
         // TextView.setTextKeepState() doesn't like null input.
-        if (text != null) {
+        if (text != null && mIsSmsEnabled) {
             mTextEditor.setTextKeepState(text);
 
             // Set the edit caret to the end of the text.
@@ -3330,6 +3358,7 @@
         } else {
             mTextEditor.setText("");
         }
+        onKeyboardStateChanged();
     }
 
     private void hideBottomPanel() {
@@ -3345,6 +3374,7 @@
         showSubjectEditor(showSubjectEditor || mWorkingMessage.hasSubject());
 
         invalidateOptionsMenu();
+        onKeyboardStateChanged();
     }
 
     //==========================================================
@@ -3543,6 +3573,9 @@
     }
 
     private void startMsgListQuery(int token) {
+        if (mSendDiscreetMode) {
+            return;
+        }
         Uri conversationUri = mConversation.getUri();
 
         if (conversationUri == null) {
@@ -3587,7 +3620,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
@@ -3664,9 +3697,10 @@
     private boolean isPreparedForSending() {
         int recipientCount = recipientCount();
 
-        return recipientCount > 0 && recipientCount <= MmsConfig.getRecipientLimit() &&
-            (mWorkingMessage.hasAttachment() ||
-                    mWorkingMessage.hasText() ||
+        return recipientCount > 0 &&
+                recipientCount <= MmsConfig.getRecipientLimit() &&
+                mIsSmsEnabled &&
+                (mWorkingMessage.hasAttachment() || mWorkingMessage.hasText() ||
                     mWorkingMessage.hasSubject());
     }
 
@@ -3731,7 +3765,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 +3872,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 +3911,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"));
         }
@@ -3921,31 +3964,34 @@
      * @param listSizeChange the amount the message list view size has vertically changed
      */
     private void smoothScrollToEnd(boolean force, int listSizeChange) {
-        int last = mMsgListView.getLastVisiblePosition();
-        int newPosition = mMsgListAdapter.getCount() - 1;
-        if (last < 0 || newPosition < 0) {
+        int lastItemVisible = mMsgListView.getLastVisiblePosition();
+        int lastItemInList = mMsgListAdapter.getCount() - 1;
+        if (lastItemVisible < 0 || lastItemInList < 0) {
             if (LogTag.VERBOSE || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                Log.v(TAG, "smoothScrollToEnd: last=" + last + ", newPos=" + newPosition +
+                Log.v(TAG, "smoothScrollToEnd: lastItemVisible=" + lastItemVisible +
+                        ", lastItemInList=" + lastItemInList +
                         ", mMsgListView not ready");
             }
             return;
         }
 
-        View lastChild = mMsgListView.getChildAt(last - mMsgListView.getFirstVisiblePosition());
-        int bottom = 0;
-        int height = 0;
-        if (lastChild != null) {
-            bottom = lastChild.getBottom();
-            height = lastChild.getHeight();
+        View lastChildVisible =
+                mMsgListView.getChildAt(lastItemVisible - mMsgListView.getFirstVisiblePosition());
+        int lastVisibleItemBottom = 0;
+        int lastVisibleItemHeight = 0;
+        if (lastChildVisible != null) {
+            lastVisibleItemBottom = lastChildVisible.getBottom();
+            lastVisibleItemHeight = lastChildVisible.getHeight();
         }
 
         if (LogTag.VERBOSE || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-            Log.v(TAG, "smoothScrollToEnd newPosition: " + newPosition +
+            Log.v(TAG, "smoothScrollToEnd newPosition: " + lastItemInList +
                     " mLastSmoothScrollPosition: " + mLastSmoothScrollPosition +
                     " first: " + mMsgListView.getFirstVisiblePosition() +
-                    " last: " + last +
-                    " bottom: " + bottom +
-                    " bottom + listSizeChange: " + (bottom + listSizeChange) +
+                    " lastItemVisible: " + lastItemVisible +
+                    " lastVisibleItemBottom: " + lastVisibleItemBottom +
+                    " lastVisibleItemBottom + listSizeChange: " +
+                    (lastVisibleItemBottom + listSizeChange) +
                     " mMsgListView.getHeight() - mMsgListView.getPaddingBottom(): " +
                     (mMsgListView.getHeight() - mMsgListView.getPaddingBottom()) +
                     " listSizeChange: " + listSizeChange);
@@ -3964,45 +4010,50 @@
         // attachment thumbnail, such as picture. In this situation, we want to scroll the list so
         // the bottom of the thumbnail is visible and the top of the item is scroll off the screen.
         int listHeight = mMsgListView.getHeight();
-        if (force || ((listSizeChange != 0 || newPosition != mLastSmoothScrollPosition) &&
-                bottom + listSizeChange <=
-                        listHeight - mMsgListView.getPaddingBottom()) ||
-                        height > listHeight) {
+        boolean lastItemTooTall = lastVisibleItemHeight > listHeight;
+        boolean willScroll = force ||
+                ((listSizeChange != 0 || lastItemInList != mLastSmoothScrollPosition) &&
+                lastVisibleItemBottom + listSizeChange <=
+                    listHeight - mMsgListView.getPaddingBottom());
+        if (willScroll || (lastItemTooTall && lastItemInList == lastItemVisible)) {
             if (Math.abs(listSizeChange) > SMOOTH_SCROLL_THRESHOLD) {
                 // When the keyboard comes up, the window manager initiates a cross fade
                 // animation that conflicts with smooth scroll. Handle that case by jumping the
                 // list directly to the end.
                 if (LogTag.VERBOSE || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                    Log.v(TAG, "keyboard state changed. setSelection=" + newPosition);
+                    Log.v(TAG, "keyboard state changed. setSelection=" + lastItemInList);
                 }
-                if (height > listHeight) {
+                if (lastItemTooTall) {
                     // If the height of the last item is taller than the whole height of the list,
                     // we need to scroll that item so that its top is negative or above the top of
                     // the list. That way, the bottom of the last item will be exposed above the
                     // keyboard.
-                    mMsgListView.setSelectionFromTop(newPosition, listHeight - height);
+                    mMsgListView.setSelectionFromTop(lastItemInList,
+                            listHeight - lastVisibleItemHeight);
                 } else {
-                    mMsgListView.setSelection(newPosition);
+                    mMsgListView.setSelection(lastItemInList);
                 }
-            } else if (newPosition - last > MAX_ITEMS_TO_INVOKE_SCROLL_SHORTCUT) {
+            } else if (lastItemInList - lastItemVisible > MAX_ITEMS_TO_INVOKE_SCROLL_SHORTCUT) {
                 if (LogTag.VERBOSE || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                    Log.v(TAG, "too many to scroll, setSelection=" + newPosition);
+                    Log.v(TAG, "too many to scroll, setSelection=" + lastItemInList);
                 }
-                mMsgListView.setSelection(newPosition);
+                mMsgListView.setSelection(lastItemInList);
             } else {
                 if (LogTag.VERBOSE || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                    Log.v(TAG, "smooth scroll to " + newPosition);
+                    Log.v(TAG, "smooth scroll to " + lastItemInList);
                 }
-                if (height > listHeight) {
+                if (lastItemTooTall) {
                     // If the height of the last item is taller than the whole height of the list,
                     // we need to scroll that item so that its top is negative or above the top of
                     // the list. That way, the bottom of the last item will be exposed above the
-                    // keyboard.
-                    mMsgListView.setSelectionFromTop(newPosition, listHeight - height);
+                    // keyboard. We should use smoothScrollToPositionFromTop here, but it doesn't
+                    // seem to work -- the list ends up scrolling to a random position.
+                    mMsgListView.setSelectionFromTop(lastItemInList,
+                            listHeight - lastVisibleItemHeight);
                 } else {
-                    mMsgListView.smoothScrollToPosition(newPosition);
+                    mMsgListView.smoothScrollToPosition(lastItemInList);
                 }
-                mLastSmoothScrollPosition = newPosition;
+                mLastSmoothScrollPosition = lastItemInList;
             }
         }
     }
@@ -4041,12 +4092,14 @@
                     int newSelectionPos = -1;
                     long targetMsgId = getIntent().getLongExtra("select_id", -1);
                     if (targetMsgId != -1) {
-                        cursor.moveToPosition(-1);
-                        while (cursor.moveToNext()) {
-                            long msgId = cursor.getLong(COLUMN_ID);
-                            if (msgId == targetMsgId) {
-                                newSelectionPos = cursor.getPosition();
-                                break;
+                        if (cursor != null) {
+                            cursor.moveToPosition(-1);
+                            while (cursor.moveToNext()) {
+                                long msgId = cursor.getLong(COLUMN_ID);
+                                if (msgId == targetMsgId) {
+                                    newSelectionPos = cursor.getPosition();
+                                    break;
+                                }
                             }
                         }
                     } else if (mSavedScrollPosition != -1) {
@@ -4078,7 +4131,7 @@
                     } else {
                         int count = mMsgListAdapter.getCount();
                         long lastMsgId = 0;
-                        if (count > 0) {
+                        if (cursor != null && count > 0) {
                             cursor.moveToLast();
                             lastMsgId = cursor.getLong(COLUMN_ID);
                         }
@@ -4102,7 +4155,8 @@
                     // mSentMessage is true).
                     // Show the recipients editor to give the user a chance to add
                     // more people before the conversation begins.
-                    if (cursor.getCount() == 0 && !isRecipientsEditorVisible() && !mSentMessage) {
+                    if (cursor != null && cursor.getCount() == 0
+                            && !isRecipientsEditorVisible() && !mSentMessage) {
                         initRecipientsEditor();
                     }
 
@@ -4212,85 +4266,6 @@
         }
     }
 
-    private void showSmileyDialog() {
-        if (mSmileyDialog == null) {
-            int[] icons = SmileyParser.DEFAULT_SMILEY_RES_IDS;
-            String[] names = getResources().getStringArray(
-                    SmileyParser.DEFAULT_SMILEY_NAMES);
-            final String[] texts = getResources().getStringArray(
-                    SmileyParser.DEFAULT_SMILEY_TEXTS);
-
-            final int N = names.length;
-
-            List<Map<String, ?>> entries = new ArrayList<Map<String, ?>>();
-            for (int i = 0; i < N; i++) {
-                // We might have different ASCII for the same icon, skip it if
-                // the icon is already added.
-                boolean added = false;
-                for (int j = 0; j < i; j++) {
-                    if (icons[i] == icons[j]) {
-                        added = true;
-                        break;
-                    }
-                }
-                if (!added) {
-                    HashMap<String, Object> entry = new HashMap<String, Object>();
-
-                    entry. put("icon", icons[i]);
-                    entry. put("name", names[i]);
-                    entry.put("text", texts[i]);
-
-                    entries.add(entry);
-                }
-            }
-
-            final SimpleAdapter a = new SimpleAdapter(
-                    this,
-                    entries,
-                    R.layout.smiley_menu_item,
-                    new String[] {"icon", "name", "text"},
-                    new int[] {R.id.smiley_icon, R.id.smiley_name, R.id.smiley_text});
-            SimpleAdapter.ViewBinder viewBinder = new SimpleAdapter.ViewBinder() {
-                @Override
-                public boolean setViewValue(View view, Object data, String textRepresentation) {
-                    if (view instanceof ImageView) {
-                        Drawable img = getResources().getDrawable((Integer)data);
-                        ((ImageView)view).setImageDrawable(img);
-                        return true;
-                    }
-                    return false;
-                }
-            };
-            a.setViewBinder(viewBinder);
-
-            AlertDialog.Builder b = new AlertDialog.Builder(this);
-
-            b.setTitle(getString(R.string.menu_insert_smiley));
-
-            b.setCancelable(true);
-            b.setAdapter(a, new DialogInterface.OnClickListener() {
-                @Override
-                @SuppressWarnings("unchecked")
-                public final void onClick(DialogInterface dialog, int which) {
-                    HashMap<String, Object> item = (HashMap<String, Object>) a.getItem(which);
-
-                    String smiley = (String)item.get("text");
-                    if (mSubjectTextEditor != null && mSubjectTextEditor.hasFocus()) {
-                        mSubjectTextEditor.append(smiley);
-                    } else {
-                        mTextEditor.append(smiley);
-                    }
-
-                    dialog.dismiss();
-                }
-            });
-
-            mSmileyDialog = b.create();
-        }
-
-        mSmileyDialog.show();
-    }
-
     @Override
     public void onUpdate(final Contact updated) {
         // Using an existing handler for the post, rather than conjuring up a new one.
diff --git a/src/com/android/mms/ui/ConversationList.java b/src/com/android/mms/ui/ConversationList.java
index 4d5ca6b..afbc456 100644
--- a/src/com/android/mms/ui/ConversationList.java
+++ b/src/com/android/mms/ui/ConversationList.java
@@ -17,10 +17,6 @@
 
 package com.android.mms.ui;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-
 import android.app.ActionBar;
 import android.app.AlertDialog;
 import android.app.ListActivity;
@@ -35,16 +31,20 @@
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SqliteWrapper;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
+import android.provider.Telephony;
 import android.provider.Telephony.Mms;
 import android.provider.Telephony.Threads;
 import android.util.Log;
@@ -63,11 +63,14 @@
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.CheckBox;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.SearchView;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.android.mms.LogTag;
+import com.android.mms.MmsConfig;
 import com.android.mms.R;
 import com.android.mms.data.Contact;
 import com.android.mms.data.ContactList;
@@ -80,6 +83,10 @@
 import com.android.mms.widget.MmsWidgetProvider;
 import com.google.android.mms.pdu.PduHeaders;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
 /**
  * This activity provides a list view of existing conversations.
  */
@@ -87,7 +94,6 @@
     private static final String TAG = "ConversationList";
     private static final boolean DEBUG = false;
     private static final boolean DEBUGCLEANUP = true;
-    private static final boolean LOCAL_LOGV = DEBUG;
 
     private static final int THREAD_LIST_QUERY_TOKEN       = 1701;
     private static final int UNREAD_THREADS_QUERY_TOKEN    = 1702;
@@ -109,15 +115,29 @@
     private TextView mUnreadConvCount;
     private MenuItem mSearchItem;
     private SearchView mSearchView;
+    private View mSmsPromoBannerView;
+    private int mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+    private int mSavedFirstItemOffset;
+
+    // keys for extras and icicles
+    private final static String LAST_LIST_POS = "last_list_pos";
+    private final static String LAST_LIST_OFFSET = "last_list_offset";
 
     static private final String CHECKED_MESSAGE_LIMITS = "checked_message_limits";
 
+    // Whether or not we are currently enabled for SMS. This field is updated in onResume to make
+    // sure we notice if the user has changed the default SMS app.
+    private boolean mIsSmsEnabled;
+    private Toast mComposeDisabledToast;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.conversation_list_screen);
 
+        mSmsPromoBannerView = findViewById(R.id.banner_sms_promo);
+
         mQueryHandler = new ThreadListQueryHandler(getContentResolver());
 
         ListView listView = getListView();
@@ -139,9 +159,70 @@
         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
         boolean checkedMessageLimits = mPrefs.getBoolean(CHECKED_MESSAGE_LIMITS, false);
         if (DEBUG) Log.v(TAG, "checkedMessageLimits: " + checkedMessageLimits);
-        if (!checkedMessageLimits || DEBUG) {
+        if (!checkedMessageLimits) {
             runOneTimeStorageLimitCheckForLegacyMessages();
         }
+
+        if (savedInstanceState != null) {
+            mSavedFirstVisiblePosition = savedInstanceState.getInt(LAST_LIST_POS,
+                    AdapterView.INVALID_POSITION);
+            mSavedFirstItemOffset = savedInstanceState.getInt(LAST_LIST_OFFSET, 0);
+        } else {
+            mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+            mSavedFirstItemOffset = 0;
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(LAST_LIST_POS, mSavedFirstVisiblePosition);
+        outState.putInt(LAST_LIST_OFFSET, mSavedFirstItemOffset);
+    }
+
+    @Override
+    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.
+        ListView listView = getListView();
+        mSavedFirstVisiblePosition = listView.getFirstVisiblePosition();
+        View firstChild = listView.getChildAt(0);
+        mSavedFirstItemOffset = (firstChild == null) ? 0 : firstChild.getTop();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        boolean isSmsEnabled = MmsConfig.isSmsEnabled(this);
+        if (isSmsEnabled != mIsSmsEnabled) {
+            mIsSmsEnabled = isSmsEnabled;
+            invalidateOptionsMenu();
+        }
+
+        // Multi-select is used to delete conversations. It is disabled if we are not the sms app.
+        ListView listView = getListView();
+        if (mIsSmsEnabled) {
+            listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+        } else {
+            listView.setChoiceMode(ListView.CHOICE_MODE_NONE);
+        }
+
+        // Show or hide the SMS promo banner
+        if (mIsSmsEnabled || MmsConfig.isSmsPromoDismissed(this)) {
+            mSmsPromoBannerView.setVisibility(View.GONE);
+        } else {
+            initSmsPromoBanner();
+            mSmsPromoBannerView.setVisibility(View.VISIBLE);
+        }
+
+        mListAdapter.setOnContentChangedListener(mContentChangedListener);
     }
 
     private void setupActionBar() {
@@ -174,6 +255,54 @@
         getListView().setRecyclerListener(mListAdapter);
     }
 
+    private void initSmsPromoBanner() {
+        final PackageManager packageManager = getPackageManager();
+        final String smsAppPackage = Telephony.Sms.getDefaultSmsPackage(this);
+
+        // Get all the data we need about the default app to properly render the promo banner. We
+        // try to show the icon and name of the user's selected SMS app and have the banner link
+        // to that app. If we can't read that information for any reason we leave the fallback
+        // text that links to Messaging settings where the user can change the default.
+        Drawable smsAppIcon = null;
+        ApplicationInfo smsAppInfo = null;
+        try {
+            smsAppIcon = packageManager.getApplicationIcon(smsAppPackage);
+            smsAppInfo = packageManager.getApplicationInfo(smsAppPackage, 0);
+        } catch (NameNotFoundException e) {
+        }
+        final Intent smsAppIntent = packageManager.getLaunchIntentForPackage(smsAppPackage);
+
+        // If we got all the info we needed
+        if (smsAppIcon != null && smsAppInfo != null && smsAppIntent != null) {
+            ImageView defaultSmsAppIconImageView =
+                    (ImageView)mSmsPromoBannerView.findViewById(R.id.banner_sms_default_app_icon);
+            defaultSmsAppIconImageView.setImageDrawable(smsAppIcon);
+            TextView smsPromoBannerTitle =
+                    (TextView)mSmsPromoBannerView.findViewById(R.id.banner_sms_promo_title);
+            String message = getResources().getString(R.string.banner_sms_promo_title_application,
+                    smsAppInfo.loadLabel(packageManager));
+            smsPromoBannerTitle.setText(message);
+
+            mSmsPromoBannerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startActivity(smsAppIntent);
+                }
+            });
+        } else {
+            // Otherwise the banner will be left alone and will launch settings
+            mSmsPromoBannerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    // Launch settings
+                    Intent settingsIntent = new Intent(ConversationList.this,
+                            MessagingPreferenceActivity.class);
+                    startActivityIfNeeded(settingsIntent, -1);
+                }
+            });
+        }
+    }
+
     /**
      * Checks to see if the number of MMS and SMS messages are under the limits for the
      * recycler. If so, it will automatically turn on the recycler setting. If not, it
@@ -382,7 +511,12 @@
     public boolean onPrepareOptionsMenu(Menu menu) {
         MenuItem item = menu.findItem(R.id.action_delete_all);
         if (item != null) {
-            item.setVisible(mListAdapter.getCount() > 0);
+            item.setVisible((mListAdapter.getCount() > 0) && mIsSmsEnabled);
+        }
+        item = menu.findItem(R.id.action_compose_new);
+        if (item != null ){
+            // Dim compose if SMS is disabled because it will not work (will show a toast)
+            item.getIcon().setAlpha(mIsSmsEnabled ? 255 : 127);
         }
         if (!LogTag.DEBUG_DUMP) {
             item = menu.findItem(R.id.action_debug_dump);
@@ -405,7 +539,16 @@
     public boolean onOptionsItemSelected(MenuItem item) {
         switch(item.getItemId()) {
             case R.id.action_compose_new:
-                createNewMessage();
+                if (mIsSmsEnabled) {
+                    createNewMessage();
+                } else {
+                    // Display a toast letting the user know they can not compose.
+                    if (mComposeDisabledToast == null) {
+                        mComposeDisabledToast = Toast.makeText(this,
+                                R.string.compose_disabled_toast, Toast.LENGTH_SHORT);
+                    }
+                    mComposeDisabledToast.show();
+                }
                 break;
             case R.id.action_delete_all:
                 // The invalid threadId of -1 means all threads here.
@@ -506,7 +649,9 @@
                     menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts);
                 }
             }
-            menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
+            if (mIsSmsEnabled) {
+                menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
+            }
         }
     };
 
@@ -756,6 +901,12 @@
                     // 2. Mark all the conversations as seen.
                     Conversation.markAllConversationsAsSeen(getApplicationContext());
                 }
+                if (mSavedFirstVisiblePosition != AdapterView.INVALID_POSITION) {
+                    // Restore the list to its previous position.
+                    getListView().setSelectionFromTop(mSavedFirstVisiblePosition,
+                            mSavedFirstItemOffset);
+                    mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+                }
                 break;
 
             case UNREAD_THREADS_QUERY_TOKEN:
diff --git a/src/com/android/mms/ui/ConversationListItem.java b/src/com/android/mms/ui/ConversationListItem.java
index 730a39b..ff668ce 100644
--- a/src/com/android/mms/ui/ConversationListItem.java
+++ b/src/com/android/mms/ui/ConversationListItem.java
@@ -39,7 +39,6 @@
 import com.android.mms.data.Contact;
 import com.android.mms.data.ContactList;
 import com.android.mms.data.Conversation;
-import com.android.mms.util.SmileyParser;
 
 /**
  * This class manages the view for given conversation.
@@ -209,8 +208,7 @@
         Contact.addListener(this);
 
         // Subject
-        SmileyParser parser = SmileyParser.getInstance();
-        mSubjectView.setText(parser.addSmileySpans(conversation.getSnippet()));
+        mSubjectView.setText(conversation.getSnippet());
         LayoutParams subjectLayout = (LayoutParams)mSubjectView.getLayoutParams();
         // We have to make the subject left of whatever optional items are shown on the right.
         subjectLayout.addRule(RelativeLayout.LEFT_OF, hasAttachment ? R.id.attachment :
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/MessageItem.java b/src/com/android/mms/ui/MessageItem.java
index f01804d..ffa56db 100644
--- a/src/com/android/mms/ui/MessageItem.java
+++ b/src/com/android/mms/ui/MessageItem.java
@@ -325,7 +325,7 @@
                 }
                 MultimediaMessagePdu msg = (MultimediaMessagePdu)pduLoaded.mPdu;
                 mSlideshow = pduLoaded.mSlideshow;
-                mAttachmentType = MessageUtils.getAttachmentType(mSlideshow);
+                mAttachmentType = MessageUtils.getAttachmentType(mSlideshow, msg);
 
                 if (mMessageType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
                     if (msg == null) {
diff --git a/src/com/android/mms/ui/MessageListItem.java b/src/com/android/mms/ui/MessageListItem.java
index ffe06fb..6430be5 100644
--- a/src/com/android/mms/ui/MessageListItem.java
+++ b/src/com/android/mms/ui/MessageListItem.java
@@ -68,7 +68,6 @@
 import com.android.mms.transaction.TransactionService;
 import com.android.mms.util.DownloadManager;
 import com.android.mms.util.ItemLoadedCallback;
-import com.android.mms.util.SmileyParser;
 import com.android.mms.util.ThumbnailManager.ImageLoaded;
 import com.google.android.mms.ContentType;
 import com.google.android.mms.pdu.PduHeaders;
@@ -347,7 +346,7 @@
             if (mMessageItem.mSlideshow == null) {
                 debugText = "NULL slideshow";
             } else {
-                SlideModel slide = ((SlideshowModel) mMessageItem.mSlideshow).get(0);
+                SlideModel slide = mMessageItem.mSlideshow.get(0);
                 if (slide == null) {
                     debugText = "NULL first slide";
                 } else if (!slide.hasImage()) {
@@ -533,14 +532,8 @@
         SpannableStringBuilder buf = new SpannableStringBuilder();
 
         boolean hasSubject = !TextUtils.isEmpty(subject);
-        SmileyParser parser = SmileyParser.getInstance();
         if (hasSubject) {
-            CharSequence smilizedSubject = parser.addSmileySpans(subject);
-            // Can't use the normal getString() with extra arguments for string replacement
-            // because it doesn't preserve the SpannableText returned by addSmileySpans.
-            // We have to manually replace the %s with our text.
-            buf.append(TextUtils.replace(mContext.getResources().getString(R.string.inline_subject),
-                    new String[] { "%s" }, new CharSequence[] { smilizedSubject }));
+            buf.append(mContext.getResources().getString(R.string.inline_subject, subject));
         }
 
         if (!TextUtils.isEmpty(body)) {
@@ -552,7 +545,7 @@
                 if (hasSubject) {
                     buf.append(" - ");
                 }
-                buf.append(parser.addSmileySpans(body));
+                buf.append(body);
             }
         }
 
diff --git a/src/com/android/mms/ui/MessageUtils.java b/src/com/android/mms/ui/MessageUtils.java
index 502bfde..94994cc 100644
--- a/src/com/android/mms/ui/MessageUtils.java
+++ b/src/com/android/mms/ui/MessageUtils.java
@@ -402,8 +402,8 @@
         }
     }
 
-    public static int getAttachmentType(SlideshowModel model) {
-        if (model == null) {
+    public static int getAttachmentType(SlideshowModel model, MultimediaMessagePdu mmp) {
+        if (model == null || mmp == null) {
             return MessageItem.ATTACHMENT_TYPE_NOT_LOADED;
         }
 
@@ -432,6 +432,12 @@
             if (slide.hasText()) {
                 return WorkingMessage.TEXT;
             }
+
+            // Handle the multimedia message only has subject
+            String subject = mmp.getSubject() != null ? mmp.getSubject().getString() : null;
+            if (!TextUtils.isEmpty(subject)) {
+                return WorkingMessage.TEXT;
+            }
         }
 
         return MessageItem.ATTACHMENT_TYPE_NOT_LOADED;
@@ -574,6 +580,10 @@
             mm = slide.getVideo();
         }
 
+        if (mm == null) {
+            return;
+        }
+
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         intent.putExtra("SingleItemOnly", true); // So we don't see "surrounding" images in Gallery
@@ -1005,7 +1015,7 @@
 
         // if we are able to parse the address to a MMS compliant phone number, take that.
         String retVal = parsePhoneNumberForMms(address);
-        if (retVal != null) {
+        if (retVal != null && retVal.length() != 0) {
             return retVal;
         }
 
diff --git a/src/com/android/mms/ui/MessagingPreferenceActivity.java b/src/com/android/mms/ui/MessagingPreferenceActivity.java
index 1d498cb..112d255 100755
--- a/src/com/android/mms/ui/MessagingPreferenceActivity.java
+++ b/src/com/android/mms/ui/MessagingPreferenceActivity.java
@@ -24,15 +24,18 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceManager;
 import android.preference.PreferenceScreen;
+import android.preference.RingtonePreference;
 import android.provider.SearchRecentSuggestions;
 import android.text.TextUtils;
 import android.view.Menu;
@@ -68,6 +71,15 @@
     // Menu entries
     private static final int MENU_RESTORE_DEFAULTS    = 1;
 
+    // Preferences for enabling and disabling SMS
+    private Preference mSmsDisabledPref;
+    private Preference mSmsEnabledPref;
+
+    private PreferenceCategory mStoragePrefCategory;
+    private PreferenceCategory mSmsPrefCategory;
+    private PreferenceCategory mMmsPrefCategory;
+    private PreferenceCategory mNotificationPrefCategory;
+
     private Preference mSmsLimitPref;
     private Preference mSmsDeliveryReportPref;
     private Preference mMmsLimitPref;
@@ -76,14 +88,17 @@
     private Preference mMmsReadReportPref;
     private Preference mManageSimPref;
     private Preference mClearHistoryPref;
-    private ListPreference mVibrateWhenPref;
+    private CheckBoxPreference mVibratePref;
     private CheckBoxPreference mEnableNotificationsPref;
     private CheckBoxPreference mMmsAutoRetrievialPref;
+    private RingtonePreference mRingtonePref;
     private Recycler mSmsRecycler;
     private Recycler mMmsRecycler;
     private static final int CONFIRM_CLEAR_SEARCH_HISTORY_DIALOG = 3;
-    private CharSequence[] mVibrateEntries;
-    private CharSequence[] mVibrateValues;
+
+    // Whether or not we are currently enabled for SMS. This field is updated in onResume to make
+    // sure we notice if the user has changed the default SMS app.
+    private boolean mIsSmsEnabled;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -98,16 +113,49 @@
     @Override
     protected void onResume() {
         super.onResume();
+        boolean isSmsEnabled = MmsConfig.isSmsEnabled(this);
+        if (isSmsEnabled != mIsSmsEnabled) {
+            mIsSmsEnabled = isSmsEnabled;
+            invalidateOptionsMenu();
+        }
 
         // Since the enabled notifications pref can be changed outside of this activity,
         // we have to reload it whenever we resume.
         setEnabledNotificationsPref();
         registerListeners();
+        updateSmsEnabledState();
+    }
+
+    private void updateSmsEnabledState() {
+        // Show the right pref (SMS Disabled or SMS Enabled)
+        PreferenceScreen prefRoot = (PreferenceScreen)findPreference("pref_key_root");
+        if (!mIsSmsEnabled) {
+            prefRoot.addPreference(mSmsDisabledPref);
+            prefRoot.removePreference(mSmsEnabledPref);
+        } else {
+            prefRoot.removePreference(mSmsDisabledPref);
+            prefRoot.addPreference(mSmsEnabledPref);
+        }
+
+        // Enable or Disable the settings as appropriate
+        mStoragePrefCategory.setEnabled(mIsSmsEnabled);
+        mSmsPrefCategory.setEnabled(mIsSmsEnabled);
+        mMmsPrefCategory.setEnabled(mIsSmsEnabled);
+        mNotificationPrefCategory.setEnabled(mIsSmsEnabled);
     }
 
     private void loadPrefs() {
         addPreferencesFromResource(R.xml.preferences);
 
+        mSmsDisabledPref = findPreference("pref_key_sms_disabled");
+        mSmsEnabledPref = findPreference("pref_key_sms_enabled");
+
+        mStoragePrefCategory = (PreferenceCategory)findPreference("pref_key_storage_settings");
+        mSmsPrefCategory = (PreferenceCategory)findPreference("pref_key_sms_settings");
+        mMmsPrefCategory = (PreferenceCategory)findPreference("pref_key_mms_settings");
+        mNotificationPrefCategory =
+                (PreferenceCategory)findPreference("pref_key_notification_settings");
+
         mManageSimPref = findPreference("pref_key_manage_sim_messages");
         mSmsLimitPref = findPreference("pref_key_sms_delete_limit");
         mSmsDeliveryReportPref = findPreference("pref_key_sms_delivery_reports");
@@ -118,10 +166,8 @@
         mClearHistoryPref = findPreference("pref_key_mms_clear_history");
         mEnableNotificationsPref = (CheckBoxPreference) findPreference(NOTIFICATION_ENABLED);
         mMmsAutoRetrievialPref = (CheckBoxPreference) findPreference(AUTO_RETRIEVAL);
-        mVibrateWhenPref = (ListPreference) findPreference(NOTIFICATION_VIBRATE_WHEN);
-
-        mVibrateEntries = getResources().getTextArray(R.array.prefEntries_vibrateWhen);
-        mVibrateValues = getResources().getTextArray(R.array.prefValues_vibrateWhen);
+        mVibratePref = (CheckBoxPreference) findPreference(NOTIFICATION_VIBRATE);
+        mRingtonePref = (RingtonePreference) findPreference(NOTIFICATION_RINGTONE);
 
         setMessagePreferences();
     }
@@ -130,6 +176,7 @@
         PreferenceManager.getDefaultSharedPreferences(this).edit().clear().apply();
         setPreferenceScreen(null);
         loadPrefs();
+        updateSmsEnabledState();
 
         // NOTE: After restoring preferences, the auto delete function (i.e. message recycler)
         // will be turned off by default. However, we really want the default to be turned on.
@@ -143,55 +190,49 @@
     private void setMessagePreferences() {
         if (!MmsApp.getApplication().getTelephonyManager().hasIccCard()) {
             // No SIM card, remove the SIM-related prefs
-            PreferenceCategory smsCategory =
-                (PreferenceCategory)findPreference("pref_key_sms_settings");
-            smsCategory.removePreference(mManageSimPref);
+            mSmsPrefCategory.removePreference(mManageSimPref);
         }
 
         if (!MmsConfig.getSMSDeliveryReportsEnabled()) {
-            PreferenceCategory smsCategory =
-                (PreferenceCategory)findPreference("pref_key_sms_settings");
-            smsCategory.removePreference(mSmsDeliveryReportPref);
+            mSmsPrefCategory.removePreference(mSmsDeliveryReportPref);
             if (!MmsApp.getApplication().getTelephonyManager().hasIccCard()) {
-                getPreferenceScreen().removePreference(smsCategory);
+                getPreferenceScreen().removePreference(mSmsPrefCategory);
             }
         }
 
         if (!MmsConfig.getMmsEnabled()) {
             // No Mms, remove all the mms-related preferences
-            PreferenceCategory mmsOptions =
-                (PreferenceCategory)findPreference("pref_key_mms_settings");
-            getPreferenceScreen().removePreference(mmsOptions);
+            getPreferenceScreen().removePreference(mMmsPrefCategory);
 
-            PreferenceCategory storageOptions =
-                (PreferenceCategory)findPreference("pref_key_storage_settings");
-            storageOptions.removePreference(findPreference("pref_key_mms_delete_limit"));
+            mStoragePrefCategory.removePreference(findPreference("pref_key_mms_delete_limit"));
         } else {
-            PreferenceCategory mmsOptions =
-                    (PreferenceCategory)findPreference("pref_key_mms_settings");
             if (!MmsConfig.getMMSDeliveryReportsEnabled()) {
-                mmsOptions.removePreference(mMmsDeliveryReportPref);
+                mMmsPrefCategory.removePreference(mMmsDeliveryReportPref);
             }
             if (!MmsConfig.getMMSReadReportsEnabled()) {
-                mmsOptions.removePreference(mMmsReadReportPref);
+                mMmsPrefCategory.removePreference(mMmsReadReportPref);
             }
             // If the phone's SIM doesn't know it's own number, disable group mms.
             if (!MmsConfig.getGroupMmsEnabled() ||
                     TextUtils.isEmpty(MessageUtils.getLocalNumber())) {
-                mmsOptions.removePreference(mMmsGroupMmsPref);
+                mMmsPrefCategory.removePreference(mMmsGroupMmsPref);
             }
         }
 
         setEnabledNotificationsPref();
 
-        // If needed, migrate vibration setting from a previous version
+        // If needed, migrate vibration setting from the previous tri-state setting stored in
+        // NOTIFICATION_VIBRATE_WHEN to the boolean setting stored in NOTIFICATION_VIBRATE.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
-        if (!sharedPreferences.contains(NOTIFICATION_VIBRATE_WHEN) &&
-                sharedPreferences.contains(NOTIFICATION_VIBRATE)) {
-            int stringId = sharedPreferences.getBoolean(NOTIFICATION_VIBRATE, false) ?
-                    R.string.prefDefault_vibrate_true :
-                    R.string.prefDefault_vibrate_false;
-            mVibrateWhenPref.setValue(getString(stringId));
+        if (sharedPreferences.contains(NOTIFICATION_VIBRATE_WHEN)) {
+            String vibrateWhen = sharedPreferences.
+                    getString(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN, null);
+            boolean vibrate = "always".equals(vibrateWhen);
+            SharedPreferences.Editor prefsEditor = sharedPreferences.edit();
+            prefsEditor.putBoolean(NOTIFICATION_VIBRATE, vibrate);
+            prefsEditor.remove(NOTIFICATION_VIBRATE_WHEN);  // remove obsolete setting
+            prefsEditor.apply();
+            mVibratePref.setChecked(vibrate);
         }
 
         mSmsRecycler = Recycler.getSmsRecycler();
@@ -201,7 +242,15 @@
         setSmsDisplayLimit();
         setMmsDisplayLimit();
 
-        adjustVibrateSummary(mVibrateWhenPref.getValue());
+        String soundValue = sharedPreferences.getString(NOTIFICATION_RINGTONE, null);
+        setRingtoneSummary(soundValue);
+    }
+
+    private void setRingtoneSummary(String soundValue) {
+        Uri soundUri = TextUtils.isEmpty(soundValue) ? null : Uri.parse(soundValue);
+        Ringtone tone = soundUri != null ? RingtoneManager.getRingtone(this, soundUri) : null;
+        mRingtonePref.setSummary(tone != null ? tone.getTitle(this)
+                : getResources().getString(R.string.silent_ringtone));
     }
 
     private void setEnabledNotificationsPref() {
@@ -225,7 +274,9 @@
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
         menu.clear();
-        menu.add(0, MENU_RESTORE_DEFAULTS, 0, R.string.restore_default);
+        if (mIsSmsEnabled) {
+            menu.add(0, MENU_RESTORE_DEFAULTS, 0, R.string.restore_default);
+        }
         return true;
     }
 
@@ -345,29 +396,18 @@
     }
 
     private void registerListeners() {
-        mVibrateWhenPref.setOnPreferenceChangeListener(this);
+        mRingtonePref.setOnPreferenceChangeListener(this);
     }
 
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         boolean result = false;
-        if (preference == mVibrateWhenPref) {
-            adjustVibrateSummary((String)newValue);
+        if (preference == mRingtonePref) {
+            setRingtoneSummary((String)newValue);
             result = true;
         }
         return result;
     }
 
-    private void adjustVibrateSummary(String value) {
-        int len = mVibrateValues.length;
-        for (int i = 0; i < len; i++) {
-            if (mVibrateValues[i].equals(value)) {
-                mVibrateWhenPref.setSummary(mVibrateEntries[i]);
-                return;
-            }
-        }
-        mVibrateWhenPref.setSummary(null);
-    }
-
     // For the group mms feature to be enabled, the following must be true:
     //  1. the feature is enabled in mms_config.xml (currently on by default)
     //  2. the feature is enabled in the mms settings page
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/RecipientsEditor.java b/src/com/android/mms/ui/RecipientsEditor.java
index 0baeff8..4de2118 100644
--- a/src/com/android/mms/ui/RecipientsEditor.java
+++ b/src/com/android/mms/ui/RecipientsEditor.java
@@ -274,12 +274,19 @@
             for (Contact c : list) {
                 // Calling setText to set the recipients won't create chips,
                 // but calling append() will.
-                append(contactToToken(c) + ", ");
+                append(contactToToken(c) + ",");
             }
         }
     }
 
     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/SlideListItemView.java b/src/com/android/mms/ui/SlideListItemView.java
index ae6b921..7bed245 100644
--- a/src/com/android/mms/ui/SlideListItemView.java
+++ b/src/com/android/mms/ui/SlideListItemView.java
@@ -120,15 +120,8 @@
             mAttachmentIcon.setImageDrawable(null);
         }
 
-        MediaPlayer mp = new MediaPlayer();
-        try {
-            mp.setDataSource(mContext, video);
-            mImagePreview.setImageBitmap(mp.getFrameAt(1000));
-        } catch (IOException e) {
-            Log.e(TAG, "Unexpected IOException.", e);
-        } finally {
-            mp.release();
-        }
+        // TODO: get a thumbnail from the video
+        mImagePreview.setImageBitmap(null);
     }
 
     public void setVideoThumbnail(String name, Bitmap thumbnail) {
diff --git a/src/com/android/mms/ui/SlideshowActivity.java b/src/com/android/mms/ui/SlideshowActivity.java
index c76b178..a287424 100644
--- a/src/com/android/mms/ui/SlideshowActivity.java
+++ b/src/com/android/mms/ui/SlideshowActivity.java
@@ -42,6 +42,7 @@
 import android.view.Window;
 import android.widget.MediaController;
 import android.widget.MediaController.MediaPlayerControl;
+import android.widget.SeekBar;
 
 import com.android.mms.R;
 import com.android.mms.dom.AttrImpl;
@@ -284,6 +285,13 @@
                 mSmilPlayer.stopWhenReload();
             }
             if (mMediaController != null) {
+                // Must set the seek bar change listener null, otherwise if we rotate it
+                // while tapping progress bar continuously, window will leak.
+                View seekBar = mMediaController
+                        .findViewById(com.android.internal.R.id.mediacontroller_progress);
+                if (seekBar instanceof SeekBar) {
+                    ((SeekBar)seekBar).setOnSeekBarChangeListener(null);
+                }
                 // Must do this so we don't leak a window.
                 mMediaController.hide();
             }
@@ -291,6 +299,14 @@
     }
 
     @Override
+    protected void onDestroy() {
+        if (mSlideView != null) {
+            mSlideView.setMediaController(null);
+        }
+        super.onDestroy();
+    }
+
+    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -377,6 +393,11 @@
         public boolean canSeekForward() {
             return true;
         }
+
+        @Override
+        public int getAudioSessionId() {
+            return 0;
+        }
     }
 
     public void handleEvent(Event evt) {
diff --git a/src/com/android/mms/ui/SlideshowAttachmentView.java b/src/com/android/mms/ui/SlideshowAttachmentView.java
index 22dccef..4b22c96 100644
--- a/src/com/android/mms/ui/SlideshowAttachmentView.java
+++ b/src/com/android/mms/ui/SlideshowAttachmentView.java
@@ -101,15 +101,8 @@
     }
 
     public void setVideo(String name, Uri video) {
-        MediaPlayer mp = new MediaPlayer();
-        try {
-            mp.setDataSource(mContext, video);
-            mImageView.setImageBitmap(mp.getFrameAt(1000));
-        } catch (IOException e) {
-            Log.e(TAG, "Unexpected IOException.", e);
-        } finally {
-            mp.release();
-        }
+        // TODO: get a thumbnail from the video
+        mImageView.setImageBitmap(null);
     }
 
     public void setVideoVisibility(boolean visible) {
@@ -125,7 +118,7 @@
     }
 
     public void reset() {
-        mImageView.setImageURI(null);
+        mImageView.setImageBitmap(null);
         mTextView.setText("");
     }
 
diff --git a/src/com/android/mms/ui/SmsStorageMonitor.java b/src/com/android/mms/ui/SmsStorageMonitor.java
new file mode 100644
index 0000000..c426e8d
--- /dev/null
+++ b/src/com/android/mms/ui/SmsStorageMonitor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 Roger Chen <cxr514033970@gmail.com>
+ *
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.mms.R;
+
+public class SmsStorageMonitor extends BroadcastReceiver {
+    private Context mContext;
+    private final int NOTIFICATION_STORAGE_LIMITED_ID = -1;
+    private static NotificationManager mNotificationManager;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mContext = context;
+        if (mNotificationManager == null) {
+            mNotificationManager =
+                    (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+        if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
+            notifyReachStorageLimited();
+        } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
+            cancelStorageLimitedWarning();
+        }
+    }
+
+    private void notifyReachStorageLimited() {
+        Notification.Builder mBuilder =
+                new Notification.Builder(mContext)
+                        .setSmallIcon(R.mipmap.ic_launcher_smsmms)
+                        .setContentTitle(mContext.getString(R.string.storage_warning_title))
+                        .setContentText(mContext.getString(R.string.storage_warning_content))
+                        .setOngoing(true);
+        mNotificationManager.notify(NOTIFICATION_STORAGE_LIMITED_ID, mBuilder.build());
+    }
+
+    private void cancelStorageLimitedWarning() {
+        mNotificationManager.cancel(NOTIFICATION_STORAGE_LIMITED_ID);
+    }
+}
diff --git a/src/com/android/mms/ui/UriImage.java b/src/com/android/mms/ui/UriImage.java
index c74764b..2270e6f 100644
--- a/src/com/android/mms/ui/UriImage.java
+++ b/src/com/android/mms/ui/UriImage.java
@@ -17,19 +17,18 @@
 
 package com.android.mms.ui;
 
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.UriMatcher;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SqliteWrapper;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
 import android.net.Uri;
+import android.provider.MediaStore;
 import android.provider.MediaStore.Images;
 import android.provider.Telephony.Mms.Part;
 import android.text.TextUtils;
@@ -37,14 +36,25 @@
 import android.webkit.MimeTypeMap;
 
 import com.android.mms.LogTag;
+import com.android.mms.exif.ExifInterface;
 import com.android.mms.model.ImageModel;
 import com.google.android.mms.ContentType;
 import com.google.android.mms.pdu.PduPart;
 
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
 public class UriImage {
     private static final String TAG = "Mms/image";
     private static final boolean DEBUG = false;
     private static final boolean LOCAL_LOGV = false;
+    private static final int MMS_PART_ID = 12;
+    private static final UriMatcher sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+    static {
+        sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
+    }
 
     private final Context mContext;
     private final Uri mUri;
@@ -269,17 +279,20 @@
             scaleFactor *= .75F;
         }
 
+        int orientation = getOrientation(context, uri);
+
         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
             Log.v(TAG, "getResizedBitmap: wlimit=" + widthLimit +
                     ", hlimit=" + heightLimit + ", sizeLimit=" + byteLimit +
                     ", width=" + width + ", height=" + height +
                     ", initialScaleFactor=" + scaleFactor +
-                    ", uri=" + uri);
+                    ", uri=" + uri +
+                    ", orientation=" + orientation);
         }
 
         InputStream input = null;
+        ByteArrayOutputStream os = null;
         try {
-            ByteArrayOutputStream os = null;
             int attempts = 1;
             int sampleSize = 1;
             BitmapFactory.Options options = new BitmapFactory.Options();
@@ -354,6 +367,13 @@
                     // Compress the image into a JPG. Start with MessageUtils.IMAGE_COMPRESSION_QUALITY.
                     // In case that the image byte size is still too large reduce the quality in
                     // proportion to the desired byte size.
+                    if (os != null) {
+                        try {
+                            os.close();
+                        } catch (IOException e) {
+                            Log.e(TAG, e.getMessage(), e);
+                        }
+                    }
                     os = new ByteArrayOutputStream();
                     b.compress(CompressFormat.JPEG, quality, os);
                     int jpgFileSize = os.size();
@@ -367,6 +387,13 @@
                             Log.v(TAG, "getResizedImageData: compress(2) w/ quality=" + quality);
                         }
 
+                        if (os != null) {
+                            try {
+                                os.close();
+                            } catch (IOException e) {
+                                Log.e(TAG, e.getMessage(), e);
+                            }
+                        }
                         os = new ByteArrayOutputStream();
                         b.compress(CompressFormat.JPEG, quality, os);
                     }
@@ -387,6 +414,21 @@
                 attempts++;
                 resultTooBig = os == null || os.size() > byteLimit;
             } while (resultTooBig && attempts < NUMBER_OF_RESIZE_ATTEMPTS);
+            if (!resultTooBig && orientation != 0) {
+                // Rotate the final bitmap if we need to.
+                try {
+                    b = UriImage.rotateBitmap(b, orientation);
+                    os = new ByteArrayOutputStream();
+                    b.compress(CompressFormat.JPEG, quality, os);
+                    resultTooBig = os == null || os.size() > byteLimit;
+                } catch (java.lang.OutOfMemoryError e) {
+                    Log.w(TAG, "getResizedImageData - image too big (OutOfMemoryError)");
+                    if (os == null) {
+                        return null;
+                    }
+                }
+            }
+
             b.recycle();        // done with the bitmap, release the memory
             if (Log.isLoggable(LogTag.APP, Log.VERBOSE) && resultTooBig) {
                 Log.v(TAG, "getResizedImageData returning NULL because the result is too big: " +
@@ -400,6 +442,122 @@
         } catch (java.lang.OutOfMemoryError e) {
             Log.e(TAG, e.getMessage(), e);
             return null;
+        } finally {
+            if (input != null) {
+                try {
+                    input.close();
+                } catch (IOException e) {
+                    Log.e(TAG, e.getMessage(), e);
+                }
+            }
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    Log.e(TAG, e.getMessage(), e);
+                }
+            }
         }
     }
+
+    /**
+     * Bitmap rotation method
+     *
+     * @param bitmap The input bitmap
+     * @param degrees The rotation angle
+     */
+    public static Bitmap rotateBitmap(Bitmap bitmap, int degrees) {
+        if (degrees != 0 && bitmap != null) {
+            final Matrix m = new Matrix();
+            final int w = bitmap.getWidth();
+            final int h = bitmap.getHeight();
+            m.setRotate(degrees, (float) w / 2, (float) h / 2);
+
+            try {
+                final Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, m, true);
+                if (bitmap != rotatedBitmap && rotatedBitmap != null) {
+                    bitmap.recycle();
+                    bitmap = rotatedBitmap;
+                }
+            } catch (OutOfMemoryError ex) {
+                Log.e(TAG, "OOM in rotateBitmap", ex);
+                // We have no memory to rotate. Return the original bitmap.
+            }
+        }
+
+        return bitmap;
+    }
+
+    /**
+     * Returns the number of degrees to rotate the picture, based on the orientation tag in
+     * the exif data or the orientation column in the database. If there's no tag or column,
+     * 0 degrees is returned.
+     *
+     * @param context Used to get the ContentResolver
+     * @param uri Path to the image
+     */
+    public static int getOrientation(Context context, Uri uri) {
+        long dur = System.currentTimeMillis();
+        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme()) ||
+                sURLMatcher.match(uri) == MMS_PART_ID) {
+            // If the uri is a file or an mms part, we have to look at the exif data in the
+            // file for the orientation because there is no column in the db for the orientation.
+            try {
+                InputStream inputStream = context.getContentResolver().openInputStream(uri);
+                ExifInterface exif = new ExifInterface();
+                try {
+                    exif.readExif(inputStream);
+                    Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+                    if (val == null){
+                        return 0;
+                    }
+                    int orientation =
+                            ExifInterface.getRotationForOrientationValue(val.shortValue());
+                    return orientation;
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to read EXIF orientation", e);
+                } finally {
+                    if (inputStream != null) {
+                        try {
+                            inputStream.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "Can't open uri: " + uri, e);
+            } finally {
+                dur = System.currentTimeMillis() - dur;
+                if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
+                    Log.v(TAG, "UriImage.getOrientation (exif path) took: " + dur + " ms");
+                }
+            }
+        } else {
+            // Try to get the orientation from the ORIENTATION column in the database. This is much
+            // faster than reading all the exif tags from the file.
+            Cursor cursor = null;
+            try {
+                cursor = context.getContentResolver().query(uri,
+                        new String[] {
+                            MediaStore.Images.ImageColumns.ORIENTATION
+                        },
+                        null, null, null);
+                if (cursor.moveToNext()) {
+                    int ori = cursor.getInt(0);
+                    return ori;
+                }
+            } catch (SQLiteException e) {
+            } catch (IllegalArgumentException e) {
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+                dur = System.currentTimeMillis() - dur;
+                if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
+                    Log.v(TAG, "UriImage.getOrientation (db column path) took: " + dur + " ms");
+                }
+            }
+        }
+        return 0;
+    }
 }
diff --git a/src/com/android/mms/util/DownloadManager.java b/src/com/android/mms/util/DownloadManager.java
index 3e061e3..16f49ed 100644
--- a/src/com/android/mms/util/DownloadManager.java
+++ b/src/com/android/mms/util/DownloadManager.java
@@ -189,8 +189,8 @@
         try {
             NotificationInd nInd = (NotificationInd) PduPersister.getPduPersister(mContext)
                     .load(uri);
-            if ((nInd.getExpiry() < System.currentTimeMillis()/1000L)
-                && (state == STATE_DOWNLOADING)) {
+            if ((nInd.getExpiry() < System.currentTimeMillis() / 1000L)
+                    && (state == STATE_DOWNLOADING || state == STATE_PRE_DOWNLOADING)) {
                 mHandler.post(new Runnable() {
                     public void run() {
                         Toast.makeText(mContext, R.string.service_message_not_found,
diff --git a/src/com/android/mms/util/SmileyParser.java b/src/com/android/mms/util/SmileyParser.java
deleted file mode 100644
index 7a663d1..0000000
--- a/src/com/android/mms/util/SmileyParser.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2009 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.util;
-
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import android.content.Context;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.style.ImageSpan;
-
-import com.android.mms.R;
-
-/**
- * A class for annotating a CharSequence with spans to convert textual emoticons
- * to graphical ones.
- */
-public class SmileyParser {
-    // Singleton stuff
-    private static SmileyParser sInstance;
-    public static SmileyParser getInstance() { return sInstance; }
-    public static void init(Context context) {
-        sInstance = new SmileyParser(context);
-    }
-
-    private final Context mContext;
-    private final String[] mSmileyTexts;
-    private final Pattern mPattern;
-    private final HashMap<String, Integer> mSmileyToRes;
-
-    private SmileyParser(Context context) {
-        mContext = context;
-        mSmileyTexts = mContext.getResources().getStringArray(DEFAULT_SMILEY_TEXTS);
-        mSmileyToRes = buildSmileyToRes();
-        mPattern = buildPattern();
-    }
-
-    static class Smileys {
-        private static final int[] sIconIds = {
-            R.drawable.emo_im_happy,
-            R.drawable.emo_im_sad,
-            R.drawable.emo_im_winking,
-            R.drawable.emo_im_tongue_sticking_out,
-            R.drawable.emo_im_surprised,
-            R.drawable.emo_im_kissing,
-            R.drawable.emo_im_yelling,
-            R.drawable.emo_im_cool,
-            R.drawable.emo_im_money_mouth,
-            R.drawable.emo_im_foot_in_mouth,
-            R.drawable.emo_im_embarrassed,
-            R.drawable.emo_im_angel,
-            R.drawable.emo_im_undecided,
-            R.drawable.emo_im_crying,
-            R.drawable.emo_im_lips_are_sealed,
-            R.drawable.emo_im_laughing,
-            R.drawable.emo_im_wtf,
-            R.drawable.emo_im_heart,
-            R.drawable.emo_im_mad,
-            R.drawable.emo_im_smirk,
-            R.drawable.emo_im_pokerface
-        };
-
-        public static int HAPPY = 0;
-        public static int SAD = 1;
-        public static int WINKING = 2;
-        public static int TONGUE_STICKING_OUT = 3;
-        public static int SURPRISED = 4;
-        public static int KISSING = 5;
-        public static int YELLING = 6;
-        public static int COOL = 7;
-        public static int MONEY_MOUTH = 8;
-        public static int FOOT_IN_MOUTH = 9;
-        public static int EMBARRASSED = 10;
-        public static int ANGEL = 11;
-        public static int UNDECIDED = 12;
-        public static int CRYING = 13;
-        public static int LIPS_ARE_SEALED = 14;
-        public static int LAUGHING = 15;
-        public static int WTF = 16;
-        public static int MAD = 17;
-        public static int HEART = 18;
-        public static int SMIRK = 19;
-        public static int POKERFACE = 20;
-
-        public static int getSmileyResource(int which) {
-            return sIconIds[which];
-        }
-    }
-
-    // NOTE: if you change anything about this array, you must make the corresponding change
-    // to the string arrays: default_smiley_texts and default_smiley_names in res/values/arrays.xml
-    public static final int[] DEFAULT_SMILEY_RES_IDS = {
-        Smileys.getSmileyResource(Smileys.HAPPY),                //  0
-        Smileys.getSmileyResource(Smileys.SAD),                  //  1
-        Smileys.getSmileyResource(Smileys.WINKING),              //  2
-        Smileys.getSmileyResource(Smileys.TONGUE_STICKING_OUT),  //  3
-        Smileys.getSmileyResource(Smileys.SURPRISED),            //  4
-        Smileys.getSmileyResource(Smileys.KISSING),              //  5
-        Smileys.getSmileyResource(Smileys.YELLING),              //  6
-        Smileys.getSmileyResource(Smileys.COOL),                 //  7
-        Smileys.getSmileyResource(Smileys.MONEY_MOUTH),          //  8
-        Smileys.getSmileyResource(Smileys.FOOT_IN_MOUTH),        //  9
-        Smileys.getSmileyResource(Smileys.EMBARRASSED),          //  10
-        Smileys.getSmileyResource(Smileys.ANGEL),                //  11
-        Smileys.getSmileyResource(Smileys.UNDECIDED),            //  12
-        Smileys.getSmileyResource(Smileys.CRYING),               //  13
-        Smileys.getSmileyResource(Smileys.LIPS_ARE_SEALED),      //  14
-        Smileys.getSmileyResource(Smileys.LAUGHING),             //  15
-        Smileys.getSmileyResource(Smileys.WTF),                  //  16
-        Smileys.getSmileyResource(Smileys.MAD),                  //  17
-        Smileys.getSmileyResource(Smileys.HEART),                //  18
-        Smileys.getSmileyResource(Smileys.SMIRK),                //  19
-        Smileys.getSmileyResource(Smileys.POKERFACE),            //  20
-    };
-
-    public static final int DEFAULT_SMILEY_TEXTS = R.array.default_smiley_texts;
-    public static final int DEFAULT_SMILEY_NAMES = R.array.default_smiley_names;
-
-    /**
-     * Builds the hashtable we use for mapping the string version
-     * of a smiley (e.g. ":-)") to a resource ID for the icon version.
-     */
-    private HashMap<String, Integer> buildSmileyToRes() {
-        if (DEFAULT_SMILEY_RES_IDS.length != mSmileyTexts.length) {
-            // Throw an exception if someone updated DEFAULT_SMILEY_RES_IDS
-            // and failed to update arrays.xml
-            throw new IllegalStateException("Smiley resource ID/text mismatch");
-        }
-
-        HashMap<String, Integer> smileyToRes =
-                            new HashMap<String, Integer>(mSmileyTexts.length);
-        for (int i = 0; i < mSmileyTexts.length; i++) {
-            smileyToRes.put(mSmileyTexts[i], DEFAULT_SMILEY_RES_IDS[i]);
-        }
-
-        return smileyToRes;
-    }
-
-    /**
-     * Builds the regular expression we use to find smileys in {@link #addSmileySpans}.
-     */
-    private Pattern buildPattern() {
-        // Set the StringBuilder capacity with the assumption that the average
-        // smiley is 3 characters long.
-        StringBuilder patternString = new StringBuilder(mSmileyTexts.length * 3);
-
-        // Build a regex that looks like (:-)|:-(|...), but escaping the smilies
-        // properly so they will be interpreted literally by the regex matcher.
-        patternString.append('(');
-        for (String s : mSmileyTexts) {
-            patternString.append(Pattern.quote(s));
-            patternString.append('|');
-        }
-        // Replace the extra '|' with a ')'
-        patternString.replace(patternString.length() - 1, patternString.length(), ")");
-
-        return Pattern.compile(patternString.toString());
-    }
-
-
-    /**
-     * Adds ImageSpans to a CharSequence that replace textual emoticons such
-     * as :-) with a graphical version.
-     *
-     * @param text A CharSequence possibly containing emoticons
-     * @return A CharSequence annotated with ImageSpans covering any
-     *         recognized emoticons.
-     */
-    public CharSequence addSmileySpans(CharSequence text) {
-        SpannableStringBuilder builder = new SpannableStringBuilder(text);
-
-        Matcher matcher = mPattern.matcher(text);
-        while (matcher.find()) {
-            int resId = mSmileyToRes.get(matcher.group());
-            builder.setSpan(new ImageSpan(mContext, resId),
-                            matcher.start(), matcher.end(),
-                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        }
-
-        return builder;
-    }
-}
-
-
diff --git a/src/com/android/mms/util/ThumbnailManager.java b/src/com/android/mms/util/ThumbnailManager.java
index 3834168..df401da 100644
--- a/src/com/android/mms/util/ThumbnailManager.java
+++ b/src/com/android/mms/util/ThumbnailManager.java
@@ -16,12 +16,6 @@
 
 package com.android.mms.util;
 
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.Set;
-
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -39,6 +33,12 @@
 import com.android.mms.ui.UriImage;
 import com.android.mms.util.ImageCacheService.ImageData;
 
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Set;
+
 /**
  * Primary {@link ThumbnailManager} implementation used by {@link MessagingApplication}.
  * <p>
@@ -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;
 
@@ -483,7 +485,14 @@
             // We need to resize down if the decoder does not support inSampleSize.
             // (For example, GIF images.)
             result = resizeDownIfTooBig(result, targetSize, true);
-            return ensureGLCompatibleBitmap(result);
+            result = ensureGLCompatibleBitmap(result);
+
+            int orientation = UriImage.getOrientation(mContext, uri);
+            // Rotate the bitmap if we need to.
+            if (result != null && orientation != 0) {
+                result = UriImage.rotateBitmap(result, orientation);
+            }
+            return result;
         }
 
         // This computes a sample size which makes the longer side at least
@@ -516,7 +525,6 @@
             if (scale > 0.5f) return bitmap;
             return resizeBitmapByScale(bitmap, scale, recycle);
         }
-
     }
 
     public static class ImageLoaded {
diff --git a/src/com/android/mms/widget/MmsWidgetService.java b/src/com/android/mms/widget/MmsWidgetService.java
index a0644a8..29b0b93 100644
--- a/src/com/android/mms/widget/MmsWidgetService.java
+++ b/src/com/android/mms/widget/MmsWidgetService.java
@@ -39,7 +39,6 @@
 import com.android.mms.ui.ConversationList;
 import com.android.mms.ui.ConversationListItem;
 import com.android.mms.ui.MessageUtils;
-import com.android.mms.util.SmileyParser;
 
 public class MmsWidgetService extends RemoteViewsService {
     private static final String TAG = "MmsWidgetService";
@@ -271,11 +270,8 @@
                 remoteViews.setTextViewText(R.id.from, from);
 
                 // Subject
-                // TODO: the SmileyParser inserts image spans but they don't seem to make it
-                // into the remote view.
-                SmileyParser parser = SmileyParser.getInstance();
                 remoteViews.setTextViewText(R.id.subject,
-                        addColor(parser.addSmileySpans(conv.getSnippet()),
+                        addColor(conv.getSnippet(),
                                 conv.hasUnreadMessages() ? SUBJECT_TEXT_COLOR_UNREAD :
                                     SUBJECT_TEXT_COLOR_READ));
 
diff --git a/tests/src/com/android/mms/util/SmileyParserUnitTests.java b/tests/src/com/android/mms/util/SmileyParserUnitTests.java
deleted file mode 100644
index dc24a5b..0000000
--- a/tests/src/com/android/mms/util/SmileyParserUnitTests.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.util;
-
-import java.nio.IntBuffer;
-
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.text.SpannableStringBuilder;
-import android.text.style.ImageSpan;
-
-import com.android.internal.widget.Smileys;
-
-/**
- * This is a series of unit tests for the SmileyParser class.
- * 
- * This is just unit tests of the SmileyParser - the activity is not instantiated
- */
-@SmallTest
-public class SmileyParserUnitTests extends AndroidTestCase {
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        
-        SmileyParser.init(getContext());
-    }
-
-    /**
-     * Test of smiley strings.
-     */
-    public void testSmileyParser() {
-        SmileyParser parser = SmileyParser.getInstance();
-        SpannableStringBuilder buf = new SpannableStringBuilder();
-        
-        // Put a string that looks kind of like a smiley in between two valid smileys.
-        buf.append(parser.addSmileySpans(":-):-:-("));
-
-        ImageSpan[] spans = buf.getSpans(0, buf.length(), ImageSpan.class);
-
-        assertTrue("Smiley (happy) bitmaps aren't equal",
-                compareImageSpans(new ImageSpan(mContext,
-                        Smileys.getSmileyResource(Smileys.HAPPY)), spans[0]));
-
-        assertTrue("Smiley (sad) bitmaps aren't equal",
-                compareImageSpans(new ImageSpan(mContext,
-                        Smileys.getSmileyResource(Smileys.SAD)), spans[1]));
-    }
-    
-    private boolean compareImageSpans(ImageSpan span1, ImageSpan span2) {
-        BitmapDrawable bitmapDrawable1 = (BitmapDrawable)span1.getDrawable();
-        BitmapDrawable bitmapDrawable2 = (BitmapDrawable)span2.getDrawable();
-        Bitmap bitmap1 = bitmapDrawable1.getBitmap();
-        Bitmap bitmap2 = bitmapDrawable2.getBitmap();
-
-        int rowBytes1 = bitmap1.getRowBytes();
-        int rowBytes2 = bitmap2.getRowBytes();
-        if (rowBytes1 != rowBytes2) {
-            return false;
-        }
-        int height1 = bitmap1.getHeight();
-        int height2 = bitmap2.getHeight();
-        if (height1 != height2) {
-            return false;
-        }
-        int size = height1 * rowBytes1;
-        int[] intArray1 = new int[size];
-        int[] intArray2 = new int[size];
-        IntBuffer intBuf1 = IntBuffer.wrap(intArray1);
-        IntBuffer intBuf2 = IntBuffer.wrap(intArray2);
-
-        bitmap1.copyPixelsToBuffer(intBuf1);
-        bitmap2.copyPixelsToBuffer(intBuf2);
-        
-        for (int i = 0; i < size; i++) {
-            if (intArray1[i] != intArray2[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-}