Home | History | Annotate | Download | only in shadows
      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