1 package android.location.cts; 2 3 import android.app.Activity; 4 import android.app.PendingIntent; 5 import android.content.BroadcastReceiver; 6 import android.content.ContentResolver; 7 import android.content.Context; 8 import android.content.Intent; 9 import android.content.IntentFilter; 10 import android.content.pm.PackageManager; 11 import android.net.Uri; 12 import android.os.SystemClock; 13 import android.telephony.SmsManager; 14 import android.telephony.TelephonyManager; 15 import android.test.AndroidTestCase; 16 import android.text.TextUtils; 17 import android.util.Log; 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.SendConf; 30 import com.google.android.mms.pdu.SendReq; 31 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.util.Random; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.TimeUnit; 38 39 /** 40 * Test sending SMS and MMS using {@link android.telephony.SmsManager}. 41 */ 42 public class EmergencyCallMessageTest extends GnssTestCase { 43 44 private static final String TAG = "EmergencyCallMSGTest"; 45 46 private static final String ACTION_MMS_SENT = "CTS_MMS_SENT_ACTION"; 47 private static final long DEFAULT_EXPIRY_TIME_SECS = TimeUnit.DAYS.toSeconds(7); 48 private static final long MMS_CONFIG_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1); 49 private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; 50 private static final short DEFAULT_DATA_SMS_PORT = 8091; 51 private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber"; 52 private static final String SUBJECT = "CTS Emergency Call MMS Test"; 53 private static final String MMS_MESSAGE_BODY = "CTS Emergency Call MMS test message body"; 54 private static final String SMS_MESSAGE_BODY = "CTS Emergency Call Sms test message body"; 55 private static final String SMS_DATA_MESSAGE_BODY = 56 "CTS Emergency Call Sms data test message body"; 57 private static final String TEXT_PART_FILENAME = "text_0.txt"; 58 private static final String SMIL_TEXT = 59 "<smil>" + 60 "<head>" + 61 "<layout>" + 62 "<root-layout/>" + 63 "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" + 64 "</layout>" + 65 "</head>" + 66 "<body>" + 67 "<par dur=\"8000ms\">" + 68 "<text src=\"%s\" region=\"Text\"/>" + 69 "</par>" + 70 "</body>" + 71 "</smil>"; 72 73 private static final long SENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); // 5 minutes 74 75 private static final String PROVIDER_AUTHORITY = "emergencycallverifier"; 76 77 private Random mRandom; 78 private SentReceiver mSentReceiver; 79 private TelephonyManager mTelephonyManager; 80 private PackageManager mPackageManager; 81 82 private static class SentReceiver extends BroadcastReceiver { 83 private boolean mSuccess; 84 private boolean mDone; 85 private final CountDownLatch mLatch; 86 public SentReceiver() { 87 mLatch = new CountDownLatch(1); 88 mSuccess = false; 89 mDone = false; 90 } 91 92 @Override 93 public void onReceive(Context context, Intent intent) { 94 Log.i(TAG, "Action " + intent.getAction()); 95 if (!ACTION_MMS_SENT.equals(intent.getAction())) { 96 return; 97 } 98 final int resultCode = getResultCode(); 99 if (resultCode == Activity.RESULT_OK) { 100 final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA); 101 if (response != null) { 102 final GenericPdu pdu = new PduParser( 103 response, shouldParseContentDisposition()).parse(); 104 if (pdu != null && pdu instanceof SendConf) { 105 final SendConf sendConf = (SendConf) pdu; 106 if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) { 107 mSuccess = true; 108 } else { 109 Log.e(TAG, "SendConf response status=" + sendConf.getResponseStatus()); 110 } 111 } else { 112 Log.e(TAG, "Not a SendConf: " + 113 (pdu != null ? pdu.getClass().getCanonicalName() : "NULL")); 114 } 115 } else { 116 Log.e(TAG, "Empty response"); 117 } 118 } else { 119 Log.e(TAG, "Failure result=" + resultCode); 120 if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) { 121 final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0); 122 Log.e(TAG, "HTTP failure=" + httpError); 123 } 124 } 125 mDone = true; 126 mLatch.countDown(); 127 } 128 129 public boolean waitForSuccess(long timeoutMs) throws Exception { 130 mLatch.await(timeoutMs, TimeUnit.MILLISECONDS); 131 Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess); 132 return mDone && mSuccess; 133 } 134 } 135 136 @Override 137 protected void setUp() throws Exception { 138 super.setUp(); 139 140 mRandom = new Random(System.currentTimeMillis()); 141 mTelephonyManager = 142 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 143 mPackageManager = mContext.getPackageManager(); 144 } 145 146 public void testSendSmsMessage() { 147 // this test is only for cts verifier 148 if (!isCtsVerifierTest()) { 149 return; 150 } 151 SmsManager smsManager = SmsManager.getDefault(); 152 final String selfNumber = getPhoneNumber(mContext); 153 smsManager.sendTextMessage(selfNumber, null, SMS_MESSAGE_BODY, null, null); 154 } 155 156 public void testSendSmsDataMessage() { 157 // this test is only for cts verifier 158 if (!isCtsVerifierTest()) { 159 return; 160 } 161 SmsManager smsManager = SmsManager.getDefault(); 162 final String selfNumber = getPhoneNumber(mContext); 163 smsManager.sendDataMessage(selfNumber, null, DEFAULT_DATA_SMS_PORT, 164 SMS_DATA_MESSAGE_BODY.getBytes(), null, null); 165 } 166 167 public void testSendMmsMessage() throws Exception { 168 // this test is only for cts verifier 169 if (!isCtsVerifierTest()) { 170 return; 171 } 172 if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 173 || !doesSupportMMS()) { 174 Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported"); 175 return; 176 } 177 178 Log.i(TAG, "testSendMmsMessage"); 179 // Prime the MmsService so that MMS config is loaded 180 final SmsManager smsManager = SmsManager.getDefault(); 181 // MMS config is loaded asynchronously. Wait a bit so it will be loaded. 182 try { 183 Thread.sleep(MMS_CONFIG_DELAY_MILLIS); 184 } catch (InterruptedException e) { 185 // Ignore 186 } 187 188 final Context context = getContext(); 189 // Register sent receiver 190 mSentReceiver = new SentReceiver(); 191 context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT)); 192 // Create local provider file for sending PDU 193 final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat"; 194 final File sendFile = new File(context.getCacheDir(), fileName); 195 final String selfNumber = getPhoneNumber(context); 196 assertTrue(!TextUtils.isEmpty(selfNumber)); 197 final byte[] pdu = buildPdu(context, selfNumber, SUBJECT, MMS_MESSAGE_BODY); 198 assertNotNull(pdu); 199 assertTrue(writePdu(sendFile, pdu)); 200 final Uri contentUri = (new Uri.Builder()) 201 .authority(PROVIDER_AUTHORITY) 202 .path(fileName) 203 .scheme(ContentResolver.SCHEME_CONTENT) 204 .build(); 205 // Send 206 final PendingIntent pendingIntent = PendingIntent.getBroadcast( 207 context, 0, new Intent(ACTION_MMS_SENT), 0); 208 smsManager.sendMultimediaMessage(context, 209 contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent); 210 assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT_MILLIS)); 211 sendFile.delete(); 212 } 213 214 private static boolean writePdu(File file, byte[] pdu) { 215 FileOutputStream writer = null; 216 try { 217 writer = new FileOutputStream(file); 218 writer.write(pdu); 219 return true; 220 } catch (final IOException e) { 221 String stackTrace = Log.getStackTraceString(e); 222 Log.i(TAG, stackTrace); 223 return false; 224 } finally { 225 if (writer != null) { 226 try { 227 writer.close(); 228 } catch (IOException e) { 229 } 230 } 231 } 232 } 233 234 private static byte[] buildPdu(Context context, String selfNumber, String subject, 235 String text) { 236 final SendReq req = new SendReq(); 237 // From, per spec 238 req.setFrom(new EncodedStringValue(selfNumber)); 239 // To 240 final String[] recipients = new String[1]; 241 recipients[0] = selfNumber; 242 final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients); 243 if (encodedNumbers != null) { 244 req.setTo(encodedNumbers); 245 } 246 // Subject 247 if (!TextUtils.isEmpty(subject)) { 248 req.setSubject(new EncodedStringValue(subject)); 249 } 250 // Date 251 req.setDate(System.currentTimeMillis() / 1000); 252 // Body 253 final PduBody body = new PduBody(); 254 // Add text part. Always add a smil part for compatibility, without it there 255 // may be issues on some carriers/client apps 256 final int size = addTextPart(body, text, true/* add text smil */); 257 req.setBody(body); 258 // Message size 259 req.setMessageSize(size); 260 // Message class 261 req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes()); 262 // Expiry 263 req.setExpiry(DEFAULT_EXPIRY_TIME_SECS); 264 // The following set methods throw InvalidHeaderValueException 265 try { 266 // Priority 267 req.setPriority(DEFAULT_PRIORITY); 268 // Delivery report 269 req.setDeliveryReport(PduHeaders.VALUE_NO); 270 // Read report 271 req.setReadReport(PduHeaders.VALUE_NO); 272 } catch (InvalidHeaderValueException e) { 273 return null; 274 } 275 276 return new PduComposer(context, req).make(); 277 } 278 279 private static int addTextPart(PduBody pb, String message, boolean addTextSmil) { 280 final PduPart part = new PduPart(); 281 // Set Charset if it's a text media. 282 part.setCharset(CharacterSets.UTF_8); 283 // Set Content-Type. 284 part.setContentType(ContentType.TEXT_PLAIN.getBytes()); 285 // Set Content-Location. 286 part.setContentLocation(TEXT_PART_FILENAME.getBytes()); 287 int index = TEXT_PART_FILENAME.lastIndexOf("."); 288 String contentId = (index == -1) ? TEXT_PART_FILENAME 289 : TEXT_PART_FILENAME.substring(0, index); 290 part.setContentId(contentId.getBytes()); 291 part.setData(message.getBytes()); 292 pb.addPart(part); 293 if (addTextSmil) { 294 final String smil = String.format(SMIL_TEXT, TEXT_PART_FILENAME); 295 addSmilPart(pb, smil); 296 } 297 return part.getData().length; 298 } 299 300 private static void addSmilPart(PduBody pb, String smil) { 301 final PduPart smilPart = new PduPart(); 302 smilPart.setContentId("smil".getBytes()); 303 smilPart.setContentLocation("smil.xml".getBytes()); 304 smilPart.setContentType(ContentType.APP_SMIL.getBytes()); 305 smilPart.setData(smil.getBytes()); 306 pb.addPart(0, smilPart); 307 } 308 309 private static String getPhoneNumber(Context context) { 310 final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( 311 Context.TELEPHONY_SERVICE); 312 String phoneNumber = telephonyManager.getLine1Number(); 313 if (phoneNumber.trim().isEmpty()) { 314 phoneNumber = System.getProperty(PHONE_NUMBER_KEY); 315 } 316 return phoneNumber; 317 } 318 319 private static boolean shouldParseContentDisposition() { 320 return SmsManager 321 .getDefault() 322 .getCarrierConfigValues() 323 .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true); 324 } 325 326 private static boolean doesSupportMMS() { 327 return SmsManager 328 .getDefault() 329 .getCarrierConfigValues() 330 .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true); 331 } 332 333 }