diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 3bec8f867..0fe694d78 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -31,8 +31,12 @@
+
+
+
+
@@ -1284,18 +1288,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/layout/mms_demo.xml b/samples/ApiDemos/res/layout/mms_demo.xml
new file mode 100644
index 000000000..f9bab39d9
--- /dev/null
+++ b/samples/ApiDemos/res/layout/mms_demo.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 73b5acd70..8fd47a7d8 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -1522,4 +1522,22 @@
Entry alias:
Generate
Must supply an alias
-
+
+
+
+
+
+ Last received message
+ Addresses
+ Subject
+ Text
+ Send
+
+ Sending
+ Sent OK
+ Downloaded
+ Failed
+ Downloading
+
+ Enable MMS broadcast receiver
+
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/HeadlessSmsSendService.java b/samples/ApiDemos/src/com/example/android/apis/os/HeadlessSmsSendService.java
new file mode 100644
index 000000000..1f47fa11b
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/HeadlessSmsSendService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.example.android.apis.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Dummy service to make sure this app can be default SMS app
+ */
+public class HeadlessSmsSendService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/MmsMessagingDemo.java b/samples/ApiDemos/src/com/example/android/apis/os/MmsMessagingDemo.java
new file mode 100644
index 000000000..7c7fae5d2
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/MmsMessagingDemo.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2014 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.example.android.apis.os;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.pdu.CharacterSets;
+import com.google.android.mms.pdu.EncodedStringValue;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.PduBody;
+import com.google.android.mms.pdu.PduComposer;
+import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.PduPart;
+import com.google.android.mms.pdu.RetrieveConf;
+import com.google.android.mms.pdu.SendConf;
+import com.google.android.mms.pdu.SendReq;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.example.android.apis.R;
+
+public class MmsMessagingDemo extends Activity {
+ private static final String TAG = "MmsMessagingDemo";
+
+ public static final String EXTRA_NOTIFICATION_URL = "notification_url";
+
+ private static final String ACTION_MMS_SENT = "com.example.android.apis.os.MMS_SENT_ACTION";
+ private static final String ACTION_MMS_RECEIVED =
+ "com.example.android.apis.os.MMS_RECEIVED_ACTION";
+
+ private EditText mRecipientsInput;
+ private EditText mSubjectInput;
+ private EditText mTextInput;
+ private TextView mSendStatusView;
+ private Button mSendButton;
+
+ private BroadcastReceiver mSentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleSentResult(getResultCode(), intent);
+ }
+ };
+ private IntentFilter mSentFilter = new IntentFilter(ACTION_MMS_SENT);
+
+ private BroadcastReceiver mReceivedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleReceivedResult(context, getResultCode(), intent);
+ }
+ };
+ private IntentFilter mReceivedFilter = new IntentFilter(ACTION_MMS_RECEIVED);
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ final String notificationIndUrl = intent.getStringExtra(EXTRA_NOTIFICATION_URL);
+ if (!TextUtils.isEmpty(notificationIndUrl)) {
+ downloadMessage(notificationIndUrl);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.mms_demo);
+
+ // Enable or disable the broadcast receiver depending on the checked
+ // state of the checkbox.
+ final CheckBox enableCheckBox = (CheckBox) findViewById(R.id.mms_enable_receiver);
+ final PackageManager pm = this.getPackageManager();
+ final ComponentName componentName = new ComponentName("com.example.android.apis",
+ "com.example.android.apis.os.MmsWapPushReceiver");
+ enableCheckBox.setChecked(pm.getComponentEnabledSetting(componentName) ==
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ enableCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Log.d(TAG, (isChecked ? "Enabling" : "Disabling") + " MMS receiver");
+ pm.setComponentEnabledSetting(componentName,
+ isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+ });
+
+ mRecipientsInput = (EditText) findViewById(R.id.mms_recipients_input);
+ mSubjectInput = (EditText) findViewById(R.id.mms_subject_input);
+ mTextInput = (EditText) findViewById(R.id.mms_text_input);
+ mSendStatusView = (TextView) findViewById(R.id.mms_send_status);
+ mSendButton = (Button) findViewById(R.id.mms_send_button);
+ mSendButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ sendMessage(
+ mRecipientsInput.getText().toString(),
+ mSubjectInput.getText().toString(),
+ mTextInput.getText().toString());
+ }
+ });
+ registerReceiver(mSentReceiver, mSentFilter);
+ registerReceiver(mReceivedReceiver, mReceivedFilter);
+ final Intent intent = getIntent();
+ final String notificationIndUrl = intent.getStringExtra(EXTRA_NOTIFICATION_URL);
+ if (!TextUtils.isEmpty(notificationIndUrl)) {
+ downloadMessage(notificationIndUrl);
+ }
+ }
+
+ private void sendMessage(final String recipients, final String subject, final String text) {
+ Log.d(TAG, "Sending");
+ mSendStatusView.setText(getResources().getString(R.string.mms_status_sending));
+ mSendButton.setEnabled(false);
+ // Making RPC call in non-UI thread
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ final byte[] pdu = buildPdu(MmsMessagingDemo.this, recipients, subject, text);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ MmsMessagingDemo.this, 0, new Intent(ACTION_MMS_SENT), 0);
+ SmsManager.getDefault().sendMultimediaMessage(
+ pdu, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
+ }
+ });
+ }
+
+ private void downloadMessage(final String locationUrl) {
+ Log.d(TAG, "Downloading " + locationUrl);
+ mSendStatusView.setText(getResources().getString(R.string.mms_status_downloading));
+ mSendButton.setEnabled(false);
+ mRecipientsInput.setText("");
+ mSubjectInput.setText("");
+ mTextInput.setText("");
+ // Making RPC call in non-UI thread
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ MmsMessagingDemo.this, 0, new Intent(ACTION_MMS_RECEIVED), 0);
+ SmsManager.getDefault().downloadMultimediaMessage(locationUrl,
+ null/*configOverrides*/, pendingIntent);
+ }
+ });
+ }
+
+ private void handleSentResult(int code, Intent intent) {
+ int status = R.string.mms_status_failed;
+ if (code == Activity.RESULT_OK) {
+ final byte[] response = intent.getByteArrayExtra(SmsManager.MMS_EXTRA_DATA);
+ if (response != null) {
+ final GenericPdu pdu = new PduParser(response).parse();
+ if (pdu instanceof SendConf) {
+ final SendConf sendConf = (SendConf) pdu;
+ if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
+ status = R.string.mms_status_sent;
+ } else {
+ Log.e(TAG, "MMS sent, error=" + sendConf.getResponseStatus());
+ }
+ } else {
+ Log.e(TAG, "MMS sent, invalid response");
+ }
+ } else {
+ Log.e(TAG, "MMS sent, empty response");
+ }
+ } else {
+ Log.e(TAG, "MMS not sent, error=" + code);
+ }
+ mSendStatusView.setText(status);
+ mSendButton.setEnabled(true);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mSentReceiver != null) {
+ unregisterReceiver(mSentReceiver);
+ }
+ if (mReceivedReceiver != null) {
+ unregisterReceiver(mReceivedReceiver);
+ }
+ }
+
+ private void handleReceivedResult(Context context, int code, Intent intent) {
+ int status = R.string.mms_status_failed;
+ if (code == Activity.RESULT_OK) {
+ final byte[] response = intent.getByteArrayExtra(SmsManager.MMS_EXTRA_DATA);
+ if (response != null) {
+ final GenericPdu pdu = new PduParser(response).parse();
+ if (pdu instanceof RetrieveConf) {
+ final RetrieveConf retrieveConf = (RetrieveConf) pdu;
+ mRecipientsInput.setText(getRecipients(context, retrieveConf));
+ mSubjectInput.setText(getSubject(retrieveConf));
+ mTextInput.setText(getMessageText(retrieveConf));
+ status = R.string.mms_status_downloaded;
+ } else {
+ Log.e(TAG, "MMS received, invalid response");
+ }
+ } else {
+ Log.e(TAG, "MMS received, empty response");
+ }
+ } else {
+ Log.e(TAG, "MMS not received, error=" + code);
+ }
+ mSendStatusView.setText(status);
+ mSendButton.setEnabled(true);
+ }
+
+ public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
+ public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
+
+ private static final String TEXT_PART_FILENAME = "text_0.txt";
+ private static final String sSmilText =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+ private static byte[] buildPdu(Context context, String recipients, String subject,
+ String text) {
+ final SendReq req = new SendReq();
+ // From, per spec
+ final String lineNumber = getSimNumber(context);
+ if (!TextUtils.isEmpty(lineNumber)) {
+ req.setFrom(new EncodedStringValue(lineNumber));
+ }
+ // To
+ EncodedStringValue[] encodedNumbers =
+ EncodedStringValue.encodeStrings(recipients.split(" "));
+ if (encodedNumbers != null) {
+ req.setTo(encodedNumbers);
+ }
+ // Subject
+ if (!TextUtils.isEmpty(subject)) {
+ req.setSubject(new EncodedStringValue(subject));
+ }
+ // Date
+ req.setDate(System.currentTimeMillis() / 1000);
+ // Body
+ PduBody body = new PduBody();
+ // Add text part. Always add a smil part for compatibility, without it there
+ // may be issues on some carriers/client apps
+ final int size = addTextPart(body, text, true/* add text smil */);
+ req.setBody(body);
+ // Message size
+ req.setMessageSize(size);
+ // Message class
+ req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
+ // Expiry
+ req.setExpiry(DEFAULT_EXPIRY_TIME);
+ try {
+ // Priority
+ req.setPriority(DEFAULT_PRIORITY);
+ // Delivery report
+ req.setDeliveryReport(PduHeaders.VALUE_NO);
+ // Read report
+ req.setReadReport(PduHeaders.VALUE_NO);
+ } catch (InvalidHeaderValueException e) {}
+
+ return new PduComposer(context, req).make();
+ }
+
+ private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
+ final PduPart part = new PduPart();
+ // Set Charset if it's a text media.
+ part.setCharset(CharacterSets.UTF_8);
+ // Set Content-Type.
+ part.setContentType(ContentType.TEXT_PLAIN.getBytes());
+ // Set Content-Location.
+ part.setContentLocation(TEXT_PART_FILENAME.getBytes());
+ int index = TEXT_PART_FILENAME.lastIndexOf(".");
+ String contentId = (index == -1) ? TEXT_PART_FILENAME
+ : TEXT_PART_FILENAME.substring(0, index);
+ part.setContentId(contentId.getBytes());
+ part.setData(message.getBytes());
+ pb.addPart(part);
+ if (addTextSmil) {
+ final String smil = String.format(sSmilText, TEXT_PART_FILENAME);
+ addSmilPart(pb, smil);
+ }
+ return part.getData().length;
+ }
+
+ private static void addSmilPart(PduBody pb, String smil) {
+ final PduPart smilPart = new PduPart();
+ smilPart.setContentId("smil".getBytes());
+ smilPart.setContentLocation("smil.xml".getBytes());
+ smilPart.setContentType(ContentType.APP_SMIL.getBytes());
+ smilPart.setData(smil.getBytes());
+ pb.addPart(0, smilPart);
+ }
+
+ private static String getRecipients(Context context, RetrieveConf retrieveConf) {
+ final String self = getSimNumber(context);
+ final StringBuilder sb = new StringBuilder();
+ if (retrieveConf.getFrom() != null) {
+ sb.append(retrieveConf.getFrom().getString());
+ }
+ if (retrieveConf.getTo() != null) {
+ for (EncodedStringValue to : retrieveConf.getTo()) {
+ final String number = to.getString();
+ if (!PhoneNumberUtils.compare(number, self)) {
+ sb.append(" ").append(to.getString());
+ }
+ }
+ }
+ if (retrieveConf.getCc() != null) {
+ for (EncodedStringValue cc : retrieveConf.getCc()) {
+ final String number = cc.getString();
+ if (!PhoneNumberUtils.compare(number, self)) {
+ sb.append(" ").append(cc.getString());
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String getSubject(RetrieveConf retrieveConf) {
+ final EncodedStringValue subject = retrieveConf.getSubject();
+ return subject != null ? subject.getString() : "";
+ }
+
+ private static String getMessageText(RetrieveConf retrieveConf) {
+ final StringBuilder sb = new StringBuilder();
+ final PduBody body = retrieveConf.getBody();
+ if (body != null) {
+ for (int i = 0; i < body.getPartsNum(); i++) {
+ final PduPart part = body.getPart(i);
+ if (part != null
+ && part.getContentType() != null
+ && ContentType.isTextType(new String(part.getContentType()))) {
+ sb.append(new String(part.getData()));
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String getSimNumber(Context context) {
+ final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephonyManager.getLine1Number();
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/MmsWapPushReceiver.java b/samples/ApiDemos/src/com/example/android/apis/os/MmsWapPushReceiver.java
new file mode 100644
index 000000000..a291e4aee
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/MmsWapPushReceiver.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 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.example.android.apis.os;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.NotificationInd;
+import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduParser;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Telephony;
+import android.util.Log;
+
+/**
+ * Receiver for MMS WAP push
+ */
+public class MmsWapPushReceiver extends BroadcastReceiver {
+ private static final String TAG = "MmsMessagingDemo";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction())
+ && ContentType.MMS_MESSAGE.equals(intent.getType())) {
+ final byte[] data = intent.getByteArrayExtra("data");
+ final PduParser parser = new PduParser(data);
+ GenericPdu pdu = null;
+ try {
+ pdu = parser.parse();
+ } catch (final RuntimeException e) {
+ Log.e(TAG, "Invalid MMS WAP push", e);
+ }
+ if (pdu == null) {
+ Log.e(TAG, "Invalid WAP push data");
+ return;
+ }
+ switch (pdu.getMessageType()) {
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: {
+ final NotificationInd nInd = (NotificationInd) pdu;
+ final String location = new String(nInd.getContentLocation());
+ Log.v(TAG, "Received MMS notification: " + location);
+ final Intent di = new Intent();
+ di.setClass(context, MmsMessagingDemo.class);
+ di.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ di.putExtra(MmsMessagingDemo.EXTRA_NOTIFICATION_URL, location);
+ context.startActivity(di);
+ break;
+ }
+ // FLAG (ywen): impl. handling of the following push
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: {
+ Log.v(TAG, "Received delivery report");
+ break;
+ }
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: {
+ Log.v(TAG, "Received read report");
+ break;
+ }
+ }
+ }
+ }
+}