1 /*
2  * Copyright (C) 2015 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 android.telephony.cts;
18 
19 import static androidx.test.InstrumentationRegistry.getContext;
20 
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.app.Activity;
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.net.Uri;
33 import android.os.SystemClock;
34 import android.telephony.SmsManager;
35 import android.telephony.TelephonyManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.google.android.mms.ContentType;
40 import com.google.android.mms.InvalidHeaderValueException;
41 import com.google.android.mms.pdu.CharacterSets;
42 import com.google.android.mms.pdu.EncodedStringValue;
43 import com.google.android.mms.pdu.GenericPdu;
44 import com.google.android.mms.pdu.PduBody;
45 import com.google.android.mms.pdu.PduComposer;
46 import com.google.android.mms.pdu.PduHeaders;
47 import com.google.android.mms.pdu.PduParser;
48 import com.google.android.mms.pdu.PduPart;
49 import com.google.android.mms.pdu.SendConf;
50 import com.google.android.mms.pdu.SendReq;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 
55 import java.io.File;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.util.Random;
59 
60 /**
61  * Test sending MMS using {@link android.telephony.SmsManager}.
62  */
63 public class MmsTest {
64     private static final String TAG = "MmsTest";
65 
66     private static final String ACTION_MMS_SENT = "CTS_MMS_SENT_ACTION";
67     private static final String ACTION_MMS_DOWNLOAD = "CTS_MMS_DOWNLOAD_ACTION";
68     private static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
69     private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
70     private static final long MESSAGE_ID = 912412L;
71 
72     private static final String SUBJECT = "CTS MMS Test";
73     private static final String MESSAGE_BODY = "CTS MMS test message body";
74     private static final String TEXT_PART_FILENAME = "text_0.txt";
75     private static final String sSmilText =
76             "<smil>" +
77                     "<head>" +
78                         "<layout>" +
79                             "<root-layout/>" +
80                             "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" +
81                         "</layout>" +
82                     "</head>" +
83                     "<body>" +
84                         "<par dur=\"8000ms\">" +
85                             "<text src=\"%s\" region=\"Text\"/>" +
86                         "</par>" +
87                     "</body>" +
88             "</smil>";
89 
90     private static final long SENT_TIMEOUT = 1000 * 60 * 5; // 5 minutes
91 
92     private static final String PROVIDER_AUTHORITY = "telephonyctstest";
93 
94     private Random mRandom;
95     private SentReceiver mSentReceiver;
96     private TelephonyManager mTelephonyManager;
97     private PackageManager mPackageManager;
98 
99     private static class SentReceiver extends BroadcastReceiver {
100         private final Object mLock;
101         private boolean mSuccess;
102         private boolean mDone;
103 
SentReceiver()104         public SentReceiver() {
105             mLock = new Object();
106             mSuccess = false;
107             mDone = false;
108         }
109 
110         @Override
onReceive(Context context, Intent intent)111         public void onReceive(Context context, Intent intent) {
112             Log.i(TAG, "Action " + intent.getAction());
113             if (!ACTION_MMS_SENT.equals(intent.getAction())) {
114                 return;
115             }
116             final int resultCode = getResultCode();
117             if (resultCode == Activity.RESULT_OK) {
118                 final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
119                 if (response != null) {
120                     final GenericPdu pdu = new PduParser(
121                             response, shouldParseContentDisposition()).parse();
122                     if (pdu != null && pdu instanceof SendConf) {
123                         final SendConf sendConf = (SendConf) pdu;
124                         if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
125                             mSuccess = true;
126                         } else {
127                             Log.e(TAG, "SendConf response status=" + sendConf.getResponseStatus());
128                         }
129                     } else {
130                         Log.e(TAG, "Not a SendConf: " +
131                                 (pdu != null ? pdu.getClass().getCanonicalName() : "NULL"));
132                     }
133                 } else {
134                     Log.e(TAG, "Empty response");
135                 }
136             } else {
137                 Log.e(TAG, "Failure result=" + resultCode);
138                 if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
139                     final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
140                     Log.e(TAG, "HTTP failure=" + httpError);
141                 }
142             }
143             synchronized (mLock) {
144                 mDone = true;
145                 mLock.notify();
146             }
147         }
148 
waitForSuccess(long timeout)149         public boolean waitForSuccess(long timeout) {
150             synchronized(mLock) {
151                 final long startTime = SystemClock.elapsedRealtime();
152                 long waitTime = timeout;
153                 while (!mDone && waitTime > 0) {
154                     try {
155                         mLock.wait(waitTime);
156                     } catch (InterruptedException e) {
157                         // Ignore
158                     }
159                     waitTime = timeout - (SystemClock.elapsedRealtime() - startTime);
160                 }
161                 Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
162                 return mDone && mSuccess;
163             }
164 
165         }
166     }
167 
168     @Before
setUp()169     public void setUp() throws Exception {
170         mRandom = new Random();
171         mTelephonyManager =
172                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
173         mPackageManager = getContext().getPackageManager();
174     }
175 
176     @Test
testSendMmsMessage()177     public void testSendMmsMessage() {
178         sendMmsMessage(0L /* messageId */);
179     }
180 
181     @Test
testSendMmsMessageWithMessageId()182     public void testSendMmsMessageWithMessageId() {
183         sendMmsMessage(MESSAGE_ID);
184     }
185 
sendMmsMessage(long messageId)186     private void sendMmsMessage(long messageId) {
187         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
188              || !doesSupportMMS()) {
189             Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
190             return;
191         }
192 
193         Log.i(TAG, "testSendMmsMessage");
194 
195         final Context context = getContext();
196         // Register sent receiver
197         mSentReceiver = new SentReceiver();
198         context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT));
199         // Create local provider file for sending PDU
200         final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
201         final File sendFile = new File(context.getCacheDir(), fileName);
202         final String selfNumber = getSimNumber(context);
203         assertTrue(!TextUtils.isEmpty(selfNumber));
204         final byte[] pdu = buildPdu(context, selfNumber, SUBJECT, MESSAGE_BODY);
205         assertNotNull(pdu);
206         assertTrue(writePdu(sendFile, pdu));
207         final Uri contentUri = (new Uri.Builder())
208                 .authority(PROVIDER_AUTHORITY)
209                 .path(fileName)
210                 .scheme(ContentResolver.SCHEME_CONTENT)
211                 .build();
212         // Send
213         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
214                 context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE);
215         if (messageId == 0L) {
216             SmsManager.getDefault().sendMultimediaMessage(context,
217                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
218         } else {
219             SmsManager.getDefault().sendMultimediaMessage(context,
220                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent,
221                     messageId);
222         }
223         assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
224         sendFile.delete();
225     }
226 
writePdu(File file, byte[] pdu)227     private static boolean writePdu(File file, byte[] pdu) {
228         FileOutputStream writer = null;
229         try {
230             writer = new FileOutputStream(file);
231             writer.write(pdu);
232             return true;
233         } catch (final IOException e) {
234             return false;
235         } finally {
236             if (writer != null) {
237                 try {
238                     writer.close();
239                 } catch (IOException e) {
240                 }
241             }
242         }
243     }
244 
buildPdu(Context context, String selfNumber, String subject, String text)245     private byte[] buildPdu(Context context, String selfNumber, String subject, String text) {
246         final SendReq req = new SendReq();
247         // From, per spec
248         req.setFrom(new EncodedStringValue(selfNumber));
249         // To
250         final String[] recipients = new String[1];
251         recipients[0] = selfNumber;
252         final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
253         if (encodedNumbers != null) {
254             req.setTo(encodedNumbers);
255         }
256         // Subject
257         if (!TextUtils.isEmpty(subject)) {
258             req.setSubject(new EncodedStringValue(subject));
259         }
260         // Date
261         req.setDate(System.currentTimeMillis() / 1000);
262         // Body
263         final PduBody body = new PduBody();
264         // Add text part. Always add a smil part for compatibility, without it there
265         // may be issues on some carriers/client apps
266         final int size = addTextPart(body, text, true/* add text smil */);
267         req.setBody(body);
268         // Message size
269         req.setMessageSize(size);
270         // Message class
271         req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
272         // Expiry
273         req.setExpiry(DEFAULT_EXPIRY_TIME);
274         // The following set methods throw InvalidHeaderValueException
275         try {
276             // Priority
277             req.setPriority(DEFAULT_PRIORITY);
278             // Delivery report
279             req.setDeliveryReport(PduHeaders.VALUE_NO);
280             // Read report
281             req.setReadReport(PduHeaders.VALUE_NO);
282         } catch (InvalidHeaderValueException e) {
283             return null;
284         }
285 
286         return new PduComposer(context, req).make();
287     }
288 
addTextPart(PduBody pb, String message, boolean addTextSmil)289     private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
290         final PduPart part = new PduPart();
291         // Set Charset if it's a text media.
292         part.setCharset(CharacterSets.UTF_8);
293         // Set Content-Type.
294         part.setContentType(ContentType.TEXT_PLAIN.getBytes());
295         // Set Content-Location.
296         part.setContentLocation(TEXT_PART_FILENAME.getBytes());
297         int index = TEXT_PART_FILENAME.lastIndexOf(".");
298         String contentId = (index == -1) ? TEXT_PART_FILENAME
299                 : TEXT_PART_FILENAME.substring(0, index);
300         part.setContentId(contentId.getBytes());
301         part.setData(message.getBytes());
302         pb.addPart(part);
303         if (addTextSmil) {
304             final String smil = String.format(sSmilText, TEXT_PART_FILENAME);
305             addSmilPart(pb, smil);
306         }
307         return part.getData().length;
308     }
309 
addSmilPart(PduBody pb, String smil)310     private static void addSmilPart(PduBody pb, String smil) {
311         final PduPart smilPart = new PduPart();
312         smilPart.setContentId("smil".getBytes());
313         smilPart.setContentLocation("smil.xml".getBytes());
314         smilPart.setContentType(ContentType.APP_SMIL.getBytes());
315         smilPart.setData(smil.getBytes());
316         pb.addPart(0, smilPart);
317     }
318 
getSimNumber(Context context)319     private static String getSimNumber(Context context) {
320         final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
321                 Context.TELEPHONY_SERVICE);
322         return telephonyManager.getLine1Number();
323     }
324 
shouldParseContentDisposition()325     private static boolean shouldParseContentDisposition() {
326         return SmsManager
327                 .getDefault()
328                 .getCarrierConfigValues()
329                 .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
330     }
331 
doesSupportMMS()332     private static boolean doesSupportMMS() {
333         return SmsManager
334                 .getDefault()
335                 .getCarrierConfigValues()
336                 .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true);
337     }
338 
339     @Test
testDownloadMultimediaMessage()340     public void testDownloadMultimediaMessage() {
341         downloadMultimediaMessage(0L /* messageId */);
342     }
343 
344     @Test
testDownloadMultimediaMessageWithMessageId()345     public void testDownloadMultimediaMessageWithMessageId() {
346         downloadMultimediaMessage(MESSAGE_ID);
347     }
348 
downloadMultimediaMessage(long messageId)349     private void downloadMultimediaMessage(long messageId) {
350         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
351                 || !doesSupportMMS()) {
352             Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
353             return;
354         }
355 
356         Log.i(TAG, "testSendMmsMessage");
357         // Prime the MmsService so that MMS config is loaded
358         final SmsManager smsManager = SmsManager.getDefault();
359         smsManager.getCarrierConfigValues();
360         // MMS config is loaded asynchronously. Wait a bit so it will be loaded.
361         try {
362             Thread.sleep(1000);
363         } catch (InterruptedException e) {
364             // Ignore
365         }
366 
367         final Context context = getContext();
368         // Create local provider file
369         final String fileName = "download." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
370         final File sendFile = new File(context.getCacheDir(), fileName);
371         final Uri contentUri = (new Uri.Builder())
372                 .authority(PROVIDER_AUTHORITY)
373                 .path(fileName)
374                 .scheme(ContentResolver.SCHEME_CONTENT)
375                 .build();
376 
377         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
378                 context, 0, new Intent(ACTION_MMS_DOWNLOAD),
379                 PendingIntent.FLAG_MUTABLE);
380 
381         if (messageId == 0L) {
382             // Verify the downloadMultimediaMessage function without messageId exists. This test
383             // doesn't actually verify downloading is successful, just that the function to
384             // initiate the downloading has been implemented.
385             smsManager.downloadMultimediaMessage(context, "foo/fake", contentUri,
386                     null /* configOverrides */, pendingIntent);
387         } else {
388             // Verify the downloadMultimediaMessage function with messageId exists. This test
389             // doesn't actually verify downloading is successful, just that the function to
390             // initiate the downloading has been implemented.
391             smsManager.downloadMultimediaMessage(context, "foo/fake", contentUri,
392                     null /* configOverrides */, pendingIntent, MESSAGE_ID);
393         }
394     }
395 }
396