Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.apis.os;
     18 
     19 import com.google.android.mms.ContentType;
     20 import com.google.android.mms.InvalidHeaderValueException;
     21 import com.google.android.mms.pdu.CharacterSets;
     22 import com.google.android.mms.pdu.EncodedStringValue;
     23 import com.google.android.mms.pdu.GenericPdu;
     24 import com.google.android.mms.pdu.PduBody;
     25 import com.google.android.mms.pdu.PduComposer;
     26 import com.google.android.mms.pdu.PduHeaders;
     27 import com.google.android.mms.pdu.PduParser;
     28 import com.google.android.mms.pdu.PduPart;
     29 import com.google.android.mms.pdu.RetrieveConf;
     30 import com.google.android.mms.pdu.SendConf;
     31 import com.google.android.mms.pdu.SendReq;
     32 
     33 import android.app.Activity;
     34 import android.app.PendingIntent;
     35 import android.app.PendingIntent.CanceledException;
     36 import android.content.BroadcastReceiver;
     37 import android.content.ComponentName;
     38 import android.content.ContentResolver;
     39 import android.content.Context;
     40 import android.content.Intent;
     41 import android.content.IntentFilter;
     42 import android.content.pm.PackageManager;
     43 import android.net.Uri;
     44 import android.os.AsyncTask;
     45 import android.os.Bundle;
     46 import android.os.ParcelFileDescriptor;
     47 import android.telephony.PhoneNumberUtils;
     48 import android.telephony.SmsManager;
     49 import android.telephony.TelephonyManager;
     50 import android.text.TextUtils;
     51 import android.util.Log;
     52 import android.view.View;
     53 import android.widget.Button;
     54 import android.widget.CheckBox;
     55 import android.widget.CompoundButton;
     56 import android.widget.EditText;
     57 import android.widget.TextView;
     58 
     59 import com.example.android.apis.R;
     60 
     61 import java.io.File;
     62 import java.io.FileInputStream;
     63 import java.io.FileOutputStream;
     64 import java.io.FileNotFoundException;
     65 import java.io.IOException;
     66 import java.util.Random;
     67 
     68 public class MmsMessagingDemo extends Activity {
     69     private static final String TAG = "MmsMessagingDemo";
     70 
     71     public static final String EXTRA_NOTIFICATION_URL = "notification_url";
     72 
     73     private static final String ACTION_MMS_SENT = "com.example.android.apis.os.MMS_SENT_ACTION";
     74     private static final String ACTION_MMS_RECEIVED =
     75             "com.example.android.apis.os.MMS_RECEIVED_ACTION";
     76 
     77     private EditText mRecipientsInput;
     78     private EditText mSubjectInput;
     79     private EditText mTextInput;
     80     private TextView mSendStatusView;
     81     private Button mSendButton;
     82     private File mSendFile;
     83     private File mDownloadFile;
     84     private Random mRandom = new Random();
     85 
     86     private BroadcastReceiver mSentReceiver = new BroadcastReceiver() {
     87         @Override
     88         public void onReceive(Context context, Intent intent) {
     89             handleSentResult(getResultCode(), intent);
     90         }
     91     };
     92     private IntentFilter mSentFilter = new IntentFilter(ACTION_MMS_SENT);
     93 
     94     private BroadcastReceiver mReceivedReceiver = new BroadcastReceiver() {
     95         @Override
     96         public void onReceive(Context context, Intent intent) {
     97             handleReceivedResult(context, getResultCode(), intent);
     98         }
     99     };
    100     private IntentFilter mReceivedFilter = new IntentFilter(ACTION_MMS_RECEIVED);
    101 
    102     @Override
    103     protected void onNewIntent(Intent intent) {
    104         super.onNewIntent(intent);
    105         final String notificationIndUrl = intent.getStringExtra(EXTRA_NOTIFICATION_URL);
    106         if (!TextUtils.isEmpty(notificationIndUrl)) {
    107             downloadMessage(notificationIndUrl);
    108         }
    109     }
    110 
    111     @Override
    112     protected void onCreate(Bundle savedInstanceState) {
    113         super.onCreate(savedInstanceState);
    114         setContentView(R.layout.mms_demo);
    115 
    116         // Enable or disable the broadcast receiver depending on the checked
    117         // state of the checkbox.
    118         final CheckBox enableCheckBox = (CheckBox) findViewById(R.id.mms_enable_receiver);
    119         final PackageManager pm = this.getPackageManager();
    120         final ComponentName componentName = new ComponentName("com.example.android.apis",
    121                 "com.example.android.apis.os.MmsWapPushReceiver");
    122         enableCheckBox.setChecked(pm.getComponentEnabledSetting(componentName) ==
    123                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
    124         enableCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    125             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    126                 Log.d(TAG, (isChecked ? "Enabling" : "Disabling") + " MMS receiver");
    127                 pm.setComponentEnabledSetting(componentName,
    128                         isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
    129                                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
    130                         PackageManager.DONT_KILL_APP);
    131             }
    132         });
    133 
    134         mRecipientsInput = (EditText) findViewById(R.id.mms_recipients_input);
    135         mSubjectInput = (EditText) findViewById(R.id.mms_subject_input);
    136         mTextInput = (EditText) findViewById(R.id.mms_text_input);
    137         mSendStatusView = (TextView) findViewById(R.id.mms_send_status);
    138         mSendButton = (Button) findViewById(R.id.mms_send_button);
    139         mSendButton.setOnClickListener(new View.OnClickListener() {
    140             @Override
    141             public void onClick(View v) {
    142                 sendMessage(
    143                         mRecipientsInput.getText().toString(),
    144                         mSubjectInput.getText().toString(),
    145                         mTextInput.getText().toString());
    146             }
    147         });
    148         registerReceiver(mSentReceiver, mSentFilter);
    149         registerReceiver(mReceivedReceiver, mReceivedFilter);
    150         final Intent intent = getIntent();
    151         final String notificationIndUrl = intent.getStringExtra(EXTRA_NOTIFICATION_URL);
    152         if (!TextUtils.isEmpty(notificationIndUrl)) {
    153             downloadMessage(notificationIndUrl);
    154         }
    155     }
    156 
    157     private void sendMessage(final String recipients, final String subject, final String text) {
    158         Log.d(TAG, "Sending");
    159         mSendStatusView.setText(getResources().getString(R.string.mms_status_sending));
    160         mSendButton.setEnabled(false);
    161         final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
    162         mSendFile = new File(getCacheDir(), fileName);
    163 
    164         // Making RPC call in non-UI thread
    165         AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    166             @Override
    167             public void run() {
    168                 final byte[] pdu = buildPdu(MmsMessagingDemo.this, recipients, subject, text);
    169                 Uri writerUri = (new Uri.Builder())
    170                        .authority("com.example.android.apis.os.MmsFileProvider")
    171                        .path(fileName)
    172                        .scheme(ContentResolver.SCHEME_CONTENT)
    173                        .build();
    174                 final PendingIntent pendingIntent = PendingIntent.getBroadcast(
    175                         MmsMessagingDemo.this, 0, new Intent(ACTION_MMS_SENT), 0);
    176                 FileOutputStream writer = null;
    177                 Uri contentUri = null;
    178                 try {
    179                     writer = new FileOutputStream(mSendFile);
    180                     writer.write(pdu);
    181                     contentUri = writerUri;
    182                 } catch (final IOException e) {
    183                     Log.e(TAG, "Error writing send file", e);
    184                 } finally {
    185                     if (writer != null) {
    186                         try {
    187                             writer.close();
    188                         } catch (IOException e) {
    189                         }
    190                     }
    191                 }
    192 
    193                 if (contentUri != null) {
    194                     SmsManager.getDefault().sendMultimediaMessage(getApplicationContext(),
    195                             contentUri, null/*locationUrl*/, null/*configOverrides*/,
    196                             pendingIntent);
    197                 } else {
    198                     Log.e(TAG, "Error writing sending Mms");
    199                     try {
    200                         pendingIntent.send(SmsManager.MMS_ERROR_IO_ERROR);
    201                     } catch (CanceledException ex) {
    202                         Log.e(TAG, "Mms pending intent cancelled?", ex);
    203                     }
    204                 }
    205             }
    206         });
    207     }
    208 
    209     private void downloadMessage(final String locationUrl) {
    210         Log.d(TAG, "Downloading " + locationUrl);
    211         mSendStatusView.setText(getResources().getString(R.string.mms_status_downloading));
    212         mSendButton.setEnabled(false);
    213         mRecipientsInput.setText("");
    214         mSubjectInput.setText("");
    215         mTextInput.setText("");
    216         final String fileName = "download." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
    217         mDownloadFile = new File(getCacheDir(), fileName);
    218         // Making RPC call in non-UI thread
    219         AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    220             @Override
    221             public void run() {
    222                 Uri contentUri = (new Uri.Builder())
    223                         .authority("com.example.android.apis.os.MmsFileProvider")
    224                         .path(fileName)
    225                         .scheme(ContentResolver.SCHEME_CONTENT)
    226                         .build();
    227                 final PendingIntent pendingIntent = PendingIntent.getBroadcast(
    228                         MmsMessagingDemo.this, 0, new Intent(ACTION_MMS_RECEIVED), 0);
    229                 SmsManager.getDefault().downloadMultimediaMessage(getApplicationContext(),
    230                         locationUrl, contentUri, null/*configOverrides*/, pendingIntent);
    231             }
    232         });
    233     }
    234 
    235     private void handleSentResult(int code, Intent intent) {
    236         mSendFile.delete();
    237         int status = R.string.mms_status_failed;
    238         if (code == Activity.RESULT_OK) {
    239             final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
    240             if (response != null) {
    241                 final GenericPdu pdu = new PduParser(response).parse();
    242                 if (pdu instanceof SendConf) {
    243                     final SendConf sendConf = (SendConf) pdu;
    244                     if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
    245                         status = R.string.mms_status_sent;
    246                     } else {
    247                         Log.e(TAG, "MMS sent, error=" + sendConf.getResponseStatus());
    248                     }
    249                 } else {
    250                     Log.e(TAG, "MMS sent, invalid response");
    251                 }
    252             } else {
    253                 Log.e(TAG, "MMS sent, empty response");
    254             }
    255         } else {
    256             Log.e(TAG, "MMS not sent, error=" + code);
    257         }
    258 
    259         mSendFile = null;
    260         mSendStatusView.setText(status);
    261         mSendButton.setEnabled(true);
    262     }
    263 
    264     @Override
    265     protected void onDestroy() {
    266         super.onDestroy();
    267         if (mSentReceiver != null) {
    268             unregisterReceiver(mSentReceiver);
    269         }
    270         if (mReceivedReceiver != null) {
    271             unregisterReceiver(mReceivedReceiver);
    272         }
    273     }
    274 
    275     private void handleReceivedResult(Context context, int code, Intent intent) {
    276         int status = R.string.mms_status_failed;
    277         if (code == Activity.RESULT_OK) {
    278             try {
    279                 final int nBytes = (int) mDownloadFile.length();
    280                 FileInputStream reader = new FileInputStream(mDownloadFile);
    281                 final byte[] response = new byte[nBytes];
    282                 final int read = reader.read(response, 0, nBytes);
    283                 if (read == nBytes) {
    284                     final GenericPdu pdu = new PduParser(response).parse();
    285                     if (pdu instanceof RetrieveConf) {
    286                         final RetrieveConf retrieveConf = (RetrieveConf) pdu;
    287                         mRecipientsInput.setText(getRecipients(context, retrieveConf));
    288                         mSubjectInput.setText(getSubject(retrieveConf));
    289                         mTextInput.setText(getMessageText(retrieveConf));
    290                         status = R.string.mms_status_downloaded;
    291                     } else {
    292                         Log.e(TAG, "MMS received, invalid response");
    293                     }
    294                 } else {
    295                     Log.e(TAG, "MMS received, empty response");
    296                 }
    297             } catch (FileNotFoundException e) {
    298                 Log.e(TAG, "MMS received, file not found exception", e);
    299             } catch (IOException e) {
    300                 Log.e(TAG, "MMS received, io exception", e);
    301             } finally {
    302                 mDownloadFile.delete();
    303             }
    304         } else {
    305             Log.e(TAG, "MMS not received, error=" + code);
    306         }
    307         mDownloadFile = null;
    308         mSendStatusView.setText(status);
    309         mSendButton.setEnabled(true);
    310     }
    311 
    312     public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
    313     public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
    314 
    315     private static final String TEXT_PART_FILENAME = "text_0.txt";
    316     private static final String sSmilText =
    317             "<smil>" +
    318                 "<head>" +
    319                     "<layout>" +
    320                         "<root-layout/>" +
    321                         "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" +
    322                     "</layout>" +
    323                 "</head>" +
    324                 "<body>" +
    325                     "<par dur=\"8000ms\">" +
    326                         "<text src=\"%s\" region=\"Text\"/>" +
    327                     "</par>" +
    328                 "</body>" +
    329             "</smil>";
    330 
    331     private static byte[] buildPdu(Context context, String recipients, String subject,
    332             String text) {
    333         final SendReq req = new SendReq();
    334         // From, per spec
    335         final String lineNumber = getSimNumber(context);
    336         if (!TextUtils.isEmpty(lineNumber)) {
    337             req.setFrom(new EncodedStringValue(lineNumber));
    338         }
    339         // To
    340         EncodedStringValue[] encodedNumbers =
    341                 EncodedStringValue.encodeStrings(recipients.split(" "));
    342         if (encodedNumbers != null) {
    343             req.setTo(encodedNumbers);
    344         }
    345         // Subject
    346         if (!TextUtils.isEmpty(subject)) {
    347             req.setSubject(new EncodedStringValue(subject));
    348         }
    349         // Date
    350         req.setDate(System.currentTimeMillis() / 1000);
    351         // Body
    352         PduBody body = new PduBody();
    353         // Add text part. Always add a smil part for compatibility, without it there
    354         // may be issues on some carriers/client apps
    355         final int size = addTextPart(body, text, true/* add text smil */);
    356         req.setBody(body);
    357         // Message size
    358         req.setMessageSize(size);
    359         // Message class
    360         req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
    361         // Expiry
    362         req.setExpiry(DEFAULT_EXPIRY_TIME);
    363         try {
    364             // Priority
    365             req.setPriority(DEFAULT_PRIORITY);
    366             // Delivery report
    367             req.setDeliveryReport(PduHeaders.VALUE_NO);
    368             // Read report
    369             req.setReadReport(PduHeaders.VALUE_NO);
    370         } catch (InvalidHeaderValueException e) {}
    371 
    372         return new PduComposer(context, req).make();
    373     }
    374 
    375     private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
    376         final PduPart part = new PduPart();
    377         // Set Charset if it's a text media.
    378         part.setCharset(CharacterSets.UTF_8);
    379         // Set Content-Type.
    380         part.setContentType(ContentType.TEXT_PLAIN.getBytes());
    381         // Set Content-Location.
    382         part.setContentLocation(TEXT_PART_FILENAME.getBytes());
    383         int index = TEXT_PART_FILENAME.lastIndexOf(".");
    384         String contentId = (index == -1) ? TEXT_PART_FILENAME
    385                 : TEXT_PART_FILENAME.substring(0, index);
    386         part.setContentId(contentId.getBytes());
    387         part.setData(message.getBytes());
    388         pb.addPart(part);
    389         if (addTextSmil) {
    390             final String smil = String.format(sSmilText, TEXT_PART_FILENAME);
    391             addSmilPart(pb, smil);
    392         }
    393         return part.getData().length;
    394     }
    395 
    396     private static void addSmilPart(PduBody pb, String smil) {
    397         final PduPart smilPart = new PduPart();
    398         smilPart.setContentId("smil".getBytes());
    399         smilPart.setContentLocation("smil.xml".getBytes());
    400         smilPart.setContentType(ContentType.APP_SMIL.getBytes());
    401         smilPart.setData(smil.getBytes());
    402         pb.addPart(0, smilPart);
    403     }
    404 
    405     private static String getRecipients(Context context, RetrieveConf retrieveConf) {
    406         final String self = getSimNumber(context);
    407         final StringBuilder sb = new StringBuilder();
    408         if (retrieveConf.getFrom() != null) {
    409             sb.append(retrieveConf.getFrom().getString());
    410         }
    411         if (retrieveConf.getTo() != null) {
    412             for (EncodedStringValue to : retrieveConf.getTo()) {
    413                 final String number = to.getString();
    414                 if (!PhoneNumberUtils.compare(number, self)) {
    415                     sb.append(" ").append(to.getString());
    416                 }
    417             }
    418         }
    419         if (retrieveConf.getCc() != null) {
    420             for (EncodedStringValue cc : retrieveConf.getCc()) {
    421                 final String number = cc.getString();
    422                 if (!PhoneNumberUtils.compare(number, self)) {
    423                     sb.append(" ").append(cc.getString());
    424                 }
    425             }
    426         }
    427         return sb.toString();
    428     }
    429 
    430     private static String getSubject(RetrieveConf retrieveConf) {
    431         final EncodedStringValue subject = retrieveConf.getSubject();
    432         return subject != null ? subject.getString() : "";
    433     }
    434 
    435     private static String getMessageText(RetrieveConf retrieveConf) {
    436         final StringBuilder sb = new StringBuilder();
    437         final PduBody body = retrieveConf.getBody();
    438         if (body != null) {
    439             for (int i = 0; i < body.getPartsNum(); i++) {
    440                 final PduPart part = body.getPart(i);
    441                 if (part != null
    442                         && part.getContentType() != null
    443                         && ContentType.isTextType(new String(part.getContentType()))) {
    444                     sb.append(new String(part.getData()));
    445                 }
    446             }
    447         }
    448         return sb.toString();
    449     }
    450 
    451     private static String getSimNumber(Context context) {
    452         final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
    453                 Context.TELEPHONY_SERVICE);
    454         return telephonyManager.getLine1Number();
    455     }
    456 }
    457