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