1 package com.xtremelabs.robolectric.shadows; 2 3 import android.app.Application; 4 import android.appwidget.AppWidgetManager; 5 import android.content.BroadcastReceiver; 6 import android.content.ComponentName; 7 import android.content.ContentResolver; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.IntentFilter; 11 import android.content.ServiceConnection; 12 import android.content.res.Resources; 13 import android.os.IBinder; 14 import android.os.Looper; 15 import android.view.LayoutInflater; 16 import android.widget.Toast; 17 import com.xtremelabs.robolectric.Robolectric; 18 import com.xtremelabs.robolectric.internal.Implementation; 19 import com.xtremelabs.robolectric.internal.Implements; 20 import com.xtremelabs.robolectric.internal.RealObject; 21 import com.xtremelabs.robolectric.res.ResourceLoader; 22 import com.xtremelabs.robolectric.tester.org.apache.http.FakeHttpLayer; 23 import com.xtremelabs.robolectric.util.Scheduler; 24 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.Iterator; 28 import java.util.List; 29 import java.util.Map; 30 31 import static com.xtremelabs.robolectric.Robolectric.newInstanceOf; 32 import static com.xtremelabs.robolectric.Robolectric.shadowOf; 33 34 /** 35 * Shadows the {@code android.app.Application} class. 36 */ 37 @SuppressWarnings({"UnusedDeclaration"}) 38 @Implements(Application.class) 39 public class ShadowApplication extends ShadowContextWrapper { 40 private static final Map<String, String> SYSTEM_SERVICE_MAP = new HashMap<String, String>(); 41 42 static { 43 // note that these are different! 44 // They specify concrete classes within Robolectric for interfaces or abstract classes defined by Android 45 SYSTEM_SERVICE_MAP.put(Context.WINDOW_SERVICE, "com.xtremelabs.robolectric.tester.android.view.TestWindowManager"); 46 SYSTEM_SERVICE_MAP.put(Context.CLIPBOARD_SERVICE, "com.xtremelabs.robolectric.tester.android.text.TestClipboardManager"); 47 SYSTEM_SERVICE_MAP.put(Context.SENSOR_SERVICE, "android.hardware.TestSensorManager"); 48 SYSTEM_SERVICE_MAP.put(Context.VIBRATOR_SERVICE, "android.os.TestVibrator"); 49 50 // the rest are as mapped in docs... 51 SYSTEM_SERVICE_MAP.put(Context.LAYOUT_INFLATER_SERVICE, "android.view.LayoutInflater"); 52 SYSTEM_SERVICE_MAP.put(Context.ACTIVITY_SERVICE, "android.app.ActivityManager"); 53 SYSTEM_SERVICE_MAP.put(Context.POWER_SERVICE, "android.os.PowerManager"); 54 SYSTEM_SERVICE_MAP.put(Context.ALARM_SERVICE, "android.app.AlarmManager"); 55 SYSTEM_SERVICE_MAP.put(Context.NOTIFICATION_SERVICE, "android.app.NotificationManager"); 56 SYSTEM_SERVICE_MAP.put(Context.KEYGUARD_SERVICE, "android.app.KeyguardManager"); 57 SYSTEM_SERVICE_MAP.put(Context.LOCATION_SERVICE, "android.location.LocationManager"); 58 SYSTEM_SERVICE_MAP.put(Context.SEARCH_SERVICE, "android.app.SearchManager"); 59 SYSTEM_SERVICE_MAP.put(Context.STORAGE_SERVICE, "android.os.storage.StorageManager"); 60 SYSTEM_SERVICE_MAP.put(Context.CONNECTIVITY_SERVICE, "android.net.ConnectivityManager"); 61 SYSTEM_SERVICE_MAP.put(Context.WIFI_SERVICE, "android.net.wifi.WifiManager"); 62 SYSTEM_SERVICE_MAP.put(Context.AUDIO_SERVICE, "android.media.AudioManager"); 63 SYSTEM_SERVICE_MAP.put(Context.TELEPHONY_SERVICE, "android.telephony.TelephonyManager"); 64 SYSTEM_SERVICE_MAP.put(Context.INPUT_METHOD_SERVICE, "android.view.inputmethod.InputMethodManager"); 65 SYSTEM_SERVICE_MAP.put(Context.UI_MODE_SERVICE, "android.app.UiModeManager"); 66 SYSTEM_SERVICE_MAP.put(Context.DOWNLOAD_SERVICE, "android.app.DownloadManager"); 67 } 68 69 @RealObject private Application realApplication; 70 71 private ResourceLoader resourceLoader; 72 private ContentResolver contentResolver; 73 private Map<String, Object> systemServices = new HashMap<String, Object>(); 74 private List<Intent> startedActivities = new ArrayList<Intent>(); 75 private List<Intent> startedServices = new ArrayList<Intent>(); 76 private List<Intent> stoppedServies = new ArrayList<Intent>(); 77 private List<Intent> broadcastIntents = new ArrayList<Intent>(); 78 private List<ServiceConnection> unboundServiceConnections = new ArrayList<ServiceConnection>(); 79 private List<Wrapper> registeredReceivers = new ArrayList<Wrapper>(); 80 private Map<String, Intent> stickyIntents = new HashMap<String, Intent>(); 81 private FakeHttpLayer fakeHttpLayer = new FakeHttpLayer(); 82 private Looper mainLooper = ShadowLooper.myLooper(); 83 private Scheduler backgroundScheduler = new Scheduler(); 84 private Map<String, Map<String, Object>> sharedPreferenceMap = new HashMap<String, Map<String, Object>>(); 85 private ArrayList<Toast> shownToasts = new ArrayList<Toast>(); 86 private ShadowAlertDialog latestAlertDialog; 87 private ShadowDialog latestDialog; 88 private Object bluetoothAdapter = Robolectric.newInstanceOf("android.bluetooth.BluetoothAdapter"); 89 private Resources resources; 90 91 // these are managed by the AppSingletonizier... kinda gross, sorry [xw] 92 LayoutInflater layoutInflater; 93 AppWidgetManager appWidgetManager; 94 private ServiceConnection serviceConnection; 95 private ComponentName componentNameForBindService; 96 private IBinder serviceForBindService; 97 private List<String> unbindableActions = new ArrayList<String>(); 98 99 /** 100 * Associates a {@code ResourceLoader} with an {@code Application} instance 101 * 102 * @param application application 103 * @param resourceLoader resource loader 104 * @return the application 105 * todo: make this non-static? 106 */ 107 public static Application bind(Application application, ResourceLoader resourceLoader) { 108 ShadowApplication shadowApplication = shadowOf(application); 109 if (shadowApplication.resourceLoader != null) throw new RuntimeException("ResourceLoader already set!"); 110 shadowApplication.resourceLoader = resourceLoader; 111 shadowApplication.resources = ShadowResources.bind(new Resources(null, null, null), resourceLoader); 112 return application; 113 } 114 115 public List<Toast> getShownToasts() { 116 return shownToasts; 117 } 118 119 public Scheduler getBackgroundScheduler() { 120 return backgroundScheduler; 121 } 122 123 @Override 124 @Implementation 125 public Context getApplicationContext() { 126 return realApplication; 127 } 128 129 @Override 130 @Implementation 131 public Resources getResources() { 132 if (resources == null ) { 133 resources = ShadowResources.bind(new Resources(null, null, null), resourceLoader); 134 } 135 return resources; 136 } 137 138 @Implementation 139 @Override 140 public ContentResolver getContentResolver() { 141 if (contentResolver == null) { 142 contentResolver = new ContentResolver(realApplication) { 143 }; 144 } 145 return contentResolver; 146 } 147 148 @Implementation 149 @Override 150 public Object getSystemService(String name) { 151 if (name.equals(Context.LAYOUT_INFLATER_SERVICE)) { 152 return LayoutInflater.from(realApplication); 153 } else { 154 Object service = systemServices.get(name); 155 if (service == null) { 156 String serviceClassName = SYSTEM_SERVICE_MAP.get(name); 157 if (serviceClassName != null) { 158 try { 159 service = newInstanceOf(Class.forName(serviceClassName)); 160 } catch (ClassNotFoundException e) { 161 throw new RuntimeException(e); 162 } 163 systemServices.put(name, service); 164 } 165 } 166 return service; 167 } 168 } 169 170 @Implementation 171 @Override 172 public void startActivity(Intent intent) { 173 startedActivities.add(intent); 174 } 175 176 @Implementation 177 @Override 178 public ComponentName startService(Intent intent) { 179 startedServices.add(intent); 180 return new ComponentName("some.service.package", "SomeServiceName-FIXME"); 181 } 182 183 @Implementation 184 @Override 185 public boolean stopService(Intent name) { 186 stoppedServies.add(name); 187 188 return startedServices.contains(name); 189 } 190 191 public void setComponentNameAndServiceForBindService(ComponentName name, IBinder service) { 192 this.componentNameForBindService = name; 193 this.serviceForBindService = service; 194 } 195 196 @Implementation 197 public boolean bindService(Intent intent, final ServiceConnection serviceConnection, int i) { 198 if (unbindableActions.contains(intent.getAction())) { 199 return false; 200 } 201 startedServices.add(intent); 202 shadowOf(Looper.getMainLooper()).post(new Runnable() { 203 @Override 204 public void run() { 205 serviceConnection.onServiceConnected(componentNameForBindService, serviceForBindService); 206 } 207 }, 0); 208 return true; 209 } 210 211 @Implementation 212 public void unbindService(final ServiceConnection serviceConnection) { 213 unboundServiceConnections.add(serviceConnection); 214 shadowOf(Looper.getMainLooper()).post(new Runnable() { 215 @Override 216 public void run() { 217 serviceConnection.onServiceDisconnected(componentNameForBindService); 218 } 219 }, 0); 220 } 221 222 public List<ServiceConnection> getUnboundServiceConnections() { 223 return unboundServiceConnections; 224 } 225 /** 226 * Consumes the most recent {@code Intent} started by {@link #startActivity(android.content.Intent)} and returns it. 227 * 228 * @return the most recently started {@code Intent} 229 */ 230 @Override 231 public Intent getNextStartedActivity() { 232 if (startedActivities.isEmpty()) { 233 return null; 234 } else { 235 return startedActivities.remove(0); 236 } 237 } 238 239 /** 240 * Returns the most recent {@code Intent} started by {@link #startActivity(android.content.Intent)} without 241 * consuming it. 242 * 243 * @return the most recently started {@code Intent} 244 */ 245 @Override 246 public Intent peekNextStartedActivity() { 247 if (startedActivities.isEmpty()) { 248 return null; 249 } else { 250 return startedActivities.get(0); 251 } 252 } 253 254 /** 255 * Consumes the most recent {@code Intent} started by {@link #startService(android.content.Intent)} and returns it. 256 * 257 * @return the most recently started {@code Intent} 258 */ 259 @Override 260 public Intent getNextStartedService() { 261 if (startedServices.isEmpty()) { 262 return null; 263 } else { 264 return startedServices.remove(0); 265 } 266 } 267 268 /** 269 * Returns the most recent {@code Intent} started by {@link #startService(android.content.Intent)} without 270 * consuming it. 271 * 272 * @return the most recently started {@code Intent} 273 */ 274 @Override 275 public Intent peekNextStartedService() { 276 if (startedServices.isEmpty()) { 277 return null; 278 } else { 279 return startedServices.get(0); 280 } 281 } 282 283 /** 284 * Clears all {@code Intent} started by {@link #startService(android.content.Intent)} 285 */ 286 @Override 287 public void clearStartedServices() { 288 startedServices.clear(); 289 } 290 291 /** 292 * Consumes the {@code Intent} requested to stop a service by {@link #stopService(android.content.Intent)} 293 * from the bottom of the stack of stop requests. 294 */ 295 @Override 296 public Intent getNextStoppedService() { 297 if (stoppedServies.isEmpty()) { 298 return null; 299 } else { 300 return stoppedServies.remove(0); 301 } 302 } 303 304 /** 305 * Non-Android accessor (and a handy way to get a working {@code ResourceLoader} 306 * 307 * @return the {@code ResourceLoader} associated with this Application 308 */ 309 public ResourceLoader getResourceLoader() { 310 return resourceLoader; 311 } 312 313 /** 314 * Broadcasts the {@code Intent} by iterating through the registered receivers, invoking their filters, and calling 315 * {@code onRecieve(Application, Intent)} as appropriate. Does not enqueue the {@code Intent} for later inspection. 316 * 317 * @param intent the {@code Intent} to broadcast 318 * todo: enqueue the Intent for later inspection 319 */ 320 @Override 321 @Implementation 322 public void sendBroadcast(Intent intent) { 323 broadcastIntents.add(intent); 324 325 List<Wrapper> copy = new ArrayList<Wrapper>(); 326 copy.addAll(registeredReceivers); 327 for (Wrapper wrapper : copy) { 328 if (wrapper.intentFilter.matchAction(intent.getAction())) { 329 wrapper.broadcastReceiver.onReceive(realApplication, intent); 330 } 331 } 332 } 333 334 public List<Intent> getBroadcastIntents() { 335 return broadcastIntents; 336 } 337 338 @Implementation 339 public void sendStickyBroadcast(Intent intent) { 340 stickyIntents.put(intent.getAction(), intent); 341 sendBroadcast(intent); 342 } 343 344 /** 345 * Always returns {@code null} 346 * 347 * @return {@code null} 348 */ 349 @Override 350 @Implementation 351 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 352 return registerReceiverWithContext(receiver, filter, realApplication); 353 } 354 355 Intent registerReceiverWithContext(BroadcastReceiver receiver, IntentFilter filter, Context context) { 356 if (receiver != null) { 357 registeredReceivers.add(new Wrapper(receiver, filter, context)); 358 } 359 return getStickyIntent(filter); 360 } 361 362 private Intent getStickyIntent(IntentFilter filter) { 363 for (Intent stickyIntent : stickyIntents.values()) { 364 String action = null; 365 for (int i = 0; i < filter.countActions(); i++) { 366 action = filter.getAction(i); 367 if (stickyIntent.getAction().equals(action)) { 368 return stickyIntent; 369 } 370 } 371 } 372 373 return null; 374 } 375 376 @Override 377 @Implementation 378 public void unregisterReceiver(BroadcastReceiver broadcastReceiver) { 379 boolean found = false; 380 Iterator<Wrapper> iterator = registeredReceivers.iterator(); 381 while (iterator.hasNext()) { 382 Wrapper wrapper = iterator.next(); 383 if (wrapper.broadcastReceiver == broadcastReceiver) { 384 iterator.remove(); 385 found = true; 386 } 387 } 388 if (!found) { 389 throw new IllegalArgumentException("Receiver not registered: " + broadcastReceiver); 390 } 391 } 392 393 /** 394 * Iterates through all of the registered receivers on this {@code Application} and if any of them match the given 395 * {@code Context} object throws a {@code RuntimeException} 396 * 397 * @param context the {@code Context} to check for on each of the remaining registered receivers 398 * @param type the type to report for the context if an exception is thrown 399 * @throws RuntimeException if there are any recievers registered with the given {@code Context} 400 */ 401 public void assertNoBroadcastListenersRegistered(Context context, String type) { 402 for (Wrapper registeredReceiver : registeredReceivers) { 403 if (registeredReceiver.context == context) { 404 RuntimeException e = new IllegalStateException(type + " " + context + " leaked has leaked IntentReceiver " 405 + registeredReceiver.broadcastReceiver + " that was originally registered here. " + 406 "Are you missing a call to unregisterReceiver()?"); 407 e.setStackTrace(registeredReceiver.exception.getStackTrace()); 408 throw e; 409 } 410 } 411 } 412 413 public void assertNoBroadcastListenersOfActionRegistered(Context context, String action) { 414 for (Wrapper registeredReceiver : registeredReceivers) { 415 if (registeredReceiver.context == context) { 416 Iterator<String> actions = registeredReceiver.intentFilter.actionsIterator(); 417 while (actions.hasNext()) { 418 if (actions.next().equals(action)) { 419 RuntimeException e = new IllegalStateException("Unexpected BroadcastReceiver on " + context + 420 " with action " + action + " " 421 + registeredReceiver.broadcastReceiver + " that was originally registered here:"); 422 e.setStackTrace(registeredReceiver.exception.getStackTrace()); 423 throw e; 424 } 425 } 426 } 427 } 428 } 429 430 public boolean hasReceiverForIntent(Intent intent) { 431 for (Wrapper wrapper : registeredReceivers) { 432 if (wrapper.intentFilter.matchAction(intent.getAction())) { 433 return true; 434 } 435 } 436 return false; 437 } 438 439 public List<BroadcastReceiver> getReceiversForIntent(Intent intent) { 440 ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<BroadcastReceiver>(); 441 for (Wrapper wrapper : registeredReceivers) { 442 if (wrapper.intentFilter.matchAction(intent.getAction())) { 443 broadcastReceivers.add(wrapper.getBroadcastReceiver()); 444 } 445 } 446 return broadcastReceivers; 447 } 448 449 /** 450 * Non-Android accessor. 451 * 452 * @return list of {@link Wrapper}s for registered receivers 453 */ 454 public List<Wrapper> getRegisteredReceivers() { 455 return registeredReceivers; 456 } 457 458 /** 459 * Non-Android accessor. 460 * 461 * @return the layout inflater used by this {@code Application} 462 */ 463 public LayoutInflater getLayoutInflater() { 464 return layoutInflater; 465 } 466 467 /** 468 * Non-Android accessor. 469 * 470 * @return the app widget manager used by this {@code Application} 471 */ 472 public AppWidgetManager getAppWidgetManager() { 473 return appWidgetManager; 474 } 475 476 public FakeHttpLayer getFakeHttpLayer() { 477 return fakeHttpLayer; 478 } 479 480 @Override 481 @Implementation 482 public Looper getMainLooper() { 483 return mainLooper; 484 } 485 486 public Map<String, Map<String, Object>> getSharedPreferenceMap() { 487 return sharedPreferenceMap; 488 } 489 490 public ShadowAlertDialog getLatestAlertDialog() { 491 return latestAlertDialog; 492 } 493 494 public void setLatestAlertDialog(ShadowAlertDialog latestAlertDialog) { 495 this.latestAlertDialog = latestAlertDialog; 496 } 497 498 public ShadowDialog getLatestDialog() { 499 return latestDialog; 500 } 501 502 public void setLatestDialog(ShadowDialog latestDialog) { 503 this.latestDialog = latestDialog; 504 } 505 506 public Object getBluetoothAdapter() { 507 return bluetoothAdapter; 508 } 509 510 public void declareActionUnbindable(String action) { 511 unbindableActions.add(action); 512 } 513 514 public void setSystemService(String key, Object service) { 515 systemServices.put(key, service); 516 } 517 518 public class Wrapper { 519 public BroadcastReceiver broadcastReceiver; 520 public IntentFilter intentFilter; 521 public Context context; 522 public Throwable exception; 523 524 public Wrapper(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, Context context) { 525 this.broadcastReceiver = broadcastReceiver; 526 this.intentFilter = intentFilter; 527 this.context = context; 528 exception = new Throwable(); 529 } 530 531 public BroadcastReceiver getBroadcastReceiver() { 532 return broadcastReceiver; 533 } 534 535 public IntentFilter getIntentFilter() { 536 return intentFilter; 537 } 538 539 public Context getContext() { 540 return context; 541 } 542 } 543 } 544