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