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