blob: 95a5266102f5258ff923cad8c57a44faf00360af [file] [log] [blame]
package com.xtremelabs.robolectric.shadows;
import android.app.AlarmManager;
import android.app.Application;
import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.widget.Toast;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import com.xtremelabs.robolectric.internal.RealObject;
import com.xtremelabs.robolectric.res.ResourceLoader;
import com.xtremelabs.robolectric.util.Scheduler;
import com.xtremelabs.robolectric.view.TestWindowManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static com.xtremelabs.robolectric.Robolectric.newInstanceOf;
import static com.xtremelabs.robolectric.Robolectric.shadowOf;
/**
* Shadows the {@code android.app.Application} class.
*/
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Application.class)
public class ShadowApplication extends ShadowContextWrapper {
@RealObject private Application realApplication;
private ResourceLoader resourceLoader;
private ContentResolver contentResolver;
private AlarmManager alarmManager;
private LocationManager locationManager;
private ConnectivityManager connectivityManager;
private WifiManager wifiManager;
private WindowManager windowManager;
private AudioManager audioManager;
private List<Intent> startedActivities = new ArrayList<Intent>();
private List<Intent> startedServices = new ArrayList<Intent>();
private List<Wrapper> registeredReceivers = new ArrayList<Wrapper>();
private FakeHttpLayer fakeHttpLayer = new FakeHttpLayer();
private final Looper mainLooper = newInstanceOf(Looper.class);
private Looper currentLooper = mainLooper;
private Scheduler backgroundScheduler = new Scheduler();
private Map<String, Hashtable<String, Object>> sharedPreferenceMap = new HashMap<String, Hashtable<String, Object>>();
private ArrayList<Toast> shownToasts = new ArrayList<Toast>();
private ShadowAlertDialog latestAlertDialog;
private ShadowDialog latestDialog;
private BluetoothAdapter bluetoothAdapter = Robolectric.newInstanceOf(BluetoothAdapter.class);
// these are managed by the AppSingletonizier... kinda gross, sorry [xw]
LayoutInflater layoutInflater;
AppWidgetManager appWidgetManager;
/**
* Associates a {@code ResourceLoader} with an {@code Application} instance
*
* @param application application
* @param resourceLoader resource loader
* @return the application
* todo: make this non-static?
*/
public static Application bind(Application application, ResourceLoader resourceLoader) {
ShadowApplication shadowApplication = shadowOf(application);
if (shadowApplication.resourceLoader != null) throw new RuntimeException("ResourceLoader already set!");
shadowApplication.resourceLoader = resourceLoader;
return application;
}
public List<Toast> getShownToasts() {
return shownToasts;
}
public Scheduler getBackgroundScheduler() {
return backgroundScheduler;
}
@Override @Implementation
public Context getApplicationContext() {
return realApplication;
}
@Override @Implementation
public Resources getResources() {
return ShadowResources.bind(new Resources(null, null, null), resourceLoader);
}
@Implementation
@Override public ContentResolver getContentResolver() {
if (contentResolver == null) {
contentResolver = new ContentResolver(realApplication) {
};
}
return contentResolver;
}
@Implementation
@Override public Object getSystemService(String name) {
if (name.equals(Context.LAYOUT_INFLATER_SERVICE)) {
return LayoutInflater.from(realApplication);
} else if (name.equals(Context.ALARM_SERVICE)) {
return alarmManager == null ? alarmManager = newInstanceOf(AlarmManager.class) : alarmManager;
} else if (name.equals(Context.LOCATION_SERVICE)) {
return locationManager == null ? locationManager = newInstanceOf(LocationManager.class) : locationManager;
} else if (name.equals(Context.WIFI_SERVICE)) {
return wifiManager == null ? wifiManager = newInstanceOf(WifiManager.class) : wifiManager;
} else if (name.equals(Context.WINDOW_SERVICE)) {
return windowManager == null ? windowManager = new TestWindowManager() : windowManager;
} else if (name.equals(Context.AUDIO_SERVICE)) {
return audioManager == null ? audioManager = newInstanceOf(AudioManager.class) : audioManager;
} else if (name.equals(Context.CONNECTIVITY_SERVICE)) {
return connectivityManager == null ? connectivityManager = newInstanceOf(ConnectivityManager.class) : connectivityManager;
}
return null;
}
@Implementation
@Override public void startActivity(Intent intent) {
startedActivities.add(intent);
}
@Implementation
@Override public ComponentName startService(Intent intent) {
startedServices.add(intent);
return new ComponentName("some.service.package", "SomeServiceName-FIXME");
}
/**
* Consumes the most recent {@code Intent} started by {@link #startActivity(android.content.Intent)} and returns it.
*
* @return the most recently started {@code Intent}
*/
@Override public Intent getNextStartedActivity() {
if (startedActivities.isEmpty()) {
return null;
} else {
return startedActivities.remove(0);
}
}
/**
* Returns the most recent {@code Intent} started by {@link #startActivity(android.content.Intent)} without
* consuming it.
*
* @return the most recently started {@code Intent}
*/
@Override public Intent peekNextStartedActivity() {
if (startedActivities.isEmpty()) {
return null;
} else {
return startedActivities.get(0);
}
}
/**
* Consumes the most recent {@code Intent} started by {@link #startService(android.content.Intent)} and returns it.
*
* @return the most recently started {@code Intent}
*/
@Override public Intent getNextStartedService() {
if (startedServices.isEmpty()) {
return null;
} else {
return startedServices.remove(0);
}
}
/**
* Returns the most recent {@code Intent} started by {@link #startService(android.content.Intent)} without
* consuming it.
*
* @return the most recently started {@code Intent}
*/
@Override public Intent peekNextStartedService() {
if (startedServices.isEmpty()) {
return null;
} else {
return startedServices.get(0);
}
}
/**
* Non-Android accessor (and a handy way to get a working {@code ResourceLoader}
*
* @return the {@code ResourceLoader} associated with this Application
*/
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
/**
* Broadcasts the {@code Intent} by iterating through the registered receivers, invoking their filters, and calling
* {@code onRecieve(Application, Intent)} as appropriate. Does not enqueue the {@code Intent} for later inspection.
*
* @param intent the {@code Intent} to broadcast
* todo: enqueue the Intent for later inspection
*/
@Override @Implementation
public void sendBroadcast(Intent intent) {
for (Wrapper wrapper : registeredReceivers) {
if (wrapper.intentFilter.matchAction(intent.getAction())) {
wrapper.broadcastReceiver.onReceive(realApplication, intent);
}
}
}
/**
* Always returns {@code null}
*
* @return {@code null}
*/
@Override @Implementation
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiverWithContext(receiver, filter, realApplication);
}
Intent registerReceiverWithContext(BroadcastReceiver receiver, IntentFilter filter, Context context) {
registeredReceivers.add(new Wrapper(receiver, filter, context));
return null;
}
@Override @Implementation
public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
boolean found = false;
Iterator<Wrapper> iterator = registeredReceivers.iterator();
while (iterator.hasNext()) {
Wrapper wrapper = iterator.next();
if (wrapper.broadcastReceiver == broadcastReceiver) {
iterator.remove();
found = true;
}
}
if (!found) {
throw new IllegalArgumentException("Receiver not registered: " + broadcastReceiver);
}
}
/**
* Iterates through all of the registered receivers on this {@code Application} and if any of them match the given
* {@code Context} object throws a {@code RuntimeException}
*
* @param context the {@code Context} to check for on each of the remaining registered receivers
* @param type the type to report for the context if an exception is thrown
* @throws RuntimeException if there are any recievers registered with the given {@code Context}
*/
public void assertNoBroadcastListenersRegistered(Context context, String type) {
for (Wrapper registeredReceiver : registeredReceivers) {
if (registeredReceiver.context == context) {
RuntimeException e = new IllegalStateException(type + " " + context + " leaked has leaked IntentReceiver "
+ registeredReceiver.broadcastReceiver + " that was originally registered here. " +
"Are you missing a call to unregisterReceiver()?");
e.setStackTrace(registeredReceiver.exception.getStackTrace());
throw e;
}
}
}
/**
* Non-Android accessor.
*
* @return list of {@link Wrapper}s for registered receivers
*/
public List<Wrapper> getRegisteredReceivers() {
return registeredReceivers;
}
/**
* Non-Android accessor.
*
* @return the layout inflater used by this {@code Application}
*/
public LayoutInflater getLayoutInflater() {
return layoutInflater;
}
/**
* Non-Android accessor.
*
* @return the app widget manager used by this {@code Application}
*/
public AppWidgetManager getAppWidgetManager() {
return appWidgetManager;
}
public FakeHttpLayer getFakeHttpLayer() {
return fakeHttpLayer;
}
@Override @Implementation
public Looper getMainLooper() {
return mainLooper;
}
public Looper getCurrentLooper() {
return currentLooper;
}
public Map<String, Hashtable<String, Object>> getSharedPreferenceMap() {
return sharedPreferenceMap;
}
public ShadowAlertDialog getLatestAlertDialog() {
return latestAlertDialog;
}
public void setLatestAlertDialog(ShadowAlertDialog latestAlertDialog) {
this.latestAlertDialog = latestAlertDialog;
}
public ShadowDialog getLatestDialog() {
return latestDialog;
}
public void setLatestDialog(ShadowDialog latestDialog) {
this.latestDialog = latestDialog;
}
public BluetoothAdapter getBluetoothAdapter() {
return bluetoothAdapter;
}
public class Wrapper {
private BroadcastReceiver broadcastReceiver;
private IntentFilter intentFilter;
private Context context;
public Throwable exception;
public Wrapper(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, Context context) {
this.broadcastReceiver = broadcastReceiver;
this.intentFilter = intentFilter;
this.context = context;
exception = new Throwable();
}
}
}