1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 4 import static android.os.Build.VERSION_CODES.R; 5 import static android.os.Build.VERSION_CODES.S; 6 7 import android.app.PendingIntent; 8 import android.content.Context; 9 import android.net.Uri; 10 import android.os.Bundle; 11 import android.telephony.SmsManager; 12 import android.text.TextUtils; 13 import java.util.ArrayList; 14 import java.util.List; 15 import java.util.Map; 16 import javax.annotation.Nullable; 17 import org.robolectric.RuntimeEnvironment; 18 import org.robolectric.annotation.Implementation; 19 import org.robolectric.annotation.Implements; 20 import org.robolectric.annotation.Resetter; 21 import org.robolectric.util.ReflectionHelpers; 22 23 @Implements(value = SmsManager.class) 24 public class ShadowSmsManager { 25 26 private String smscAddress; 27 private boolean hasSmscAddressPermission = true; 28 private static int defaultSmsSubscriptionId = -1; 29 30 @Resetter reset()31 public static void reset() { 32 if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP_MR1) { 33 Map<String, Object> sSubInstances = 34 ReflectionHelpers.getStaticField(SmsManager.class, "sSubInstances"); 35 sSubInstances.clear(); 36 defaultSmsSubscriptionId = -1; 37 } 38 } 39 40 // SMS functionality 41 42 protected TextSmsParams lastTextSmsParams; 43 protected TextMultipartParams lastTextMultipartParams; 44 protected DataMessageParams lastDataParams; 45 46 @Implementation sendDataMessage( String destinationAddress, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)47 protected void sendDataMessage( 48 String destinationAddress, 49 String scAddress, 50 short destinationPort, 51 byte[] data, 52 PendingIntent sentIntent, 53 PendingIntent deliveryIntent) { 54 if (TextUtils.isEmpty(destinationAddress)) { 55 throw new IllegalArgumentException("Invalid destinationAddress"); 56 } 57 58 lastDataParams = 59 new DataMessageParams( 60 destinationAddress, scAddress, destinationPort, data, sentIntent, deliveryIntent); 61 } 62 63 @Implementation sendTextMessage( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent)64 protected void sendTextMessage( 65 String destinationAddress, 66 String scAddress, 67 String text, 68 PendingIntent sentIntent, 69 PendingIntent deliveryIntent) { 70 if (TextUtils.isEmpty(destinationAddress)) { 71 throw new IllegalArgumentException("Invalid destinationAddress"); 72 } 73 74 if (TextUtils.isEmpty(text)) { 75 throw new IllegalArgumentException("Invalid message body"); 76 } 77 78 lastTextSmsParams = 79 new TextSmsParams(destinationAddress, scAddress, text, sentIntent, deliveryIntent); 80 } 81 82 @Implementation(minSdk = R) sendTextMessage( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, long messageId)83 protected void sendTextMessage( 84 String destinationAddress, 85 String scAddress, 86 String text, 87 PendingIntent sentIntent, 88 PendingIntent deliveryIntent, 89 long messageId) { 90 if (TextUtils.isEmpty(destinationAddress)) { 91 throw new IllegalArgumentException("Invalid destinationAddress"); 92 } 93 94 if (TextUtils.isEmpty(text)) { 95 throw new IllegalArgumentException("Invalid message body"); 96 } 97 98 lastTextSmsParams = 99 new TextSmsParams( 100 destinationAddress, scAddress, text, sentIntent, deliveryIntent, messageId); 101 } 102 103 @Implementation sendMultipartTextMessage( String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents)104 protected void sendMultipartTextMessage( 105 String destinationAddress, 106 String scAddress, 107 ArrayList<String> parts, 108 ArrayList<PendingIntent> sentIntents, 109 ArrayList<PendingIntent> deliveryIntents) { 110 if (TextUtils.isEmpty(destinationAddress)) { 111 throw new IllegalArgumentException("Invalid destinationAddress"); 112 } 113 114 if (parts == null) { 115 throw new IllegalArgumentException("Invalid message parts"); 116 } 117 118 lastTextMultipartParams = 119 new TextMultipartParams(destinationAddress, scAddress, parts, sentIntents, deliveryIntents); 120 } 121 122 /** 123 * @return Parameters for last call to {@link #sendDataMessage}. 124 */ getLastSentDataMessageParams()125 public DataMessageParams getLastSentDataMessageParams() { 126 return lastDataParams; 127 } 128 129 /** Clear last recorded parameters for {@link #sendDataMessage}. */ clearLastSentDataMessageParams()130 public void clearLastSentDataMessageParams() { 131 lastDataParams = null; 132 } 133 134 /** 135 * @return Parameters for last call to {@link #sendTextMessage}. 136 */ getLastSentTextMessageParams()137 public TextSmsParams getLastSentTextMessageParams() { 138 return lastTextSmsParams; 139 } 140 141 /** Clear last recorded parameters for {@link #sendTextMessage}. */ clearLastSentTextMessageParams()142 public void clearLastSentTextMessageParams() { 143 lastTextSmsParams = null; 144 } 145 146 /** 147 * @return Parameters for last call to {@link #sendMultipartTextMessage}. 148 */ getLastSentMultipartTextMessageParams()149 public TextMultipartParams getLastSentMultipartTextMessageParams() { 150 return lastTextMultipartParams; 151 } 152 153 /** Clear last recorded parameters for {@link #sendMultipartTextMessage}. */ clearLastSentMultipartTextMessageParams()154 public void clearLastSentMultipartTextMessageParams() { 155 lastTextMultipartParams = null; 156 } 157 158 public static class DataMessageParams { 159 private final String destinationAddress; 160 private final String scAddress; 161 private final short destinationPort; 162 private final byte[] data; 163 private final PendingIntent sentIntent; 164 private final PendingIntent deliveryIntent; 165 DataMessageParams( String destinationAddress, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)166 public DataMessageParams( 167 String destinationAddress, 168 String scAddress, 169 short destinationPort, 170 byte[] data, 171 PendingIntent sentIntent, 172 PendingIntent deliveryIntent) { 173 this.destinationAddress = destinationAddress; 174 this.scAddress = scAddress; 175 this.destinationPort = destinationPort; 176 this.data = data; 177 this.sentIntent = sentIntent; 178 this.deliveryIntent = deliveryIntent; 179 } 180 getDestinationAddress()181 public String getDestinationAddress() { 182 return destinationAddress; 183 } 184 getScAddress()185 public String getScAddress() { 186 return scAddress; 187 } 188 getDestinationPort()189 public short getDestinationPort() { 190 return destinationPort; 191 } 192 getData()193 public byte[] getData() { 194 return data; 195 } 196 getSentIntent()197 public PendingIntent getSentIntent() { 198 return sentIntent; 199 } 200 getDeliveryIntent()201 public PendingIntent getDeliveryIntent() { 202 return deliveryIntent; 203 } 204 } 205 206 public static class TextSmsParams { 207 private final String destinationAddress; 208 private final String scAddress; 209 private final String text; 210 private final PendingIntent sentIntent; 211 private final PendingIntent deliveryIntent; 212 private final long messageId; 213 TextSmsParams( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent)214 public TextSmsParams( 215 String destinationAddress, 216 String scAddress, 217 String text, 218 PendingIntent sentIntent, 219 PendingIntent deliveryIntent) { 220 this(destinationAddress, scAddress, text, sentIntent, deliveryIntent, 0L); 221 } 222 TextSmsParams( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, long messageId)223 public TextSmsParams( 224 String destinationAddress, 225 String scAddress, 226 String text, 227 PendingIntent sentIntent, 228 PendingIntent deliveryIntent, 229 long messageId) { 230 this.destinationAddress = destinationAddress; 231 this.scAddress = scAddress; 232 this.text = text; 233 this.sentIntent = sentIntent; 234 this.deliveryIntent = deliveryIntent; 235 this.messageId = messageId; 236 } 237 getDestinationAddress()238 public String getDestinationAddress() { 239 return destinationAddress; 240 } 241 getScAddress()242 public String getScAddress() { 243 return scAddress; 244 } 245 getText()246 public String getText() { 247 return text; 248 } 249 getSentIntent()250 public PendingIntent getSentIntent() { 251 return sentIntent; 252 } 253 getDeliveryIntent()254 public PendingIntent getDeliveryIntent() { 255 return deliveryIntent; 256 } 257 getMessageId()258 public long getMessageId() { 259 return messageId; 260 } 261 } 262 263 public static class TextMultipartParams { 264 private final String destinationAddress; 265 private final String scAddress; 266 private final List<String> parts; 267 private final List<PendingIntent> sentIntents; 268 private final List<PendingIntent> deliveryIntents; 269 private final long messageId; 270 TextMultipartParams( String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents)271 public TextMultipartParams( 272 String destinationAddress, 273 String scAddress, 274 ArrayList<String> parts, 275 ArrayList<PendingIntent> sentIntents, 276 ArrayList<PendingIntent> deliveryIntents) { 277 this(destinationAddress, scAddress, parts, sentIntents, deliveryIntents, 0L); 278 } 279 TextMultipartParams( String destinationAddress, String scAddress, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, long messageId)280 public TextMultipartParams( 281 String destinationAddress, 282 String scAddress, 283 List<String> parts, 284 List<PendingIntent> sentIntents, 285 List<PendingIntent> deliveryIntents, 286 long messageId) { 287 this.destinationAddress = destinationAddress; 288 this.scAddress = scAddress; 289 this.parts = parts; 290 this.sentIntents = sentIntents; 291 this.deliveryIntents = deliveryIntents; 292 this.messageId = messageId; 293 } 294 getDestinationAddress()295 public String getDestinationAddress() { 296 return destinationAddress; 297 } 298 getScAddress()299 public String getScAddress() { 300 return scAddress; 301 } 302 getParts()303 public List<String> getParts() { 304 return parts; 305 } 306 getSentIntents()307 public List<android.app.PendingIntent> getSentIntents() { 308 return sentIntents; 309 } 310 getDeliveryIntents()311 public List<android.app.PendingIntent> getDeliveryIntents() { 312 return deliveryIntents; 313 } 314 getMessageId()315 public long getMessageId() { 316 return messageId; 317 } 318 } 319 320 // MMS functionality 321 322 protected SendMultimediaMessageParams lastSentMultimediaMessageParams; 323 protected DownloadMultimediaMessageParams lastDownloadedMultimediaMessageParams; 324 325 @Implementation sendMultimediaMessage( Context context, Uri contentUri, @Nullable String locationUrl, @Nullable Bundle configOverrides, @Nullable PendingIntent sentIntent)326 protected void sendMultimediaMessage( 327 Context context, 328 Uri contentUri, 329 @Nullable String locationUrl, 330 @Nullable Bundle configOverrides, 331 @Nullable PendingIntent sentIntent) { 332 if (contentUri == null || TextUtils.isEmpty(contentUri.getHost())) { 333 throw new IllegalArgumentException("Invalid contentUri"); 334 } 335 336 lastSentMultimediaMessageParams = 337 new SendMultimediaMessageParams(contentUri, locationUrl, configOverrides, sentIntent, 0L); 338 } 339 340 @Implementation(minSdk = S) sendMultimediaMessage( Context context, Uri contentUri, @Nullable String locationUrl, @Nullable Bundle configOverrides, @Nullable PendingIntent sentIntent, long messageId)341 protected void sendMultimediaMessage( 342 Context context, 343 Uri contentUri, 344 @Nullable String locationUrl, 345 @Nullable Bundle configOverrides, 346 @Nullable PendingIntent sentIntent, 347 long messageId) { 348 if (contentUri == null || TextUtils.isEmpty(contentUri.getHost())) { 349 throw new IllegalArgumentException("Invalid contentUri"); 350 } 351 352 lastSentMultimediaMessageParams = 353 new SendMultimediaMessageParams( 354 contentUri, locationUrl, configOverrides, sentIntent, messageId); 355 } 356 357 @Implementation downloadMultimediaMessage( Context context, String locationUrl, Uri contentUri, @Nullable Bundle configOverrides, @Nullable PendingIntent sentIntent)358 protected void downloadMultimediaMessage( 359 Context context, 360 String locationUrl, 361 Uri contentUri, 362 @Nullable Bundle configOverrides, 363 @Nullable PendingIntent sentIntent) { 364 if (contentUri == null || TextUtils.isEmpty(contentUri.getHost())) { 365 throw new IllegalArgumentException("Invalid contentUri"); 366 } 367 368 if (TextUtils.isEmpty(locationUrl)) { 369 throw new IllegalArgumentException("Invalid locationUrl"); 370 } 371 372 lastDownloadedMultimediaMessageParams = 373 new DownloadMultimediaMessageParams( 374 contentUri, locationUrl, configOverrides, sentIntent, 0L); 375 } 376 377 @Implementation(minSdk = S) downloadMultimediaMessage( Context context, String locationUrl, Uri contentUri, @Nullable Bundle configOverrides, @Nullable PendingIntent sentIntent, long messageId)378 protected void downloadMultimediaMessage( 379 Context context, 380 String locationUrl, 381 Uri contentUri, 382 @Nullable Bundle configOverrides, 383 @Nullable PendingIntent sentIntent, 384 long messageId) { 385 if (contentUri == null || TextUtils.isEmpty(contentUri.getHost())) { 386 throw new IllegalArgumentException("Invalid contentUri"); 387 } 388 389 if (TextUtils.isEmpty(locationUrl)) { 390 throw new IllegalArgumentException("Invalid locationUrl"); 391 } 392 393 lastDownloadedMultimediaMessageParams = 394 new org.robolectric.shadows.ShadowSmsManager.DownloadMultimediaMessageParams( 395 contentUri, locationUrl, configOverrides, sentIntent, messageId); 396 } 397 398 /** 399 * @return Parameters for last call to {@link #sendMultimediaMessage}. 400 */ getLastSentMultimediaMessageParams()401 public SendMultimediaMessageParams getLastSentMultimediaMessageParams() { 402 return lastSentMultimediaMessageParams; 403 } 404 405 /** Clear last recorded parameters for {@link #sendMultimediaMessage}. */ clearLastSentMultimediaMessageParams()406 public void clearLastSentMultimediaMessageParams() { 407 lastSentMultimediaMessageParams = null; 408 } 409 410 /** 411 * @return Parameters for last call to {@link #downloadMultimediaMessage}. 412 */ getLastDownloadedMultimediaMessageParams()413 public DownloadMultimediaMessageParams getLastDownloadedMultimediaMessageParams() { 414 return lastDownloadedMultimediaMessageParams; 415 } 416 417 /** Clear last recorded parameters for {@link #downloadMultimediaMessage}. */ clearLastDownloadedMultimediaMessageParams()418 public void clearLastDownloadedMultimediaMessageParams() { 419 lastDownloadedMultimediaMessageParams = null; 420 } 421 422 @Implementation(minSdk = R) sendMultipartTextMessage( String destinationAddress, String scAddress, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, long messageId)423 protected void sendMultipartTextMessage( 424 String destinationAddress, 425 String scAddress, 426 List<String> parts, 427 List<PendingIntent> sentIntents, 428 List<PendingIntent> deliveryIntents, 429 long messageId) { 430 if (TextUtils.isEmpty(destinationAddress)) { 431 throw new IllegalArgumentException("Invalid destinationAddress"); 432 } 433 434 if (parts == null) { 435 throw new IllegalArgumentException("Invalid message parts"); 436 } 437 438 lastTextMultipartParams = 439 new TextMultipartParams( 440 destinationAddress, scAddress, parts, sentIntents, deliveryIntents, messageId); 441 } 442 443 /** 444 * Base class for testable parameters from calls to either {@link #downloadMultimediaMessage} or 445 * {@link #downloadMultimediaMessage}. 446 */ 447 public abstract static class MultimediaMessageParams { 448 private final Uri contentUri; 449 protected final String locationUrl; 450 @Nullable private final Bundle configOverrides; 451 @Nullable protected final PendingIntent pendingIntent; 452 protected final long messageId; 453 MultimediaMessageParams( Uri contentUri, String locationUrl, @Nullable Bundle configOverrides, @Nullable PendingIntent pendingIntent, long messageId)454 protected MultimediaMessageParams( 455 Uri contentUri, 456 String locationUrl, 457 @Nullable Bundle configOverrides, 458 @Nullable PendingIntent pendingIntent, 459 long messageId) { 460 this.contentUri = contentUri; 461 this.locationUrl = locationUrl; 462 this.configOverrides = configOverrides; 463 this.pendingIntent = pendingIntent; 464 this.messageId = messageId; 465 } 466 getContentUri()467 public Uri getContentUri() { 468 return contentUri; 469 } 470 471 @Nullable getConfigOverrides()472 public Bundle getConfigOverrides() { 473 return configOverrides; 474 } 475 getMessageId()476 public long getMessageId() { 477 return messageId; 478 } 479 } 480 481 /** Testable parameters from calls to {@link #sendMultimediaMessage}. */ 482 public static final class SendMultimediaMessageParams extends MultimediaMessageParams { SendMultimediaMessageParams( Uri contentUri, @Nullable String locationUrl, @Nullable Bundle configOverrides, @Nullable PendingIntent pendingIntent, long messageId)483 public SendMultimediaMessageParams( 484 Uri contentUri, 485 @Nullable String locationUrl, 486 @Nullable Bundle configOverrides, 487 @Nullable PendingIntent pendingIntent, 488 long messageId) { 489 super(contentUri, locationUrl, configOverrides, pendingIntent, messageId); 490 } 491 492 @Nullable getLocationUrl()493 public String getLocationUrl() { 494 return locationUrl; 495 } 496 497 @Nullable getSentIntent()498 public PendingIntent getSentIntent() { 499 return pendingIntent; 500 } 501 } 502 503 /** Testable parameters from calls to {@link #downloadMultimediaMessage}. */ 504 public static final class DownloadMultimediaMessageParams extends MultimediaMessageParams { DownloadMultimediaMessageParams( Uri contentUri, String locationUrl, @Nullable Bundle configOverrides, @Nullable PendingIntent pendingIntent, long messageId)505 public DownloadMultimediaMessageParams( 506 Uri contentUri, 507 String locationUrl, 508 @Nullable Bundle configOverrides, 509 @Nullable PendingIntent pendingIntent, 510 long messageId) { 511 super(contentUri, locationUrl, configOverrides, pendingIntent, messageId); 512 } 513 getLocationUrl()514 public String getLocationUrl() { 515 return locationUrl; 516 } 517 518 @Nullable getDownloadedIntent()519 public PendingIntent getDownloadedIntent() { 520 return pendingIntent; 521 } 522 } 523 524 /** 525 * Sets a boolean value to simulate whether or not the required permissions to call {@link 526 * #getSmscAddress()} have been granted. 527 */ setSmscAddressPermission(boolean smscAddressPermission)528 public void setSmscAddressPermission(boolean smscAddressPermission) { 529 this.hasSmscAddressPermission = smscAddressPermission; 530 } 531 532 /** 533 * Returns {@code null} by default or the value specified via {@link #setSmscAddress(String)}. 534 * Required permission is set by {@link #setSmscAddressPermission(boolean)}. 535 */ 536 @Implementation(minSdk = R) getSmscAddress()537 protected String getSmscAddress() { 538 if (!hasSmscAddressPermission) { 539 throw new SecurityException(); 540 } 541 return smscAddress; 542 } 543 544 /** Sets the value to be returned by {@link #getDefaultSmsSubscriptionId()}. */ setDefaultSmsSubscriptionId(int id)545 public static void setDefaultSmsSubscriptionId(int id) { 546 defaultSmsSubscriptionId = id; 547 } 548 549 /** 550 * Returns {@code -1} by default or the value specified in {@link 551 * #setDefaultSmsSubscriptionId(int)}. 552 */ 553 @Implementation(minSdk = R) getDefaultSmsSubscriptionId()554 protected static int getDefaultSmsSubscriptionId() { 555 return defaultSmsSubscriptionId; 556 } 557 558 /** Sets the value returned by {@link SmsManager#getSmscAddress()}. */ setSmscAddress(String smscAddress)559 public void setSmscAddress(String smscAddress) { 560 this.smscAddress = smscAddress; 561 } 562 } 563