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.enableCellBroadcast( 446 messageId, 447 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); 448 } 449 450 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 451 mService.registerReceiver(mGsmEmergencyCBMessageListener, 452 mEmergencyCBMessage); 453 mGsmEmergencyCBListenerRegistered = true; 454 } 455 } 456 457 @Rpc(description = "Stop tracking GSM Emergency CB Messages") smsStopTrackingGsmEmergencyCBMessage()458 public void smsStopTrackingGsmEmergencyCBMessage() { 459 if (mGsmEmergencyCBListenerRegistered) { 460 mService.unregisterReceiver(mGsmEmergencyCBMessageListener); 461 mGsmEmergencyCBListenerRegistered = false; 462 for (int messageId : mGsmCbMessageIdList) { 463 mSms.disableCellBroadcast( 464 messageId, 465 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); 466 } 467 } 468 } 469 470 @Rpc(description = "Starts tracking CDMA Emergency CB Messages") smsStartTrackingCdmaEmergencyCBMessage()471 public void smsStartTrackingCdmaEmergencyCBMessage() { 472 if (!mCdmaEmergencyCBListenerRegistered) { 473 for (int messageId : mCdmaCbMessageIdList) { 474 mSms.enableCellBroadcast( 475 messageId, 476 SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); 477 } 478 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 479 mService.registerReceiver(mCdmaEmergencyCBMessageListener, 480 mEmergencyCBMessage); 481 mCdmaEmergencyCBListenerRegistered = true; 482 } 483 } 484 485 @Rpc(description = "Stop tracking CDMA Emergency CB Message.") smsStopTrackingCdmaEmergencyCBMessage()486 public void smsStopTrackingCdmaEmergencyCBMessage() { 487 if (mCdmaEmergencyCBListenerRegistered) { 488 mService.unregisterReceiver(mCdmaEmergencyCBMessageListener); 489 mCdmaEmergencyCBListenerRegistered = false; 490 for (int messageId : mCdmaCbMessageIdList) { 491 mSms.disableCellBroadcast( 492 messageId, 493 SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); 494 } 495 } 496 } 497 498 private class SmsSendListener extends BroadcastReceiver { 499 @Override onReceive(Context context, Intent intent)500 public void onReceive(Context context, Intent intent) { 501 Bundle event = new Bundle(); 502 String action = intent.getAction(); 503 int resultCode = getResultCode(); 504 event.putString("ResultCode", Integer.toString(resultCode)); 505 if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) { 506 event.putString("Type", "SmsDeliverStatus"); 507 if (resultCode == Activity.RESULT_OK) { 508 if (mNumExpectedDeliveredEvents == 1) { 509 Log.d("SMS Message delivered successfully"); 510 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event); 511 } 512 if (mNumExpectedDeliveredEvents > 0) { 513 mNumExpectedDeliveredEvents--; 514 } 515 } else { 516 Log.e("SMS Message delivery failed"); 517 // TODO . Need to find the reason for failure from pdu 518 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event); 519 } 520 } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) { 521 if (resultCode == Activity.RESULT_OK) { 522 if (mNumExpectedSentEvents == 1) { 523 event.putString("Type", "SmsSentSuccess"); 524 event.putString("ResultString", "RESULT_OK"); 525 Log.d("SMS Message sent successfully"); 526 mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event); 527 } 528 if (mNumExpectedSentEvents > 0) { 529 mNumExpectedSentEvents--; 530 } 531 } else { 532 String resultString = getSmsFailureReason(resultCode); 533 event.putString("ResultString", resultString); 534 Log.e("SMS Message send failed with code " + Integer.toString( 535 resultCode) + resultString); 536 event.putString("Type", "SmsSentFailure"); 537 event.putString("Reason", resultString); 538 mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event); 539 } 540 } 541 } 542 } 543 544 private class SmsIncomingListener extends BroadcastReceiver { 545 @Override onReceive(Context context, Intent intent)546 public void onReceive(Context context, Intent intent) { 547 Log.d(String.format("SmsIncomingListener Received: %s", intent.toUri(0))); 548 String action = intent.getAction(); 549 if (Intents.SMS_RECEIVED_ACTION.equals(action)) { 550 Log.d("New SMS Received"); 551 Bundle extras = intent.getExtras(); 552 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 553 if (extras != null) { 554 Bundle event = new Bundle(); 555 event.putString("Type", "NewSmsReceived"); 556 SmsMessage[] msgs = Intents.getMessagesFromIntent(intent); 557 StringBuilder smsMsg = new StringBuilder(); 558 559 SmsMessage sms; 560 for (int i = 0; i < msgs.length; i++) { 561 if (msgs[i] == null) { 562 Log.w("SmsMessages received from intent at " + i + " has no body."); 563 continue; 564 } 565 sms = msgs[i]; 566 String sender = sms.getOriginatingAddress(); 567 event.putString("Sender", formatPhoneNumber(sender)); 568 smsMsg.append(sms.getMessageBody()); 569 } 570 event.putString("Text", smsMsg.toString()); 571 // TODO 572 // Need to explore how to get subId information. 573 event.putInt("subscriptionId", subId); 574 mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event); 575 } 576 } 577 } 578 } 579 580 private class MmsSendListener extends BroadcastReceiver { 581 @Override onReceive(Context context, Intent intent)582 public void onReceive(Context context, Intent intent) { 583 Bundle event = new Bundle(); 584 event.putString("Type", "MmsDeliverStatus"); 585 String action = intent.getAction(); 586 int resultCode = getResultCode(); 587 event.putString("ResultCode", Integer.toString(resultCode)); 588 String eventType = TelephonyConstants.EventMmsSentFailure; 589 if (MMS_MESSAGE_SENT_ACTION.equals(action)) { 590 if (resultCode == Activity.RESULT_OK) { 591 final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA); 592 event.putString("ResultString", "RESULT_OK"); 593 if (response != null) { 594 boolean shouldParse = mSms.getCarrierConfigValues( 595 ).getBoolean( 596 SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true); 597 final GenericPdu pdu = new PduParser(response, shouldParse).parse(); 598 if (pdu instanceof SendConf) { 599 final SendConf sendConf = (SendConf) pdu; 600 int responseCode = sendConf.getResponseStatus(); 601 String responseStatus = getMmsResponseStatus(responseCode); 602 event.putString("ResponseStatus", responseStatus); 603 if (responseCode == PduHeaders.RESPONSE_STATUS_OK) { 604 Log.d("MMS Message sent successfully"); 605 eventType = TelephonyConstants.EventMmsSentSuccess; 606 } else { 607 Log.e("MMS sent with error response = " + responseStatus); 608 event.putString("Reason", responseStatus); 609 } 610 } else { 611 Log.e("MMS sent, invalid response"); 612 event.putString("Reason", "InvalidResponse"); 613 } 614 } else { 615 Log.e("MMS sent, empty response"); 616 event.putString("Reason", "EmptyResponse"); 617 } 618 } else { 619 String resultString = getMmsFailureReason(resultCode); 620 event.putString("ResultString", resultString); 621 Log.e("MMS Message send failed with result code " + Integer.toString( 622 resultCode) + resultString); 623 event.putString("Reason", resultString); 624 } 625 event.putString("Type", eventType); 626 mEventFacade.postEvent(eventType, event); 627 } else { 628 Log.e("MMS Send Listener Received Invalid Event" + intent.toString()); 629 } 630 } 631 } 632 633 // add mms matching after mms message parser is added in sl4a. b/34276948 634 private class MmsIncomingListener extends BroadcastReceiver { 635 @Override onReceive(Context context, Intent intent)636 public void onReceive(Context context, Intent intent) { 637 Log.d("MmsIncomingListener Received an Intent " + intent.toString()); 638 String action = intent.getAction(); 639 if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) { 640 Log.d("New MMS Downloaded"); 641 Bundle event = new Bundle(); 642 event.putString("Type", "NewMmsReceived"); 643 mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, event); 644 } 645 else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { 646 Log.d("New Wap Push Received"); 647 Bundle event = new Bundle(); 648 event.putString("Type", "NewWapPushReceived"); 649 mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, event); 650 } 651 else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) { 652 Log.d("New Data SMS Received"); 653 Bundle event = new Bundle(); 654 event.putString("Type", "NewDataSMSReceived"); 655 mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, event); 656 } 657 else { 658 Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString()); 659 } 660 } 661 } 662 formatPhoneNumber(String phoneNumber)663 String formatPhoneNumber(String phoneNumber) { 664 String senderNumberStr = null; 665 int len = phoneNumber.length(); 666 if (len > 0) { 667 /** 668 * Currently this incomingNumber modification is specific for US numbers. 669 */ 670 if ((INTERNATIONAL_NUMBER_LENGTH == len) && ('+' == phoneNumber.charAt(0))) { 671 senderNumberStr = phoneNumber.substring(1); 672 } else if (DOMESTIC_NUMBER_LENGTH == len) { 673 senderNumberStr = '1' + phoneNumber; 674 } else { 675 senderNumberStr = phoneNumber; 676 } 677 } 678 return senderNumberStr; 679 } 680 681 private class SmsEmergencyCBMessageListener extends BroadcastReceiver { 682 @Override onReceive(Context context, Intent intent)683 public void onReceive(Context context, Intent intent) { 684 if (EMERGENCY_CB_MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) { 685 Bundle extras = intent.getExtras(); 686 if (extras != null) { 687 Bundle event = new Bundle(); 688 String eventName = null; 689 SmsCbMessage message = (SmsCbMessage) extras.get("message"); 690 if (message != null) { 691 if (message.isEmergencyMessage()) { 692 event.putString("geographicalScope", getGeographicalScope( 693 message.getGeographicalScope())); 694 event.putInt("serialNumber", message.getSerialNumber()); 695 event.putString("location", message.getLocation().toString()); 696 event.putInt("serviceCategory", message.getServiceCategory()); 697 event.putString("language", message.getLanguageCode()); 698 event.putString("message", message.getMessageBody()); 699 event.putString("priority", getPriority(message.getMessagePriority())); 700 if (message.isCmasMessage()) { 701 // CMAS message 702 eventName = TelephonyConstants.EventCmasReceived; 703 event.putString("cmasMessageClass", getCMASMessageClass( 704 message.getCmasWarningInfo().getMessageClass())); 705 event.putString("cmasCategory", getCMASCategory( 706 message.getCmasWarningInfo().getCategory())); 707 event.putString("cmasResponseType", getCMASResponseType( 708 message.getCmasWarningInfo().getResponseType())); 709 event.putString("cmasSeverity", getCMASSeverity( 710 message.getCmasWarningInfo().getSeverity())); 711 event.putString("cmasUrgency", getCMASUrgency( 712 message.getCmasWarningInfo().getUrgency())); 713 event.putString("cmasCertainty", getCMASCertainty( 714 message.getCmasWarningInfo().getCertainty())); 715 } else if (message.isEtwsMessage()) { 716 // ETWS message 717 eventName = TelephonyConstants.EventEtwsReceived; 718 event.putString("etwsWarningType", getETWSWarningType( 719 message.getEtwsWarningInfo().getWarningType())); 720 event.putBoolean("etwsIsEmergencyUserAlert", 721 message.getEtwsWarningInfo().isEmergencyUserAlert()); 722 event.putBoolean("etwsActivatePopup", 723 message.getEtwsWarningInfo().isPopupAlert()); 724 } else { 725 Log.d("Received message is not CMAS or ETWS"); 726 } 727 if (eventName != null) 728 mEventFacade.postEvent(eventName, event); 729 } 730 } 731 } else { 732 Log.d("Received Emergency CB without extras"); 733 } 734 } 735 } 736 } 737 createBroadcastPendingIntent(String intentAction, Uri messageUri)738 private PendingIntent createBroadcastPendingIntent(String intentAction, Uri messageUri) { 739 Intent intent = new Intent(intentAction, messageUri); 740 return PendingIntent.getBroadcast(mService, 0, intent, 0); 741 } 742 getSmsFailureReason(int resultCode)743 private static String getSmsFailureReason(int resultCode) { 744 return sSmsSendFailureMap.get(resultCode); 745 } 746 getMmsFailureReason(int resultCode)747 private static String getMmsFailureReason(int resultCode) { 748 return sMmsSendFailureMap.get(resultCode); 749 } 750 getMmsResponseStatus(int resultCode)751 private static String getMmsResponseStatus(int resultCode) { 752 return sMmsSendResponseMap.get(resultCode); 753 } 754 getETWSWarningType(int type)755 private static String getETWSWarningType(int type) { 756 switch (type) { 757 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE: 758 return "EARTHQUAKE"; 759 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI: 760 return "TSUNAMI"; 761 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: 762 return "EARTHQUAKE_AND_TSUNAMI"; 763 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE: 764 return "TEST_MESSAGE"; 765 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY: 766 return "OTHER_EMERGENCY"; 767 } 768 return "UNKNOWN"; 769 } 770 getCMASMessageClass(int messageclass)771 private static String getCMASMessageClass(int messageclass) { 772 switch (messageclass) { 773 case SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT: 774 return "PRESIDENTIAL_LEVEL_ALERT"; 775 case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT: 776 return "EXTREME_THREAT"; 777 case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT: 778 return "SEVERE_THREAT"; 779 case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY: 780 return "CHILD_ABDUCTION_EMERGENCY"; 781 case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST: 782 return "REQUIRED_MONTHLY_TEST"; 783 case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE: 784 return "CMAS_EXERCISE"; 785 } 786 return "UNKNOWN"; 787 } 788 getCMASCategory(int category)789 private static String getCMASCategory(int category) { 790 switch (category) { 791 case SmsCbCmasInfo.CMAS_CATEGORY_GEO: 792 return "GEOPHYSICAL"; 793 case SmsCbCmasInfo.CMAS_CATEGORY_MET: 794 return "METEOROLOGICAL"; 795 case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY: 796 return "SAFETY"; 797 case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY: 798 return "SECURITY"; 799 case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE: 800 return "RESCUE"; 801 case SmsCbCmasInfo.CMAS_CATEGORY_FIRE: 802 return "FIRE"; 803 case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH: 804 return "HEALTH"; 805 case SmsCbCmasInfo.CMAS_CATEGORY_ENV: 806 return "ENVIRONMENTAL"; 807 case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT: 808 return "TRANSPORTATION"; 809 case SmsCbCmasInfo.CMAS_CATEGORY_INFRA: 810 return "INFRASTRUCTURE"; 811 case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE: 812 return "CHEMICAL"; 813 case SmsCbCmasInfo.CMAS_CATEGORY_OTHER: 814 return "OTHER"; 815 } 816 return "UNKNOWN"; 817 } 818 getCMASResponseType(int type)819 private static String getCMASResponseType(int type) { 820 switch (type) { 821 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER: 822 return "SHELTER"; 823 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE: 824 return "EVACUATE"; 825 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE: 826 return "PREPARE"; 827 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE: 828 return "EXECUTE"; 829 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR: 830 return "MONITOR"; 831 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID: 832 return "AVOID"; 833 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS: 834 return "ASSESS"; 835 case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE: 836 return "NONE"; 837 } 838 return "UNKNOWN"; 839 } 840 getCMASSeverity(int severity)841 private static String getCMASSeverity(int severity) { 842 switch (severity) { 843 case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME: 844 return "EXTREME"; 845 case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE: 846 return "SEVERE"; 847 } 848 return "UNKNOWN"; 849 } 850 getCMASUrgency(int urgency)851 private static String getCMASUrgency(int urgency) { 852 switch (urgency) { 853 case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE: 854 return "IMMEDIATE"; 855 case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED: 856 return "EXPECTED"; 857 } 858 return "UNKNOWN"; 859 } 860 getCMASCertainty(int certainty)861 private static String getCMASCertainty(int certainty) { 862 switch (certainty) { 863 case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED: 864 return "IMMEDIATE"; 865 case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY: 866 return "LIKELY"; 867 } 868 return "UNKNOWN"; 869 } 870 getGeographicalScope(int scope)871 private static String getGeographicalScope(int scope) { 872 switch (scope) { 873 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 874 return "CELL_WIDE_IMMEDIATE"; 875 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: 876 return "PLMN_WIDE "; 877 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: 878 return "LA_WIDE"; 879 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: 880 return "CELL_WIDE"; 881 } 882 return "UNKNOWN"; 883 } 884 getPriority(int priority)885 private static String getPriority(int priority) { 886 switch (priority) { 887 case SmsCbMessage.MESSAGE_PRIORITY_NORMAL: 888 return "NORMAL"; 889 case SmsCbMessage.MESSAGE_PRIORITY_INTERACTIVE: 890 return "INTERACTIVE"; 891 case SmsCbMessage.MESSAGE_PRIORITY_URGENT: 892 return "URGENT"; 893 case SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY: 894 return "EMERGENCY"; 895 } 896 return "UNKNOWN"; 897 } 898 899 @Override shutdown()900 public void shutdown() { 901 902 smsStopTrackingIncomingSmsMessage(); 903 smsStopTrackingIncomingMmsMessage(); 904 smsStopTrackingGsmEmergencyCBMessage(); 905 smsStopTrackingCdmaEmergencyCBMessage(); 906 907 synchronized (lock) { 908 if (mSentReceiversRegistered) { 909 mService.unregisterReceiver(mSmsSendListener); 910 mService.unregisterReceiver(mMmsSendListener); 911 mSentReceiversRegistered = false; 912 } 913 } 914 } 915 916 private class MmsBuilder { 917 918 public static final String MESSAGE_CLASS_PERSONAL = 919 PduHeaders.MESSAGE_CLASS_PERSONAL_STR; 920 921 public static final String MESSAGE_CLASS_ADVERTISEMENT = 922 PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR; 923 924 public static final String MESSAGE_CLASS_INFORMATIONAL = 925 PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR; 926 927 public static final String MESSAGE_CLASS_AUTO = 928 PduHeaders.MESSAGE_CLASS_AUTO_STR; 929 930 public static final int MESSAGE_PRIORITY_LOW = PduHeaders.PRIORITY_LOW; 931 public static final int MESSAGE_PRIORITY_NORMAL = PduHeaders.PRIORITY_LOW; 932 public static final int MESSAGE_PRIORITY_HIGH = PduHeaders.PRIORITY_LOW; 933 934 private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60; 935 private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; 936 937 private SendReq mRequest; 938 private PduBody mBody; 939 940 // FIXME: Eventually this should be exposed as a parameter 941 private static final String TEMP_CONTENT_FILE_NAME = "text0.txt"; 942 943 // Synchronized Multimedia Internet Language 944 // Fragment for compatibility 945 private static final String sSmilText = 946 "<smil>" + 947 "<head>" + 948 "<layout>" + 949 "<root-layout/>" + 950 "<region height=\"100%%\" id=\"Text\" left=\"0%%\"" + 951 " top=\"0%%\" width=\"100%%\"/>" + 952 "</layout>" + 953 "</head>" + 954 "<body>" + 955 "<par dur=\"8000ms\">" + 956 "<text src=\"%s\" region=\"Text\"/>" + 957 "</par>" + 958 "</body>" + 959 "</smil>"; 960 MmsBuilder()961 public MmsBuilder() { 962 mRequest = new SendReq(); 963 mBody = new PduBody(); 964 } 965 setFromPhoneNumber(String number)966 public void setFromPhoneNumber(String number) { 967 mRequest.setFrom(new EncodedStringValue(number)); 968 } 969 setToPhoneNumber(String number)970 public void setToPhoneNumber(String number) { 971 mRequest.setTo(new EncodedStringValue[] { 972 new EncodedStringValue(number) }); 973 } 974 setToPhoneNumbers(List<String> number)975 public void setToPhoneNumbers(List<String> number) { 976 mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray())); 977 } 978 setSubject(String subject)979 public void setSubject(String subject) { 980 mRequest.setSubject(new EncodedStringValue(subject)); 981 } 982 setDate()983 public void setDate() { 984 setDate(System.currentTimeMillis() / 1000); 985 } 986 setDate(long time)987 public void setDate(long time) { 988 mRequest.setDate(time); 989 } 990 addMessageBody(String message)991 public void addMessageBody(String message) { 992 addMessageBody(message, true); 993 } 994 setMessageClass(String messageClass)995 public void setMessageClass(String messageClass) { 996 mRequest.setMessageClass(messageClass.getBytes()); 997 } 998 setMessagePriority(int priority)999 public void setMessagePriority(int priority) { 1000 try { 1001 mRequest.setPriority(priority); 1002 } catch (InvalidHeaderValueException e) { 1003 Log.e("Invalid Header Value "+e.toString()); 1004 } 1005 } 1006 setDeliveryReport(boolean report)1007 public void setDeliveryReport(boolean report) { 1008 try { 1009 mRequest.setDeliveryReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); 1010 } catch (InvalidHeaderValueException e) { 1011 Log.e("Invalid Header Value "+e.toString()); 1012 } 1013 } 1014 setReadReport(boolean report)1015 public void setReadReport(boolean report) { 1016 try { 1017 mRequest.setReadReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); 1018 } catch (InvalidHeaderValueException e) { 1019 Log.e("Invalid Header Value "+e.toString()); 1020 } 1021 } 1022 setExpirySeconds(int seconds)1023 public void setExpirySeconds(int seconds) { 1024 mRequest.setExpiry(seconds); 1025 } 1026 build()1027 public byte[] build() { 1028 mRequest.setBody(mBody); 1029 1030 int msgSize = 0; 1031 for (int i = 0; i < mBody.getPartsNum(); i++) { 1032 msgSize += mBody.getPart(i).getDataLength(); 1033 } 1034 mRequest.setMessageSize(msgSize); 1035 1036 return new PduComposer(mContext, mRequest).make(); 1037 } 1038 addMessageBody(String message, boolean addSmilFragment)1039 public void addMessageBody(String message, boolean addSmilFragment) { 1040 final PduPart part = new PduPart(); 1041 part.setCharset(CharacterSets.UTF_8); 1042 part.setContentType(ContentType.TEXT_PLAIN.getBytes()); 1043 part.setContentLocation(TEMP_CONTENT_FILE_NAME.getBytes()); 1044 int index = TEMP_CONTENT_FILE_NAME.lastIndexOf("."); 1045 String contentId = (index == -1) ? TEMP_CONTENT_FILE_NAME 1046 : TEMP_CONTENT_FILE_NAME.substring(0, index); 1047 part.setContentId(contentId.getBytes()); 1048 part.setContentId("txt".getBytes()); 1049 part.setData(message.getBytes()); 1050 mBody.addPart(part); 1051 if (addSmilFragment) { 1052 addSmilTextFragment(TEMP_CONTENT_FILE_NAME); 1053 } 1054 } 1055 addSmilTextFragment(String contentFilename)1056 private void addSmilTextFragment(String contentFilename) { 1057 1058 final String smil = String.format(sSmilText, contentFilename); 1059 final PduPart smilPart = new PduPart(); 1060 smilPart.setContentId("smil".getBytes()); 1061 smilPart.setContentLocation("smil.xml".getBytes()); 1062 smilPart.setContentType(ContentType.APP_SMIL.getBytes()); 1063 smilPart.setData(smil.getBytes()); 1064 mBody.addPart(0, smilPart); 1065 } 1066 } 1067 1068 } 1069