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 17 package com.googlecode.android_scripting.facade.telephony; 18 19 import com.google.android.mms.ContentType; 20 import com.google.android.mms.InvalidHeaderValueException; 21 import com.google.android.mms.pdu.CharacterSets; 22 import com.google.android.mms.pdu.EncodedStringValue; 23 import com.google.android.mms.pdu.GenericPdu; 24 import com.google.android.mms.pdu.PduBody; 25 import com.google.android.mms.pdu.PduComposer; 26 import com.google.android.mms.pdu.PduHeaders; 27 import com.google.android.mms.pdu.PduParser; 28 import com.google.android.mms.pdu.PduPart; 29 import com.google.android.mms.pdu.PduPersister; 30 import com.google.android.mms.pdu.SendConf; 31 import com.google.android.mms.pdu.SendReq; 32 import com.google.android.mms.MmsException; 33 34 import java.io.File; 35 import java.io.FileOutputStream; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 import android.app.Activity; 41 import android.app.PendingIntent; 42 import android.app.Service; 43 import android.content.BroadcastReceiver; 44 import android.content.ContentResolver; 45 import android.content.Context; 46 import android.content.Intent; 47 import android.content.IntentFilter; 48 import android.net.Uri; 49 import android.os.Bundle; 50 import android.provider.Telephony.Sms.Intents; 51 import android.provider.Telephony.Mms; 52 import android.telephony.SmsManager; 53 import android.telephony.SmsMessage; 54 import android.telephony.SmsCbMessage; 55 import com.android.internal.telephony.gsm.SmsCbConstants; 56 import com.android.internal.telephony.cdma.sms.SmsEnvelope; 57 import android.telephony.SmsCbEtwsInfo; 58 import android.telephony.SmsCbCmasInfo; 59 import android.telephony.SubscriptionManager; 60 import android.telephony.TelephonyManager; 61 62 import com.googlecode.android_scripting.Log; 63 import com.googlecode.android_scripting.facade.EventFacade; 64 import com.googlecode.android_scripting.facade.FacadeManager; 65 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 66 import com.googlecode.android_scripting.rpc.Rpc; 67 import com.googlecode.android_scripting.rpc.RpcDefault; 68 import com.googlecode.android_scripting.rpc.RpcOptional; 69 import com.googlecode.android_scripting.rpc.RpcParameter; 70 import com.googlecode.android_scripting.facade.telephony.TelephonyConstants; 71 72 //FIXME: Change the build order to use constants defined in here 73 //import com.googlecode.android_scripting.provider.TelephonyTestProvider; 74 75 /** 76 * Exposes SmsManager functionality. 77 */ 78 public class SmsFacade extends RpcReceiver { 79 80 static final boolean DBG = false; 81 82 private final EventFacade mEventFacade; 83 private final SmsManager mSms; 84 private final Context mContext; 85 private final Service mService; 86 private BroadcastReceiver mSmsSendListener; 87 private BroadcastReceiver mSmsIncomingListener; 88 private int mNumExpectedSentEvents; 89 private int mNumExpectedDeliveredEvents; 90 private boolean mListeningIncomingSms; 91 private IntentFilter mEmergencyCBMessage; 92 private BroadcastReceiver mGsmEmergencyCBMessageListener; 93 private BroadcastReceiver mCdmaEmergencyCBMessageListener; 94 private boolean mGsmEmergencyCBListenerRegistered; 95 private boolean mCdmaEmergencyCBListenerRegistered; 96 private boolean mSentReceiversRegistered; 97 private Object lock = new Object(); 98 99 private BroadcastReceiver mMmsSendListener; 100 private BroadcastReceiver mMmsIncomingListener; 101 private boolean mListeningIncomingMms; 102 103 TelephonyManager mTelephonyManager; 104 105 private static final String SMS_MESSAGE_STATUS_DELIVERED_ACTION = 106 "com.googlecode.android_scripting.sms.MESSAGE_STATUS_DELIVERED"; 107 private static final String SMS_MESSAGE_SENT_ACTION = 108 "com.googlecode.android_scripting.sms.MESSAGE_SENT"; 109 110 private static final String EMERGENCY_CB_MESSAGE_RECEIVED_ACTION = 111 "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; 112 113 private static final String MMS_MESSAGE_SENT_ACTION = 114 "com.googlecode.android_scripting.mms.MESSAGE_SENT"; 115 116 private final int MAX_MESSAGE_LENGTH = 160; 117 private final int INTERNATIONAL_NUMBER_LENGTH = 12; 118 private final int DOMESTIC_NUMBER_LENGTH = 10; 119 120 private static final String DEFAULT_FROM_PHONE_NUMBER = new String("8675309"); 121 122 private final int[] mGsmCbMessageIdList = { 123 SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING, 124 SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING, 125 SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING, 126 SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE, 127 SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE, 128 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, 129 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED, 130 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY, 131 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED, 132 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY, 133 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY, 134 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED, 135 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY, 136 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY, 137 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST, 138 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE 139 }; 140 141 private final int[] mCdmaCbMessageIdList = { 142 SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 143 SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, 144 SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, 145 SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, 146 SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE 147 }; 148 SmsFacade(FacadeManager manager)149 public SmsFacade(FacadeManager manager) { 150 151 super(manager); 152 mService = manager.getService(); 153 mContext = mService; 154 mSms = SmsManager.getDefault(); 155 mEventFacade = manager.getReceiver(EventFacade.class); 156 mSmsSendListener = new SmsSendListener(); 157 mSmsIncomingListener = new SmsIncomingListener(); 158 mNumExpectedSentEvents = 0; 159 mNumExpectedDeliveredEvents = 0; 160 mListeningIncomingSms = false; 161 mGsmEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); 162 mCdmaEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); 163 mGsmEmergencyCBListenerRegistered = false; 164 mCdmaEmergencyCBListenerRegistered = false; 165 mSentReceiversRegistered = false; 166 167 mMmsIncomingListener = new MmsIncomingListener(); 168 mMmsSendListener = new MmsSendListener(); 169 170 mListeningIncomingMms = false; 171 172 IntentFilter smsFilter = new IntentFilter(SMS_MESSAGE_SENT_ACTION); 173 smsFilter.addAction(SMS_MESSAGE_STATUS_DELIVERED_ACTION); 174 175 IntentFilter mmsFilter = new IntentFilter(MMS_MESSAGE_SENT_ACTION); 176 177 synchronized (lock) { 178 mService.registerReceiver(mSmsSendListener, smsFilter); 179 mService.registerReceiver(mMmsSendListener, mmsFilter); 180 mSentReceiversRegistered = true; 181 } 182 183 mTelephonyManager = 184 (TelephonyManager) mService.getSystemService(Context.TELEPHONY_SERVICE); 185 } 186 187 // FIXME: Move to a utility class 188 // FIXME: remove the MODE_WORLD_READABLE once we verify the use case 189 @SuppressWarnings("deprecation") writeBytesToFile(String fileName, byte[] pdu)190 private boolean writeBytesToFile(String fileName, byte[] pdu) { 191 FileOutputStream writer = null; 192 try { 193 writer = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE); 194 writer.write(pdu); 195 return true; 196 } catch (final IOException e) { 197 return false; 198 } finally { 199 if (writer != null) { 200 try { 201 writer.close(); 202 } catch (IOException e) { 203 } 204 } 205 } 206 } 207 208 // FIXME: Move to a utility class writeBytesToCacheFile(String fileName, byte[] pdu)209 private boolean writeBytesToCacheFile(String fileName, byte[] pdu) { 210 File mmsFile = new File(mContext.getCacheDir(), fileName); 211 Log.d(String.format("filename:%s, directory:%s", fileName, 212 mContext.getCacheDir().toString())); 213 FileOutputStream writer = null; 214 try { 215 writer = new FileOutputStream(mmsFile); 216 writer.write(pdu); 217 return true; 218 } catch (final IOException e) { 219 Log.d("writeBytesToCacheFile() failed with " + e.toString()); 220 return false; 221 } finally { 222 if (writer != null) { 223 try { 224 writer.close(); 225 } catch (IOException e) { 226 } 227 } 228 } 229 } 230 231 @Deprecated 232 @Rpc(description = "Starts tracking incoming SMS.") smsStartTrackingIncomingMessage()233 public void smsStartTrackingIncomingMessage() { 234 Log.d("Using Deprecated smsStartTrackingIncomingMessage!"); 235 smsStartTrackingIncomingSmsMessage(); 236 } 237 238 @Rpc(description = "Starts tracking incoming SMS.") smsStartTrackingIncomingSmsMessage()239 public void smsStartTrackingIncomingSmsMessage() { 240 mService.registerReceiver(mSmsIncomingListener, 241 new IntentFilter(Intents.SMS_RECEIVED_ACTION)); 242 mListeningIncomingSms = true; 243 } 244 245 @Deprecated 246 @Rpc(description = "Stops tracking incoming SMS.") smsStopTrackingIncomingMessage()247 public void smsStopTrackingIncomingMessage() { 248 Log.d("Using Deprecated smsStopTrackingIncomingMessage!"); 249 smsStopTrackingIncomingSmsMessage(); 250 } 251 252 @Rpc(description = "Stops tracking incoming SMS.") smsStopTrackingIncomingSmsMessage()253 public void smsStopTrackingIncomingSmsMessage() { 254 if (mListeningIncomingSms) { 255 mListeningIncomingSms = false; 256 try { 257 mService.unregisterReceiver(mSmsIncomingListener); 258 } catch (Exception e) { 259 Log.e("Tried to unregister nonexistent SMS Listener!"); 260 } 261 } 262 } 263 264 @Rpc(description = "Starts tracking incoming MMS.") smsStartTrackingIncomingMmsMessage()265 public void smsStartTrackingIncomingMmsMessage() { 266 IntentFilter mmsReceived = new IntentFilter(Intents.MMS_DOWNLOADED_ACTION); 267 mmsReceived.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); 268 mmsReceived.addAction(Intents.DATA_SMS_RECEIVED_ACTION); 269 mService.registerReceiver(mMmsIncomingListener, mmsReceived); 270 mListeningIncomingSms = true; 271 } 272 273 @Rpc(description = "Stops tracking incoming MMS.") smsStopTrackingIncomingMmsMessage()274 public void smsStopTrackingIncomingMmsMessage() { 275 if (mListeningIncomingMms) { 276 mListeningIncomingMms = false; 277 try { 278 mService.unregisterReceiver(mMmsIncomingListener); 279 } catch (Exception e) { 280 Log.e("Tried to unregister nonexistent MMS Listener!"); 281 } 282 } 283 } 284 285 // Currently requires 'adb shell su root setenforce 0' 286 @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)287 public void smsSendMultimediaMessage( 288 @RpcParameter(name = "toPhoneNumber") 289 String toPhoneNumber, 290 @RpcParameter(name = "subject") 291 String subject, 292 @RpcParameter(name = "message") 293 String message, 294 @RpcParameter(name = "fromPhoneNumber") 295 @RpcOptional 296 String fromPhoneNumber, 297 @RpcParameter(name = "fileName") 298 @RpcOptional 299 String fileName) { 300 301 MmsBuilder mms = new MmsBuilder(); 302 303 mms.setToPhoneNumber(toPhoneNumber); 304 if (fromPhoneNumber == null) { 305 mTelephonyManager.getLine1Number(); //TODO: b/21592513 - multi-sim awareness 306 } 307 308 if (DBG) { 309 Log.d(String.format( 310 "Params:toPhoneNumber(%s),subject(%s),message(%s),fromPhoneNumber(%s),filename(%s)", 311 toPhoneNumber, subject, message, 312 (fromPhoneNumber != null) ? fromPhoneNumber : "", 313 (fileName != null) ? fileName : "")); 314 } 315 316 mms.setFromPhoneNumber((fromPhoneNumber != null) ? fromPhoneNumber : DEFAULT_FROM_PHONE_NUMBER); 317 mms.setSubject(subject); 318 mms.setDate(); 319 mms.addMessageBody(message); 320 mms.setMessageClass(MmsBuilder.MESSAGE_CLASS_PERSONAL); 321 mms.setMessagePriority(MmsBuilder.DEFAULT_PRIORITY); 322 mms.setDeliveryReport(true); 323 mms.setReadReport(true); 324 // Default to 1 week; 325 mms.setExpirySeconds(MmsBuilder.DEFAULT_EXPIRY_TIME); 326 327 Uri contentUri = null; 328 329 String randomFileName = "mms." + String.valueOf(System.currentTimeMillis()) + ".dat"; 330 331 byte[] mmsBytes = mms.build(); 332 if (mmsBytes.length == 0) { 333 Log.e("Failed to build PDU!"); 334 return; 335 } 336 337 if (writeBytesToCacheFile(randomFileName, mmsBytes) == false) { 338 Log.e("Failed to write PDU to file"); 339 return; 340 } 341 342 contentUri = (new Uri.Builder()) 343 .authority("com.googlecode.android_scripting.provider.telephonytestprovider") 344 .path("mms/" + randomFileName) 345 .scheme(ContentResolver.SCHEME_CONTENT) 346 .build(); 347 348 if (contentUri != null) { 349 Log.d(String.format("URI String: %s", contentUri.toString())); 350 351 SmsManager.getDefault().sendMultimediaMessage(mContext, 352 contentUri, null/* locationUrl */, null/* configOverrides */, 353 PendingIntent.getBroadcast(mService, 0, 354 new Intent(MMS_MESSAGE_SENT_ACTION), 0) 355 ); 356 } 357 else { 358 Log.d("smsSendMultimediaMessage():Content URI String is null"); 359 } 360 } 361 362 @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)363 public void smsSendTextMessage( 364 @RpcParameter(name = "phoneNumber") 365 String phoneNumber, 366 @RpcParameter(name = "message") 367 String message, 368 @RpcParameter(name = "deliveryReportRequired") 369 Boolean deliveryReportRequired) { 370 371 if (message.length() > MAX_MESSAGE_LENGTH) { 372 ArrayList<String> messagesParts = mSms.divideMessage(message); 373 mNumExpectedSentEvents = mNumExpectedDeliveredEvents = messagesParts.size(); 374 ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(); 375 ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(); 376 for (int i = 0; i < messagesParts.size(); i++) { 377 sentIntents.add(PendingIntent.getBroadcast(mService, 0, 378 new Intent(SMS_MESSAGE_SENT_ACTION), 0)); 379 if (deliveryReportRequired) { 380 deliveredIntents.add( 381 PendingIntent.getBroadcast(mService, 0, 382 new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0)); 383 } 384 } 385 mSms.sendMultipartTextMessage( 386 phoneNumber, null, messagesParts, 387 sentIntents, deliveryReportRequired ? deliveredIntents : null); 388 } else { 389 mNumExpectedSentEvents = mNumExpectedDeliveredEvents = 1; 390 PendingIntent sentIntent = PendingIntent.getBroadcast(mService, 0, 391 new Intent(SMS_MESSAGE_SENT_ACTION), 0); 392 PendingIntent deliveredIntent = PendingIntent.getBroadcast(mService, 0, 393 new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0); 394 mSms.sendTextMessage( 395 phoneNumber, null, message, sentIntent, 396 deliveryReportRequired ? deliveredIntent : null); 397 } 398 } 399 400 @Rpc(description = "Retrieves all messages currently stored on ICC.") smsGetAllMessagesFromIcc()401 public ArrayList<SmsMessage> smsGetAllMessagesFromIcc() { 402 return SmsManager.getDefault().getAllMessagesFromIcc(); 403 } 404 405 @Rpc(description = "Starts tracking GSM Emergency CB Messages.") smsStartTrackingGsmEmergencyCBMessage()406 public void smsStartTrackingGsmEmergencyCBMessage() { 407 if (!mGsmEmergencyCBListenerRegistered) { 408 for (int messageId : mGsmCbMessageIdList) { 409 mSms.enableCellBroadcast( 410 messageId, 411 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); 412 } 413 414 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 415 mService.registerReceiver(mGsmEmergencyCBMessageListener, 416 mEmergencyCBMessage); 417 mGsmEmergencyCBListenerRegistered = true; 418 } 419 } 420 421 @Rpc(description = "Stop tracking GSM Emergency CB Messages") smsStopTrackingGsmEmergencyCBMessage()422 public void smsStopTrackingGsmEmergencyCBMessage() { 423 if (mGsmEmergencyCBListenerRegistered) { 424 mService.unregisterReceiver(mGsmEmergencyCBMessageListener); 425 mGsmEmergencyCBListenerRegistered = false; 426 for (int messageId : mGsmCbMessageIdList) { 427 mSms.disableCellBroadcast( 428 messageId, 429 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); 430 } 431 } 432 } 433 434 @Rpc(description = "Starts tracking CDMA Emergency CB Messages") smsStartTrackingCdmaEmergencyCBMessage()435 public void smsStartTrackingCdmaEmergencyCBMessage() { 436 if (!mCdmaEmergencyCBListenerRegistered) { 437 for (int messageId : mCdmaCbMessageIdList) { 438 mSms.enableCellBroadcast( 439 messageId, 440 SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); 441 } 442 mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); 443 mService.registerReceiver(mCdmaEmergencyCBMessageListener, 444 mEmergencyCBMessage); 445 mCdmaEmergencyCBListenerRegistered = true; 446 } 447 } 448 449 @Rpc(description = "Stop tracking CDMA Emergency CB Message.") smsStopTrackingCdmaEmergencyCBMessage()450 public void smsStopTrackingCdmaEmergencyCBMessage() { 451 if (mCdmaEmergencyCBListenerRegistered) { 452 mService.unregisterReceiver(mCdmaEmergencyCBMessageListener); 453 mCdmaEmergencyCBListenerRegistered = false; 454 for (int messageId : mCdmaCbMessageIdList) { 455 mSms.disableCellBroadcast( 456 messageId, 457 SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); 458 } 459 } 460 } 461 462 private class SmsSendListener extends BroadcastReceiver { 463 @Override onReceive(Context context, Intent intent)464 public void onReceive(Context context, Intent intent) { 465 Bundle event = new Bundle(); 466 event.putString("Type", "SmsDeliverStatus"); 467 String action = intent.getAction(); 468 int resultCode = getResultCode(); 469 if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) { 470 if (resultCode == Activity.RESULT_OK) { 471 if (mNumExpectedDeliveredEvents == 1) { 472 Log.d("SMS Message delivered successfully"); 473 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event); 474 } 475 if (mNumExpectedDeliveredEvents > 0) { 476 mNumExpectedDeliveredEvents--; 477 } 478 } else { 479 Log.e("SMS Message delivery failed"); 480 // TODO . Need to find the reason for failure from pdu 481 mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event); 482 } 483 } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) { 484 if (resultCode == Activity.RESULT_OK) { 485 if (mNumExpectedSentEvents == 1) { 486 event.putString("Type", "SmsSentSuccess"); 487 Log.d("SMS Message sent successfully"); 488 mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event); 489 } 490 if (mNumExpectedSentEvents > 0) { 491 mNumExpectedSentEvents--; 492 } 493 } else { 494 Log.e("SMS Message send failed"); 495 event.putString("Type", "SmsSentFailure"); 496 switch (resultCode) { 497 case SmsManager.RESULT_ERROR_GENERIC_FAILURE: 498 event.putString("Reason", "GenericFailure"); 499 break; 500 case SmsManager.RESULT_ERROR_RADIO_OFF: 501 event.putString("Reason", "RadioOff"); 502 break; 503 case SmsManager.RESULT_ERROR_NULL_PDU: 504 event.putString("Reason", "NullPdu"); 505 break; 506 case SmsManager.RESULT_ERROR_NO_SERVICE: 507 event.putString("Reason", "NoService"); 508 break; 509 case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED: 510 event.putString("Reason", "LimitExceeded"); 511 break; 512 case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE: 513 event.putString("Reason", "FdnCheckFailure"); 514 break; 515 default: 516 event.putString("Reason", "Unknown"); 517 break; 518 } 519 mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event); 520 } 521 } 522 } 523 } 524 525 private class SmsIncomingListener extends BroadcastReceiver { 526 @Override onReceive(Context context, Intent intent)527 public void onReceive(Context context, Intent intent) { 528 String action = intent.getAction(); 529 if (Intents.SMS_RECEIVED_ACTION.equals(action)) { 530 Log.d("New SMS Received"); 531 Bundle extras = intent.getExtras(); 532 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 533 if (extras != null) { 534 Bundle event = new Bundle(); 535 event.putString("Type", "NewSmsReceived"); 536 SmsMessage[] msgs = Intents.getMessagesFromIntent(intent); 537 StringBuilder smsMsg = new StringBuilder(); 538 539 SmsMessage sms = msgs[0]; 540 String sender = sms.getOriginatingAddress(); 541 event.putString("Sender", formatPhoneNumber(sender)); 542 543 for (int i = 0; i < msgs.length; i++) { 544 sms = msgs[i]; 545 smsMsg.append(sms.getMessageBody()); 546 } 547 event.putString("Text", smsMsg.toString()); 548 // TODO 549 // Need to explore how to get subId information. 550 event.putInt("subscriptionId", subId); 551 mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event); 552 } 553 } 554 } 555 } 556 557 private class MmsSendListener extends BroadcastReceiver { 558 @Override onReceive(Context context, Intent intent)559 public void onReceive(Context context, Intent intent) { 560 Bundle event = new Bundle(); 561 String action = intent.getAction(); 562 int resultCode = getResultCode(); 563 event.putString("ResultCode", Integer.toString(resultCode)); 564 if (MMS_MESSAGE_SENT_ACTION.equals(action)) { 565 if (resultCode == Activity.RESULT_OK) { 566 Log.d("MMS Message sent successfully"); 567 mEventFacade.postEvent(TelephonyConstants.EventMmsSentSuccess, event); 568 } else { 569 Log.e(String.format("MMS Message send failed: %d", resultCode)); 570 mEventFacade.postEvent(TelephonyConstants.EventMmsSentFailure, event); 571 } 572 } else { 573 Log.e("MMS Send Listener Received Invalid Event" + intent.toString()); 574 } 575 } 576 } 577 578 // b/21569494 - Never receiving ANY of these events: requires debugging 579 private class MmsIncomingListener extends BroadcastReceiver { 580 @Override onReceive(Context context, Intent intent)581 public void onReceive(Context context, Intent intent) { 582 Log.d("MmsIncomingListener Received an Intent " + intent.toString()); 583 String action = intent.getAction(); 584 if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) { 585 Log.d("New MMS Downloaded"); 586 mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, new Bundle()); 587 } 588 else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { 589 Log.d("New Wap Push Received"); 590 mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, new Bundle()); 591 } 592 else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) { 593 Log.d("New Data SMS Received"); 594 mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, new Bundle()); 595 } 596 else { 597 Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString()); 598 } 599 } 600 } 601 formatPhoneNumber(String phoneNumber)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 onReceive(Context context, Intent intent)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 getETWSWarningType(int type)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 getCMASMessageClass(int messageclass)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 getCMASCategory(int category)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 getCMASResponseType(int type)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 getCMASSeverity(int severity)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 getCMASUrgency(int urgency)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 getCMASCertainty(int certainty)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 getGeographicalScope(int scope)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 getPriority(int priority)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 shutdown()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 MmsBuilder()883 public MmsBuilder() { 884 mRequest = new SendReq(); 885 mBody = new PduBody(); 886 } 887 setFromPhoneNumber(String number)888 public void setFromPhoneNumber(String number) { 889 mRequest.setFrom(new EncodedStringValue(number)); 890 } 891 setToPhoneNumber(String number)892 public void setToPhoneNumber(String number) { 893 mRequest.setTo(new EncodedStringValue[] { 894 new EncodedStringValue(number) }); 895 } 896 setToPhoneNumbers(List<String> number)897 public void setToPhoneNumbers(List<String> number) { 898 mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray())); 899 } 900 setSubject(String subject)901 public void setSubject(String subject) { 902 mRequest.setSubject(new EncodedStringValue(subject)); 903 } 904 setDate()905 public void setDate() { 906 setDate(System.currentTimeMillis() / 1000); 907 } 908 setDate(long time)909 public void setDate(long time) { 910 mRequest.setDate(time); 911 } 912 addMessageBody(String message)913 public void addMessageBody(String message) { 914 addMessageBody(message, true); 915 } 916 setMessageClass(String messageClass)917 public void setMessageClass(String messageClass) { 918 mRequest.setMessageClass(messageClass.getBytes()); 919 } 920 setMessagePriority(int priority)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 setDeliveryReport(boolean report)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 setReadReport(boolean report)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 setExpirySeconds(int seconds)945 public void setExpirySeconds(int seconds) { 946 mRequest.setExpiry(seconds); 947 } 948 build()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 addMessageBody(String message, boolean addSmilFragment)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("text0".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 addSmilTextFragment(String contentFilename)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