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.example.android.mmslib.ContentType;
     20 import com.example.android.mmslib.InvalidHeaderValueException;
     21 import com.example.android.mmslib.pdu.CharacterSets;
     22 import com.example.android.mmslib.pdu.EncodedStringValue;
     23 import com.example.android.mmslib.pdu.GenericPdu;
     24 import com.example.android.mmslib.pdu.PduBody;
     25 import com.example.android.mmslib.pdu.PduComposer;
     26 import com.example.android.mmslib.pdu.PduHeaders;
     27 import com.example.android.mmslib.pdu.PduParser;
     28 import com.example.android.mmslib.pdu.PduPart;
     29 import com.example.android.mmslib.pdu.RetrieveConf;
     30 import com.example.android.mmslib.pdu.SendConf;
     31 import com.example.android.mmslib.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(
    242                         response, PduParserUtil.shouldParseContentDisposition()).parse();
    243                 if (pdu instanceof SendConf) {
    244                     final SendConf sendConf = (SendConf) pdu;
    245                     if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
    246                         status = R.string.mms_status_sent;
    247                     } else {
    248                         Log.e(TAG, "MMS sent, error=" + sendConf.getResponseStatus());
    249                     }
    250                 } else {
    251                     Log.e(TAG, "MMS sent, invalid response");
    252                 }
    253             } else {
    254                 Log.e(TAG, "MMS sent, empty response");
    255             }
    256         } else {
    257             Log.e(TAG, "MMS not sent, error=" + code);
    258         }
    259 
    260         mSendFile = null;
    261         mSendStatusView.setText(status);
    262         mSendButton.setEnabled(true);
    263     }
    264 
    265     @Override
    266     protected void onDestroy() {
    267         super.onDestroy();
    268         if (mSentReceiver != null) {
    269             unregisterReceiver(mSentReceiver);
    270         }
    271         if (mReceivedReceiver != null) {
    272             unregisterReceiver(mReceivedReceiver);
    273         }
    274     }
    275 
    276     private void handleReceivedResult(Context context, int code, Intent intent) {
    277         int status = R.string.mms_status_failed;
    278         if (code == Activity.RESULT_OK) {
    279             try {
    280                 final int nBytes = (int) mDownloadFile.length();
    281                 FileInputStream reader = new FileInputStream(mDownloadFile);
    282                 final byte[] response = new byte[nBytes];
    283                 final int read = reader.read(response, 0, nBytes);
    284                 if (read == nBytes) {
    285                     final GenericPdu pdu = new PduParser(
    286                             response, PduParserUtil.shouldParseContentDisposition()).parse();
    287                     if (pdu instanceof RetrieveConf) {
    288                         final RetrieveConf retrieveConf = (RetrieveConf) pdu;
    289                         mRecipientsInput.setText(getRecipients(context, retrieveConf));
    290                         mSubjectInput.setText(getSubject(retrieveConf));
    291                         mTextInput.setText(getMessageText(retrieveConf));
    292                         status = R.string.mms_status_downloaded;
    293                     } else {
    294                         Log.e(TAG, "MMS received, invalid response");
    295                     }
    296                 } else {
    297                     Log.e(TAG, "MMS received, empty response");
    298                 }
    299             } catch (FileNotFoundException e) {
    300                 Log.e(TAG, "MMS received, file not found exception", e);
    301             } catch (IOException e) {
    302                 Log.e(TAG, "MMS received, io exception", e);
    303             } finally {
    304                 mDownloadFile.delete();
    305             }
    306         } else {
    307             Log.e(TAG, "MMS not received, error=" + code);
    308         }
    309         mDownloadFile = null;
    310         mSendStatusView.setText(status);
    311         mSendButton.setEnabled(true);
    312     }
    313 
    314     public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
    315     public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
    316 
    317     private static final String TEXT_PART_FILENAME = "text_0.txt";
    318     private static final String sSmilText =
    319             "<smil>" +
    320                 "<head>" +
    321                     "<layout>" +
    322                         "<root-layout/>" +
    323                         "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" +
    324                     "</layout>" +
    325                 "</head>" +
    326                 "<body>" +
    327                     "<par dur=\"8000ms\">" +
    328                         "<text src=\"%s\" region=\"Text\"/>" +
    329                     "</par>" +
    330                 "</body>" +
    331             "</smil>";
    332 
    333     private static byte[] buildPdu(Context context, String recipients, String subject,
    334             String text) {
    335         final SendReq req = new SendReq();
    336         // From, per spec
    337         final String lineNumber = getSimNumber(context);
    338         if (!TextUtils.isEmpty(lineNumber)) {
    339             req.setFrom(new EncodedStringValue(lineNumber));
    340         }
    341         // To
    342         EncodedStringValue[] encodedNumbers =
    343                 EncodedStringValue.encodeStrings(recipients.split(" "));
    344         if (encodedNumbers != null) {
    345             req.setTo(encodedNumbers);
    346         }
    347         // Subject
    348         if (!TextUtils.isEmpty(subject)) {
    349             req.setSubject(new EncodedStringValue(subject));
    350         }
    351         // Date
    352         req.setDate(System.currentTimeMillis() / 1000);
    353         // Body
    354         PduBody body = new PduBody();
    355         // Add text part. Always add a smil part for compatibility, without it there
    356         // may be issues on some carriers/client apps
    357         final int size = addTextPart(body, text, true/* add text smil */);
    358         req.setBody(body);
    359         // Message size
    360         req.setMessageSize(size);
    361         // Message class
    362         req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
    363         // Expiry
    364         req.setExpiry(DEFAULT_EXPIRY_TIME);
    365         try {
    366             // Priority
    367             req.setPriority(DEFAULT_PRIORITY);
    368             // Delivery report
    369             req.setDeliveryReport(PduHeaders.VALUE_NO);
    370             // Read report
    371             req.setReadReport(PduHeaders.VALUE_NO);
    372         } catch (InvalidHeaderValueException e) {}
    373 
    374         return new PduComposer(context, req).make();
    375     }
    376 
    377     private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
    378         final PduPart part = new PduPart();
    379         // Set Charset if it's a text media.
    380         part.setCharset(CharacterSets.UTF_8);
    381         // Set Content-Type.
    382         part.setContentType(ContentType.TEXT_PLAIN.getBytes());
    383         // Set Content-Location.
    384         part.setContentLocation(TEXT_PART_FILENAME.getBytes());
    385         int index = TEXT_PART_FILENAME.lastIndexOf(".");
    386         String contentId = (index == -1) ? TEXT_PART_FILENAME
    387                 : TEXT_PART_FILENAME.substring(0, index);
    388         part.setContentId(contentId.getBytes());
    389         part.setData(message.getBytes());
    390         pb.addPart(part);
    391         if (addTextSmil) {
    392             final String smil = String.format(sSmilText, TEXT_PART_FILENAME);
    393             addSmilPart(pb, smil);
    394         }
    395         return part.getData().length;
    396     }
    397 
    398     private static void addSmilPart(PduBody pb, String smil) {
    399         final PduPart smilPart = new PduPart();
    400         smilPart.setContentId("smil".getBytes());
    401         smilPart.setContentLocation("smil.xml".getBytes());
    402         smilPart.setContentType(ContentType.APP_SMIL.getBytes());
    403         smilPart.setData(smil.getBytes());
    404         pb.addPart(0, smilPart);
    405     }
    406 
    407     private static String getRecipients(Context context, RetrieveConf retrieveConf) {
    408         final String self = getSimNumber(context);
    409         final StringBuilder sb = new StringBuilder();
    410         if (retrieveConf.getFrom() != null) {
    411             sb.append(retrieveConf.getFrom().getString());
    412         }
    413         if (retrieveConf.getTo() != null) {
    414             for (EncodedStringValue to : retrieveConf.getTo()) {
    415                 final String number = to.getString();
    416                 if (!PhoneNumberUtils.compare(number, self)) {
    417                     sb.append(" ").append(to.getString());
    418                 }
    419             }
    420         }
    421         if (retrieveConf.getCc() != null) {
    422             for (EncodedStringValue cc : retrieveConf.getCc()) {
    423                 final String number = cc.getString();
    424                 if (!PhoneNumberUtils.compare(number, self)) {
    425                     sb.append(" ").append(cc.getString());
    426                 }
    427             }
    428         }
    429         return sb.toString();
    430     }
    431 
    432     private static String getSubject(RetrieveConf retrieveConf) {
    433         final EncodedStringValue subject = retrieveConf.getSubject();
    434         return subject != null ? subject.getString() : "";
    435     }
    436 
    437     private static String getMessageText(RetrieveConf retrieveConf) {
    438         final StringBuilder sb = new StringBuilder();
    439         final PduBody body = retrieveConf.getBody();
    440         if (body != null) {
    441             for (int i = 0; i < body.getPartsNum(); i++) {
    442                 final PduPart part = body.getPart(i);
    443                 if (part != null
    444                         && part.getContentType() != null
    445                         && ContentType.isTextType(new String(part.getContentType()))) {
    446                     sb.append(new String(part.getData()));
    447                 }
    448             }
    449         }
    450         return sb.toString();
    451     }
    452 
    453     private static String getSimNumber(Context context) {
    454         final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
    455                 Context.TELEPHONY_SERVICE);
    456         return telephonyManager.getLine1Number();
    457     }
    458 }
    459