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: <<xliff:g id="NAME">%1$s</xliff:g>>"</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><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;
- }
-
-}