1 /* 2 * Copyright (C) 2017 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.googlecode.android_scripting.facade.telephony; 18 19 import android.app.Activity; 20 import android.app.PendingIntent; 21 import android.app.Service; 22 import android.content.BroadcastReceiver; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.provider.Telephony.Sms.Intents; 30 import android.telephony.SmsCbCmasInfo; 31 import android.telephony.SmsCbEtwsInfo; 32 import android.telephony.SmsCbMessage; 33 import android.telephony.SmsManager; 34 import android.telephony.SmsMessage; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 38 import com.android.internal.telephony.cdma.sms.SmsEnvelope; 39 import com.android.internal.telephony.gsm.SmsCbConstants; 40 41 import com.google.android.mms.ContentType; 42 import com.google.android.mms.InvalidHeaderValueException; 43 import com.google.android.mms.pdu.CharacterSets; 44 import com.google.android.mms.pdu.EncodedStringValue; 45 import com.google.android.mms.pdu.GenericPdu; 46 import com.google.android.mms.pdu.PduBody; 47 import com.google.android.mms.pdu.PduComposer; 48 import com.google.android.mms.pdu.PduHeaders; 49 import com.google.android.mms.pdu.PduParser; 50 import com.google.android.mms.pdu.PduPart; 51 import com.google.android.mms.pdu.SendConf; 52 import com.google.android.mms.pdu.SendReq; 53 import com.googlecode.android_scripting.Log; 54 import com.googlecode.android_scripting.facade.EventFacade; 55 import com.googlecode.android_scripting.facade.FacadeManager; 56 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 57 import com.googlecode.android_scripting.rpc.Rpc; 58 import com.googlecode.android_scripting.rpc.RpcOptional; 59 import com.googlecode.android_scripting.rpc.RpcParameter; 60 61 import java.io.File; 62 import java.io.FileOutputStream; 63 import java.io.IOException; 64 import java.lang.reflect.Field; 65 import java.util.ArrayList; 66 import java.util.HashMap; 67 import java.util.List; 68 69 /** 70 * Exposes SmsManager functionality. 71 */ 72 public class SmsFacade extends RpcReceiver { 73 74 static final boolean DBG = false; 75 76 private final EventFacade mEventFacade; 77 private final SmsManager mSms; 78 private final Context mContext; 79 private final Service mService; 80 private BroadcastReceiver mSmsSendListener; 81 private BroadcastReceiver mSmsIncomingListener; 82 private int mNumExpectedSentEvents; 83 private int mNumExpectedDeliveredEvents; 84 private boolean mListeningIncomingSms; 85 private IntentFilter mEmergencyCBMessage; 86 private BroadcastReceiver mGsmEmergencyCBMessageListener; 87 private BroadcastReceiver mCdmaEmergencyCBMessageListener; 88 private boolean mGsmEmergencyCBListenerRegistered; 89 private boolean mCdmaEmergencyCBListenerRegistered; 90 private boolean mSentReceiversRegistered; 91 private Object lock = new Object(); 92 private File mMmsSendFile; 93 private String mPackageName; 94 95 private BroadcastReceiver mMmsSendListener; 96 private BroadcastReceiver mMmsIncomingListener; 97 private boolean mListeningIncomingMms; 98 99 TelephonyManager mTelephonyManager; 100 101 private static final String SMS_MESSAGE_STATUS_DELIVERED_ACTION = 102 "com.googlecode.android_scripting.facade.telephony.SmsFacade.SMS_DELIVERED"; 103 private static final String SMS_MESSAGE_SENT_ACTION = 104 "com.googlecode.android_scripting.facade.telephony.SmsFacade.SMS_SENT"; 105 106 private static final String EMERGENCY_CB_MESSAGE_RECEIVED_ACTION = 107 "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; 108 109 private static final String MMS_MESSAGE_SENT_ACTION = 110 "com.googlecode.android_scripting.facade.telephony.SmsFacade.MMS_SENT"; 111 112 private final int MAX_MESSAGE_LENGTH = 160; 113 private final int INTERNATIONAL_NUMBER_LENGTH = 12; 114 private final int DOMESTIC_NUMBER_LENGTH = 10; 115 116 private static final String DEFAULT_FROM_PHONE_NUMBER = new String("8675309"); 117 118 // Core parameters needed for all types of message 119 private static final String KEY_MESSAGE_ID = "message_id"; 120 private static final String KEY_MESSAGE = "message"; 121 private static final String KEY_MESSAGE_URI = "message_uri"; 122 private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number"; 123 private static final String KEY_RECIPIENTS = "recipients"; 124 private static final String KEY_MESSAGE_TEXT = "message_text"; 125 private static final String KEY_SUBJECT_TEXT = "subject_text"; 126 127 private final int[] mGsmCbMessageIdList = { 128 SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING, 129 SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING, 130 SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING, 131 SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE, 132 SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE, 133 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, 134 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED, 135 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY, 136 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED, 137 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY, 138 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY, 139 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED, 140 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY, 141 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY, 142 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST, 143 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE 144 }; 145 146 private final int[] mCdmaCbMessageIdList = { 147 SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 148 SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, 149 SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, 150 SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, 151 SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE 152 }; 153 154 private static HashMap<Integer, String> sSmsSendFailureMap = new HashMap<Integer, String>(); 155 private static HashMap<Integer, String> sMmsSendFailureMap = new HashMap<Integer, String>(); 156 private static HashMap<Integer, String> sMmsSendResponseMap = new HashMap<Integer, String>(); 157 SmsFacade(FacadeManager manager)158 public SmsFacade(FacadeManager manager) { 159 160 super(manager); 161 mService = manager.getService(); 162 mContext = mService; 163 mSms = SmsManager.getDefault(); 164 mEventFacade = manager.getReceiver(EventFacade.class); 165 mPackageName = mContext.getPackageName(); 166 mSmsSendListener = new SmsSendListener(); 167 mSmsIncomingListener = new SmsIncomingListener(); 168 mNumExpectedSentEvents = 0; 169 mNumExpectedDeliveredEvents = 0; 170 mListeningIncomingSms = false; 171 mGsmEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); 172 mCdmaEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); 173 mGsmEmergencyCBListenerRegistered = false; 174 mCdmaEmergencyCBListenerRegistered = false; 175 mSentReceiversRegistered = false; 176 177 mMmsIncomingListener = new MmsIncomingListener(); 178 mMmsSendListener = new MmsSendListener(); 179 180 mListeningIncomingMms = false; 181 182 IntentFilter smsFilter = new IntentFilter(SMS_MESSAGE_SENT_ACTION); 183 smsFilter.addAction(SMS_MESSAGE_STATUS_DELIVERED_ACTION); 184 185 IntentFilter mmsFilter = new IntentFilter(MMS_MESSAGE_SENT_ACTION); 186 187 synchronized (lock) { 188 mService.registerReceiver(mSmsSendListener, smsFilter); 189 mService.registerReceiver(mMmsSendListener, mmsFilter); 190 mSentReceiversRegistered = true; 191 } 192 193 mTelephonyManager = 194 (TelephonyManager) mService.getSystemService(Context.TELEPHONY_SERVICE); 195 196 try { 197 Class<?> mSmsClass = mSms.getClass(); 198 Field[] fields = mSmsClass.getFields(); 199 for (Field field : fields) { 200 String name = field.getName(); 201 if (name.startsWith("RESULT_")) { 202 sSmsSendFailureMap.put((Integer) field.get(mSmsClass), name); 203 } else if (name.startsWith("MMS_ERROR_")) { 204 sMmsSendFailureMap.put((Integer) field.get(mSmsClass), name); 205 } 206 } 207 } catch (Exception e) { 208 Log.d("SmsFacade error: " + e.toString()); 209 } 210 211 try { 212 Class<?> mPduHeadersClass = PduHeaders.class; 213 Field[] fields = mPduHeadersClass.getFields(); 214 for (Field field : fields) { 215 String name = field.getName(); 216 if (name.startsWith("RESPONSE_STATUS_")) { 217 sMmsSendResponseMap.put((Integer) field.get(mPduHeadersClass), name); 218 } 219 } 220 } catch (Exception e) { 221 Log.d("SmsFacade error: " + e.toString()); 222 } 223 } 224 225 // FIXME: Move to a utility class 226 // FIXME: remove the MODE_WORLD_READABLE once we verify the use case 227 @SuppressWarnings("deprecation") writeBytesToFile(String fileName, byte[] pdu)228 private boolean writeBytesToFile(String fileName, byte[] pdu) { 229 FileOutputStream writer = null; 230 try { 231 writer = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE); 232 writer.write(pdu); 233 return true; 234 } catch (final IOException e) { 235 return false; 236 } finally { 237 if (writer != null) { 238 try { 239 writer.close(); 240 } catch (IOException e) { 241 } 242 } 243 } 244 } 245 246 // FIXME: Move to a utility class writeBytesToCacheFile(String fileName, byte[] pdu)247 private boolean writeBytesToCacheFile(String fileName, byte[] pdu) { 248 mMmsSendFile = new File(mContext.getCacheDir(), fileName); 249 Log.d(String.format("filename:%s, directory:%s", fileName, 250 mContext.getCacheDir().toString())); 251 FileOutputStream writer = null; 252 try { 253 writer = new FileOutputStream(mMmsSendFile); 254 writer.write(pdu); 255 return true; 256 } catch (final IOException e) { 257 Log.d("writeBytesToCacheFile() failed with " + e.toString()); 258 return false; 259 } finally { 260 if (writer != null) { 261 try { 262 writer.close(); 263 } catch (IOException e) { 264 } 265 } 266 } 267 } 268 269 @Deprecated 270 @Rpc(description = "Starts tracking incoming SMS.") smsStartTrackingIncomingMessage()271 public void smsStartTrackingIncomingMessage() { 272 Log.d("Using Deprecated smsStartTrackingIncomingMessage!"); 273 smsStartTrackingIncomingSmsMessage(); 274 } 275 276 @Rpc(description = "Starts tracking incoming SMS.") smsStartTrackingIncomingSmsMessage()277 public void smsStartTrackingIncomingSmsMessage() { 278 mService.registerReceiver(mSmsIncomingListener, 279 new IntentFilter(Intents.SMS_RECEIVED_ACTION)); 280 mListeningIncomingSms = true; 281 } 282 283 @Deprecated 284 @Rpc(description = "Stops tracking incoming SMS.") smsStopTrackingIncomingMessage()285 public void smsStopTrackingIncomingMessage() { 286 Log.d("Using Deprecated smsStopTrackingIncomingMessage!"); 287 smsStopTrackingIncomingSmsMessage(); 288 } 289 290 @Rpc(description = "Stops tracking incoming SMS.") smsStopTrackingIncomingSmsMessage()291 public void smsStopTrackingIncomingSmsMessage() { 292 if (mListeningIncomingSms) { 293 mListeningIncomingSms = false; 294 try { 295 mService.unregisterReceiver(mSmsIncomingListener); 296 } catch (Exception e) { 297 Log.e("Tried to unregister nonexistent SMS Listener!"); 298 } 299 } 300 } 301 302 @Rpc(description = "Starts tracking incoming MMS.") smsStartTrackingIncomingMmsMessage()303 public void smsStartTrackingIncomingMmsMessage() { 304 IntentFilter mmsReceived = new IntentFilter(Intents.MMS_DOWNLOADED_ACTION); 305 mmsReceived.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); 306 mmsReceived.addAction(Intents.DATA_SMS_RECEIVED_ACTION); 307 mService.registerReceiver(mMmsIncomingListener, mmsReceived); 308 mListeningIncomingMms = true; 309 } 310 311 @Rpc(description = "Stops tracking incoming MMS.") smsStopTrackingIncomingMmsMessage()312 public void smsStopTrackingIncomingMmsMessage() { 313 if (mListeningIncomingMms) { 314 mListeningIncomingMms = false; 315 try { 316 mService.unregisterReceiver(mMmsIncomingListener); 317 } catch (Exception e) { 318 Log.e("Tried to unregister nonexistent MMS Listener!"); 319 } 320 } 321 } 322 323 // Currently requires 'adb shell su root setenforce 0' 324 @Rpc(description = "Send a multimedia message to a specified number.") smsSendMultimediaMessage( @pcParametername = "toPhoneNumber") String toPhoneNumber, @RpcParameter(name = "subject") String subject, @RpcParameter(name = "message") String message, @RpcParameter(name = "fromPhoneNumber") @RpcOptional String fromPhoneNumber, @RpcParameter(name = "fileName") @RpcOptional String fileName)325 public void smsSendMultimediaMessage( 326 @RpcParameter(name = "toPhoneNumber") 327 String toPhoneNumber, 328 @RpcParameter(name = "subject") 329 String subject, 330 @RpcParameter(name = "message") 331 String message, 332 @RpcParameter(name = "fromPhoneNumber") 333 @RpcOptional 334 String fromPhoneNumber, 335 @RpcParameter(name = "fileName") 336 @RpcOptional 337 String fileName) { 338 339 MmsBuilder mms = new MmsBuilder(); 340 341 mms.setToPhoneNumber(toPhoneNumber); 342 if (fromPhoneNumber == null) { 343 mTelephonyManager.getLine1Number(); //TODO: b/21592513 - multi-sim awareness 344 } 345 346 mms.setFromPhoneNumber((fromPhoneNumber != null) ? fromPhoneNumber : DEFAULT_FROM_PHONE_NUMBER); 347 mms.setSubject(subject); 348 mms.setDate(); 349 mms.addMessageBody(message); 350 mms.setMessageClass(MmsBuilder.MESSAGE_CLASS_PERSONAL); 351 mms.setMessagePriority(MmsBuilder.DEFAULT_PRIORITY); 352 mms.setDeliveryReport(true); 353 mms.setReadReport(true); 354 // Default to 1 week; 355 mms.setExpirySeconds(MmsBuilder.DEFAULT_EXPIRY_TIME); 356 357 String randomFileName = "mms." + String.valueOf(System.currentTimeMillis()) + ".dat"; 358 359 byte[] mmsBytes = mms.build(); 360 if (mmsBytes.length == 0) { 361 Log.e("Failed to build PDU!"); 362 return; 363 } 364 365 if (writeBytesToCacheFile(randomFileName, mmsBytes) == false) { 366 Log.e("Failed to write PDU to file " + randomFileName); 367 return; 368 } 369 370 Uri contentUri = (new Uri.Builder()) 371 .authority( 372 "com.googlecode.android_scripting.facade.telephony.MmsFileProvider") 373 .path(randomFileName) 374 .scheme(ContentResolver.SCHEME_CONTENT) 375 .build(); 376 377 Bundle actionParameters = new Bundle(); 378 actionParameters.putString(KEY_RECIPIENTS, toPhoneNumber); 379 actionParameters.putString(KEY_SUBJECT_TEXT, subject); 380 Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); 381 382 if (contentUri != null) { 383 Log.d(String.format("URI String: %s", contentUri.toString())); 384 mSms.sendMultimediaMessage(mContext, contentUri, null/* locationUrl */, 385 null/* configOverrides */, 386 createBroadcastPendingIntent(MMS_MESSAGE_SENT_ACTION, messageUri)); 387 } 388 else { 389 Log.e("smsSendMultimediaMessage():Content URI String is null"); 390 } 391 } 392 393 @Rpc(description = "Send a text message to a specified number.") smsSendTextMessage( @pcParametername = "phoneNumber") String phoneNumber, @RpcParameter(name = "message") String message, @RpcParameter(name = "deliveryReportRequired") Boolean deliveryReportRequired)394 public void smsSendTextMessage( 395 @RpcParameter(name = "phoneNumber") 396 String phoneNumber, 397 @RpcParameter(name = "message") 398 String message, 399 @RpcParameter(name = "deliveryReportRequired") 400 Boolean deliveryReportRequired) { 401 int message_length = message.length(); 402 Log.d(String.format("Send SMS message of length %d", message_length)); 403 ArrayList<String> messagesParts = mSms.divideMessage(message); 404 if (messagesParts.size() > 1) { 405 mNumExpectedSentEvents = mNumExpectedDeliveredEvents = messagesParts.size(); 406 Log.d(String.format("SMS message of length %d is divided into %d parts", 407 message_length, mNumExpectedSentEvents)); 408 ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(); 409 ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(); 410 for (int i = 0; i < messagesParts.size(); i++) { 411 Bundle actionParameters = new Bundle(); 412 actionParameters.putString(KEY_RECIPIENTS, phoneNumber); 413 Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); 414 sentIntents.add(createBroadcastPendingIntent(SMS_MESSAGE_SENT_ACTION, messageUri)); 415 if (deliveryReportRequired) { 416 deliveredIntents.add(createBroadcastPendingIntent( 417 SMS_MESSAGE_STATUS_DELIVERED_ACTION, messageUri)); 418 } 419 } 420 mSms.sendMultipartTextMessage( 421 phoneNumber, null, messagesParts, 422 sentIntents, deliveryReportRequired ? deliveredIntents : null); 423 } else { 424 Log.d(String.format("SMS message of length %s is sent as one part", message_length)); 425 mNumExpectedSentEvents = mNumExpectedDeliveredEvents = 1; 426 Bundle actionParameters = new Bundle(); 427 actionParameters.putString(KEY_RECIPIENTS, phoneNumber); 428 Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); 429 mSms.sendTextMessage(phoneNumber, null, messagesParts.get(0), 430 createBroadcastPendingIntent(SMS_MESSAGE_SENT_ACTION, messageUri), 431 deliveryReportRequired ? createBroadcastPendingIntent( 432 SMS_MESSAGE_STATUS_DELIVERED_ACTION, messageUri) : null); 433 } 434 } 435 436 @Rpc(description = "Retrieves all messages currently stored on ICC.") smsGetAllMessagesFromIcc()437 public ArrayList<SmsMessage> smsGetAllMessagesFromIcc() { 438 return mSms.getAllMessagesFromIcc(); 439 } 440 441 @Rpc(description = "Starts tracking GSM Emergency CB Messages.") smsStartTrackingGsmEmergencyCBMessage()442 public void smsStartTrackingGsmEmergencyCBMessage() { 443 if (!mGsmEmergencyCBListenerRegistered) { 444 for (int messageId : mGsmCbMessageIdList) { 445 mSms.enableCellBroadcastRange( 446 messageId, 447 messageId, 448 SmsCbMessage.MESSAGE_FORMAT_3GPP); 449 } 450 451 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 452 mService.registerReceiver(mGsmEmergencyCBMessageListener, 453 mEmergencyCBMessage); 454 mGsmEmergencyCBListenerRegistered = true; 455 } 456 } 457 458 @Rpc(description = "Stop tracking GSM Emergency CB Messages") smsStopTrackingGsmEmergencyCBMessage()459 public void smsStopTrackingGsmEmergencyCBMessage() { 460 if (mGsmEmergencyCBListenerRegistered) { 461 mService.unregisterReceiver(mGsmEmergencyCBMessageListener); 462 mGsmEmergencyCBListenerRegistered = false; 463 for (int messageId : mGsmCbMessageIdList) { 464 mSms.disableCellBroadcastRange( 465 messageId, 466 messageId, 467 SmsCbMessage.MESSAGE_FORMAT_3GPP); 468 } 469 } 470 } 471 472 @Rpc(description = "Starts tracking CDMA Emergency CB Messages") smsStartTrackingCdmaEmergencyCBMessage()473 public void smsStartTrackingCdmaEmergencyCBMessage() { 474 if (!mCdmaEmergencyCBListenerRegistered) { 475 for (int messageId : mCdmaCbMessageIdList) { 476 mSms.enableCellBroadcastRange( 477 messageId, 478 messageId, 479 SmsCbMessage.MESSAGE_FORMAT_3GPP2); 480 } 481 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 482 mService.registerReceiver(mCdmaEmergencyCBMessageListener, 483 mEmergencyCBMessage); 484 mCdmaEmergencyCBListenerRegistered = true; 485 } 486 } 487 488 @Rpc(description = "Stop tracking CDMA Emergency CB Message.") smsStopTrackingCdmaEmergencyCBMessage()489 public void smsStopTrackingCdmaEmergencyCBMessage() { 490 if (mCdmaEmergencyCBListenerRegistered) { 491 mService.unregisterReceiver(mCdmaEmergencyCBMessageListener); 492 mCdmaEmergencyCBListenerRegistered = false; 493 for (int messageId : mCdmaCbMessageIdList) { 494 mSms.disableCellBroadcastRange( 495 messageId, 496 messageId, 497 SmsCbMessage.MESSAGE_FORMAT_3GPP2); 498 } 499 } 500 } 501 502 private class SmsSendListener extends BroadcastReceiver { 503 @Override onReceive(Context context, Intent intent)504 public void onReceive(Context context, Intent intent) { 505 Bundle event = new Bundle(); 506 String action = intent.getAction(); 507 int resultCode = getResultCode(); 508 event.putString("ResultCode", Integer.toString(resultCode)); 509 if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) { 510 event.putString("Type", "SmsDeliverStatus"); 511 if (resultCode == Activity.RESULT_OK) { 512 if (mNumExpectedDeliveredEvents == 1) { 513 Log.d("SMS Message delivered successfully"); 514 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event); 515 } 516 if (mNumExpectedDeliveredEvents > 0) { 517 mNumExpectedDeliveredEvents--; 518 } 519 } else { 520 Log.e("SMS Message delivery failed"); 521 // TODO . Need to find the reason for failure from pdu 522 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event); 523 } 524 } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) { 525 if (resultCode == Activity.RESULT_OK) { 526 if (mNumExpectedSentEvents == 1) { 527 event.putString("Type", "SmsSentSuccess"); 528 event.putString("ResultString", "RESULT_OK"); 529 Log.d("SMS Message sent successfully"); 530 mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event); 531 } 532 if (mNumExpectedSentEvents > 0) { 533 mNumExpectedSentEvents--; 534 } 535 } else { 536 String resultString = getSmsFailureReason(resultCode); 537 event.putString("ResultString", resultString); 538 Log.e("SMS Message send failed with code " + Integer.toString( 539 resultCode) + resultString); 540 event.putString("Type", "SmsSentFailure"); 541 event.putString("Reason", resultString); 542 mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event); 543 } 544 } 545 } 546 } 547 548 private class SmsIncomingListener extends BroadcastReceiver { 549 @Override onReceive(Context context, Intent intent)550 public void onReceive(Context context, Intent intent) { 551 Log.d(String.format("SmsIncomingListener Received: %s", intent.toUri(0))); 552 String action = intent.getAction(); 553 if (Intents.SMS_RECEIVED_ACTION.equals(action)) { 554 Log.d("New SMS Received"); 555 Bundle extras = intent.getExtras(); 556 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 557 if (extras != null) { 558 Bundle event = new Bundle(); 559 event.putString("Type", "NewSmsReceived"); 560 SmsMessage[] msgs = Intents.getMessagesFromIntent(intent); 561 StringBuilder smsMsg = new StringBuilder(); 562 563 SmsMessage sms; 564 for (int i = 0; i < msgs.length; i++) { 565 if (msgs[i] == null) { 566 Log.w("SmsMessages received from intent at " + i + " has no body."); 567 continue; 568 } 569 sms = msgs[i]; 570 String sender = sms.getOriginatingAddress(); 571 event.putString("Sender", formatPhoneNumber(sender)); 572 smsMsg.append(sms.getMessageBody()); 573 } 574 event.putString("Text", smsMsg.toString()); 575 // TODO 576 // Need to explore how to get subId information. 577 event.putInt("subscriptionId", subId); 578 mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event); 579 } 580 } 581 } 582 } 583 584 private class MmsSendListener extends BroadcastReceiver { 585 @Override onReceive(Context context, Intent intent)586 public void onReceive(Context context, Intent intent) { 587 Bundle event = new Bundle(); 588 event.putString("Type", "MmsDeliverStatus"); 589 String action = intent.getAction(); 590 int resultCode = getResultCode(); 591 event.putString("ResultCode", Integer.toString(resultCode)); 592 String eventType = TelephonyConstants.EventMmsSentFailure; 593 if (MMS_MESSAGE_SENT_ACTION.equals(action)) { 594 if (resultCode == Activity.RESULT_OK) { 595 final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA); 596 event.putString("ResultString", "RESULT_OK"); 597 if (response != null) { 598 boolean shouldParse = mSms.getCarrierConfigValues( 599 ).getBoolean( 600 SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true); 601 final GenericPdu pdu = new PduParser(response, shouldParse).parse(); 602 if (pdu instanceof SendConf) { 603 final SendConf sendConf = (SendConf) pdu; 604 int responseCode = sendConf.getResponseStatus(); 605 String responseStatus = getMmsResponseStatus(responseCode); 606 event.putString("ResponseStatus", responseStatus); 607 if (responseCode == PduHeaders.RESPONSE_STATUS_OK) { 608 Log.d("MMS Message sent successfully"); 609 eventType = TelephonyConstants.EventMmsSentSuccess; 610 } else { 611 Log.e("MMS sent with error response = " + responseStatus); 612 event.putString("Reason", responseStatus); 613 } 614 } else { 615 Log.e("MMS sent, invalid response"); 616 event.putString("Reason", "InvalidResponse"); 617 } 618 } else { 619 Log.e("MMS sent, empty response"); 620 event.putString("Reason", "EmptyResponse"); 621 } 622 } else { 623 String resultString = getMmsFailureReason(resultCode); 624 event.putString("ResultString", resultString); 625 Log.e("MMS Message send failed with result code " + Integer.toString( 626 resultCode) + resultString); 627 event.putString("Reason", resultString); 628 } 629 event.putString("Type", eventType); 630 mEventFacade.postEvent(eventType, event); 631 } else { 632 Log.e("MMS Send Listener Received Invalid Event" + intent.toString()); 633 } 634 } 635 } 636 637 // add mms matching after mms message parser is added in sl4a. b/34276948 638 private class MmsIncomingListener extends BroadcastReceiver { 639 @Override onReceive(Context context, Intent intent)640 public void onReceive(Context context, Intent intent) { 641 Log.d("MmsIncomingListener Received an Intent " + intent.toString()); 642 String action = intent.getAction(); 643 if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) { 644 Log.d("New MMS Downloaded"); 645 Bundle event = new Bundle(); 646 event.putString("Type", "NewMmsReceived"); 647 mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, event); 648 } 649 else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { 650 Log.d("New Wap Push Received"); 651 Bundle event = new Bundle(); 652 event.putString("Type", "NewWapPushReceived"); 653 mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, event); 654 } 655 else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) { 656 Log.d("New Data SMS Received"); 657 Bundle event = new Bundle(); 658 event.putString("Type", "NewDataSMSReceived"); 659 mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, event); 660 } 661 else { 662 Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString()); 663 } 664 } 665 } 666 formatPhoneNumber(String phoneNumber)667 String formatPhoneNumber(String phoneNumber) { 668 String senderNumberStr = null; 669 int len = phoneNumber.length(); 670 if (len > 0) { 671 /** 672 * Currently this incomingNumber modification is specific for US numbers. 673 */ 674 if ((INTERNATIONAL_NUMBER_LENGTH == len) && ('+' == phoneNumber.charAt(0))) { 675 senderNumberStr = phoneNumber.substring(1); 676 } else if (DOMESTIC_NUMBER_LENGTH == len) { 677 senderNumberStr = '1' + phoneNumber; 678 } else { 679 senderNumberStr = phoneNumber; 680 } 681 } 682 return senderNumberStr; 683 } 684 685 private class SmsEmergencyCBMessageListener extends BroadcastReceiver { 686 @Override onReceive(Context context, Intent intent)687 public void onReceive(Context context, Intent intent) { 688 if (EMERGENCY_CB_MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) { 689 Bundle extras = intent.getExtras(); 690 if (extras != null) { 691 Bundle event = new Bundle(); 692 String eventName = null; 693 SmsCbMessage message = (SmsCbMessage) extras.get("message"); 694 if (message != null) { 695 if (message.isEmergencyMessage()) { 696 event.putString("geographicalScope", getGeographicalScope( 697 message.getGeographicalScope())); 698 event.putInt("serialNumber", message.getSerialNumber()); 699 event.putString("location", message.getLocation().toString()); 700 event.putInt("serviceCategory", message.getServiceCategory()); 701 event.putString("language", message.getLanguageCode()); 702 event.putString("message", message.getMessageBody()); 703 event.putString("priority", getPriority(message.getMessagePriority())); 704 if (message.isCmasMessage()) { 705 // CMAS message 706 eventName = TelephonyConstants.EventCmasReceived; 707 event.putString("cmasMessageClass", getCMASMessageClass( 708 message.getCmasWarningInfo().getMessageClass())); 709 event.putString("cmasCategory", getCMASCategory( 710 message.getCmasWarningInfo().getCategory())); 711 event.putString("cmasResponseType", getCMASResponseType( 712 message.getCmasWarningInfo().getResponseType())); 713 event.putString("cmasSeverity", getCMASSeverity( 714 message.getCmasWarningInfo().getSeverity())); 715 event.putString("cmasUrgency", getCMASUrgency( 716 message.getCmasWarningInfo().getUrgency())); 717 event.putString("cmasCertainty", getCMASCertainty( 718 message.getCmasWarningInfo().getCertainty())); 719 } else if (message.isEtwsMessage()) { 720 // ETWS message 721 eventName = TelephonyConstants.EventEtwsReceived; 722 event.putString("etwsWarningType", getETWSWarningType( 723 message.getEtwsWarningInfo().getWarningType())); 724 event.putBoolean("etwsIsEmergencyUserAlert", 725 message.getEtwsWarningInfo().isEmergencyUserAlert()); 726 event.putBoolean("etwsActivatePopup", 727 message.getEtwsWarningInfo().isPopupAlert()); 728 } else { 729 Log.d("Received message is not CMAS or ETWS"); 730 } 731 if (eventName != null) 732 mEventFacade.postEvent(eventName, event); 733 } 734 } 735 } else { 736 Log.d("Received Emergency CB without extras"); 737 } 738 } 739 } 740 } 741 createBroadcastPendingIntent(String intentAction, Uri messageUri)742 private PendingIntent createBroadcastPendingIntent(String intentAction, Uri messageUri) { 743 Intent intent = new Intent(intentAction, messageUri); 744 return PendingIntent.getBroadcast(mService, 0, intent, 0); 745 } 746 getSmsFailureReason(int resultCode)747 private static String getSmsFailureReason(int resultCode) { 748 return sSmsSendFailureMap.get(resultCode); 749 } 750 getMmsFailureReason(int resultCode)751 private static String getMmsFailureReason(int resultCode) { 752 return sMmsSendFailureMap.get(resultCode); 753 } 754 getMmsResponseStatus(int resultCode)755 private static String getMmsResponseStatus(int resultCode) { 756 return sMmsSendResponseMap.get(resultCode); 757 } 758 getETWSWarningType(int type)759 private static String getETWSWarningType(int type) { 760 switch (type) { 761 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE: 762 return "EARTHQUAKE"; 763 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI: 764 return "TSUNAMI"; 765 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: 766 return "EARTHQUAKE_AND_TSUNAMI"; 767 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE: 768 return "TEST_MESSAGE"; 769 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY: 770 return "OTHER_EMERGENCY"; 771 } 772 return "UNKNOWN"; 773 } 774 getCMASMessageClass(int messageclass)775 private static String getCMASMessageClass(int messageclass) { 776 switch (messageclass) { 777 case SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT: 778 return "PRESIDENTIAL_LEVEL_ALERT"; 779 case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT: 780 return "EXTREME_THREAT"; 781 case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT: 782 return "SEVERE_THREAT"; 783 case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY: 784 return "CHILD_ABDUCTION_EMERGENCY"; 785 case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST: 786 return "REQUIRED_MONTHLY_TEST"; 787 case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE: 788 return "CMAS_EXERCISE"; 789 } 790 return "UNKNOWN"; 791 } 792 getCMASCategory(int category)793 private static String getCMASCategory(int category) { 794 switch (category) { 795 case SmsCbCmasInfo.CMAS_CATEGORY_GEO: 796 return "GEOPHYSICAL"; 797 case SmsCbCmasInfo.CMAS_CATEGORY_MET: 798 return "METEOROLOGICAL"; 799 case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY: 800 return "SAFETY"; 801 case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY: 802 return "SECURITY"; 803 case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE: 804 return "RESCUE"; 805 case SmsCbCmasInfo.CMAS_CATEGORY_FIRE: 806 return "FIRE"; 807 case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH: 808 return "HEALTH"; 809 case SmsCbCmasInfo.CMAS_CATEGORY_ENV: 810 return "ENVIRONMENTAL"; 811 case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT: 812 return "TRANSPORTATION"; 813 case SmsCbCmasInfo.CMAS_CATEGORY_INFRA: 814 return "INFRASTRUCTURE"; 815 case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE: 816 return "CHEMICAL"; 817 case SmsCbCmasInfo.CMAS_CATEGORY_OTHER: 818 return "OTHER"; 819 } 820 return "UNKNOWN"; 821 } 822 getCMASResponseType(int type)823 private static String getCMASResponseType(int type) { 824 switch (type) { 825 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER: 826 return "SHELTER"; 827 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE: 828 return "EVACUATE"; 829 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE: 830 return "PREPARE"; 831 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE: 832 return "EXECUTE"; 833 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR: 834 return "MONITOR"; 835 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID: 836 return "AVOID"; 837 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS: 838 return "ASSESS"; 839 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE: 840 return "NONE"; 841 } 842 return "UNKNOWN"; 843 } 844 getCMASSeverity(int severity)845 private static String getCMASSeverity(int severity) { 846 switch (severity) { 847 case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME: 848 return "EXTREME"; 849 case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE: 850 return "SEVERE"; 851 } 852 return "UNKNOWN"; 853 } 854 getCMASUrgency(int urgency)855 private static String getCMASUrgency(int urgency) { 856 switch (urgency) { 857 case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE: 858 return "IMMEDIATE"; 859 case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED: 860 return "EXPECTED"; 861 } 862 return "UNKNOWN"; 863 } 864 getCMASCertainty(int certainty)865 private static String getCMASCertainty(int certainty) { 866 switch (certainty) { 867 case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED: 868 return "IMMEDIATE"; 869 case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY: 870 return "LIKELY"; 871 } 872 return "UNKNOWN"; 873 } 874 getGeographicalScope(int scope)875 private static String getGeographicalScope(int scope) { 876 switch (scope) { 877 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 878 return "CELL_WIDE_IMMEDIATE"; 879 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: 880 return "PLMN_WIDE "; 881 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: 882 return "LA_WIDE"; 883 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: 884 return "CELL_WIDE"; 885 } 886 return "UNKNOWN"; 887 } 888 getPriority(int priority)889 private static String getPriority(int priority) { 890 switch (priority) { 891 case SmsCbMessage.MESSAGE_PRIORITY_NORMAL: 892 return "NORMAL"; 893 case SmsCbMessage.MESSAGE_PRIORITY_INTERACTIVE: 894 return "INTERACTIVE"; 895 case SmsCbMessage.MESSAGE_PRIORITY_URGENT: 896 return "URGENT"; 897 case SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY: 898 return "EMERGENCY"; 899 } 900 return "UNKNOWN"; 901 } 902 903 @Override shutdown()904 public void shutdown() { 905 906 smsStopTrackingIncomingSmsMessage(); 907 smsStopTrackingIncomingMmsMessage(); 908 smsStopTrackingGsmEmergencyCBMessage(); 909 smsStopTrackingCdmaEmergencyCBMessage(); 910 911 synchronized (lock) { 912 if (mSentReceiversRegistered) { 913 mService.unregisterReceiver(mSmsSendListener); 914 mService.unregisterReceiver(mMmsSendListener); 915 mSentReceiversRegistered = false; 916 } 917 } 918 } 919 920 private class MmsBuilder { 921 922 public static final String MESSAGE_CLASS_PERSONAL = 923 PduHeaders.MESSAGE_CLASS_PERSONAL_STR; 924 925 public static final String MESSAGE_CLASS_ADVERTISEMENT = 926 PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR; 927 928 public static final String MESSAGE_CLASS_INFORMATIONAL = 929 PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR; 930 931 public static final String MESSAGE_CLASS_AUTO = 932 PduHeaders.MESSAGE_CLASS_AUTO_STR; 933 934 public static final int MESSAGE_PRIORITY_LOW = PduHeaders.PRIORITY_LOW; 935 public static final int MESSAGE_PRIORITY_NORMAL = PduHeaders.PRIORITY_LOW; 936 public static final int MESSAGE_PRIORITY_HIGH = PduHeaders.PRIORITY_LOW; 937 938 private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60; 939 private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; 940 941 private SendReq mRequest; 942 private PduBody mBody; 943 944 // FIXME: Eventually this should be exposed as a parameter 945 private static final String TEMP_CONTENT_FILE_NAME = "text0.txt"; 946 947 // Synchronized Multimedia Internet Language 948 // Fragment for compatibility 949 private static final String sSmilText = 950 "<smil>" + 951 "<head>" + 952 "<layout>" + 953 "<root-layout/>" + 954 "<region height=\"100%%\" id=\"Text\" left=\"0%%\"" + 955 " top=\"0%%\" width=\"100%%\"/>" + 956 "</layout>" + 957 "</head>" + 958 "<body>" + 959 "<par dur=\"8000ms\">" + 960 "<text src=\"%s\" region=\"Text\"/>" + 961 "</par>" + 962 "</body>" + 963 "</smil>"; 964 MmsBuilder()965 public MmsBuilder() { 966 mRequest = new SendReq(); 967 mBody = new PduBody(); 968 } 969 setFromPhoneNumber(String number)970 public void setFromPhoneNumber(String number) { 971 mRequest.setFrom(new EncodedStringValue(number)); 972 } 973 setToPhoneNumber(String number)974 public void setToPhoneNumber(String number) { 975 mRequest.setTo(new EncodedStringValue[] { 976 new EncodedStringValue(number) }); 977 } 978 setToPhoneNumbers(List<String> number)979 public void setToPhoneNumbers(List<String> number) { 980 mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray())); 981 } 982 setSubject(String subject)983 public void setSubject(String subject) { 984 mRequest.setSubject(new EncodedStringValue(subject)); 985 } 986 setDate()987 public void setDate() { 988 setDate(System.currentTimeMillis() / 1000); 989 } 990 setDate(long time)991 public void setDate(long time) { 992 mRequest.setDate(time); 993 } 994 addMessageBody(String message)995 public void addMessageBody(String message) { 996 addMessageBody(message, true); 997 } 998 setMessageClass(String messageClass)999 public void setMessageClass(String messageClass) { 1000 mRequest.setMessageClass(messageClass.getBytes()); 1001 } 1002 setMessagePriority(int priority)1003 public void setMessagePriority(int priority) { 1004 try { 1005 mRequest.setPriority(priority); 1006 } catch (InvalidHeaderValueException e) { 1007 Log.e("Invalid Header Value "+e.toString()); 1008 } 1009 } 1010 setDeliveryReport(boolean report)1011 public void setDeliveryReport(boolean report) { 1012 try { 1013 mRequest.setDeliveryReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); 1014 } catch (InvalidHeaderValueException e) { 1015 Log.e("Invalid Header Value "+e.toString()); 1016 } 1017 } 1018 setReadReport(boolean report)1019 public void setReadReport(boolean report) { 1020 try { 1021 mRequest.setReadReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); 1022 } catch (InvalidHeaderValueException e) { 1023 Log.e("Invalid Header Value "+e.toString()); 1024 } 1025 } 1026 setExpirySeconds(int seconds)1027 public void setExpirySeconds(int seconds) { 1028 mRequest.setExpiry(seconds); 1029 } 1030 build()1031 public byte[] build() { 1032 mRequest.setBody(mBody); 1033 1034 int msgSize = 0; 1035 for (int i = 0; i < mBody.getPartsNum(); i++) { 1036 msgSize += mBody.getPart(i).getDataLength(); 1037 } 1038 mRequest.setMessageSize(msgSize); 1039 1040 return new PduComposer(mContext, mRequest).make(); 1041 } 1042 addMessageBody(String message, boolean addSmilFragment)1043 public void addMessageBody(String message, boolean addSmilFragment) { 1044 final PduPart part = new PduPart(); 1045 part.setCharset(CharacterSets.UTF_8); 1046 part.setContentType(ContentType.TEXT_PLAIN.getBytes()); 1047 part.setContentLocation(TEMP_CONTENT_FILE_NAME.getBytes()); 1048 int index = TEMP_CONTENT_FILE_NAME.lastIndexOf("."); 1049 String contentId = (index == -1) ? TEMP_CONTENT_FILE_NAME 1050 : TEMP_CONTENT_FILE_NAME.substring(0, index); 1051 part.setContentId(contentId.getBytes()); 1052 part.setContentId("txt".getBytes()); 1053 part.setData(message.getBytes()); 1054 mBody.addPart(part); 1055 if (addSmilFragment) { 1056 addSmilTextFragment(TEMP_CONTENT_FILE_NAME); 1057 } 1058 } 1059 addSmilTextFragment(String contentFilename)1060 private void addSmilTextFragment(String contentFilename) { 1061 1062 final String smil = String.format(sSmilText, contentFilename); 1063 final PduPart smilPart = new PduPart(); 1064 smilPart.setContentId("smil".getBytes()); 1065 smilPart.setContentLocation("smil.xml".getBytes()); 1066 smilPart.setContentType(ContentType.APP_SMIL.getBytes()); 1067 smilPart.setData(smil.getBytes()); 1068 mBody.addPart(0, smilPart); 1069 } 1070 } 1071 1072 } 1073