• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onNewIntent(Intent intent)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
onCreate(Bundle savedInstanceState)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 
sendMessage(final String recipients, final String subject, final String text)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 
downloadMessage(final String locationUrl)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 
handleSentResult(int code, Intent intent)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
onDestroy()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 
handleReceivedResult(Context context, int code, Intent intent)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 
buildPdu(Context context, String recipients, String subject, String text)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 
addTextPart(PduBody pb, String message, boolean addTextSmil)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 
addSmilPart(PduBody pb, String smil)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 
getRecipients(Context context, RetrieveConf retrieveConf)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 
getSubject(RetrieveConf retrieveConf)430     private static String getSubject(RetrieveConf retrieveConf) {
431         final EncodedStringValue subject = retrieveConf.getSubject();
432         return subject != null ? subject.getString() : "";
433     }
434 
getMessageText(RetrieveConf retrieveConf)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 
getSimNumber(Context context)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