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
SYSTEM_SERVICE_MAP.put(Context.WINDOW_SERVICE, "com.xtremelabs.robolectric.tester.android.view.TestWindowManager")45         SYSTEM_SERVICE_MAP.put(Context.WINDOW_SERVICE, "com.xtremelabs.robolectric.tester.android.view.TestWindowManager");
SYSTEM_SERVICE_MAP.put(Context.CLIPBOARD_SERVICE, "com.xtremelabs.robolectric.tester.android.text.TestClipboardManager")46         SYSTEM_SERVICE_MAP.put(Context.CLIPBOARD_SERVICE, "com.xtremelabs.robolectric.tester.android.text.TestClipboardManager");
SYSTEM_SERVICE_MAP.put(Context.SENSOR_SERVICE, "android.hardware.TestSensorManager")47         SYSTEM_SERVICE_MAP.put(Context.SENSOR_SERVICE, "android.hardware.TestSensorManager");
SYSTEM_SERVICE_MAP.put(Context.VIBRATOR_SERVICE, "android.os.TestVibrator")48         SYSTEM_SERVICE_MAP.put(Context.VIBRATOR_SERVICE, "android.os.TestVibrator");
49 
50         // the rest are as mapped in docs...
SYSTEM_SERVICE_MAP.put(Context.LAYOUT_INFLATER_SERVICE, "android.view.LayoutInflater")51         SYSTEM_SERVICE_MAP.put(Context.LAYOUT_INFLATER_SERVICE, "android.view.LayoutInflater");
SYSTEM_SERVICE_MAP.put(Context.ACTIVITY_SERVICE, "android.app.ActivityManager")52         SYSTEM_SERVICE_MAP.put(Context.ACTIVITY_SERVICE, "android.app.ActivityManager");
SYSTEM_SERVICE_MAP.put(Context.POWER_SERVICE, "android.os.PowerManager")53         SYSTEM_SERVICE_MAP.put(Context.POWER_SERVICE, "android.os.PowerManager");
SYSTEM_SERVICE_MAP.put(Context.ALARM_SERVICE, "android.app.AlarmManager")54         SYSTEM_SERVICE_MAP.put(Context.ALARM_SERVICE, "android.app.AlarmManager");
SYSTEM_SERVICE_MAP.put(Context.NOTIFICATION_SERVICE, "android.app.NotificationManager")55         SYSTEM_SERVICE_MAP.put(Context.NOTIFICATION_SERVICE, "android.app.NotificationManager");
SYSTEM_SERVICE_MAP.put(Context.KEYGUARD_SERVICE, "android.app.KeyguardManager")56         SYSTEM_SERVICE_MAP.put(Context.KEYGUARD_SERVICE, "android.app.KeyguardManager");
SYSTEM_SERVICE_MAP.put(Context.LOCATION_SERVICE, "android.location.LocationManager")57         SYSTEM_SERVICE_MAP.put(Context.LOCATION_SERVICE, "android.location.LocationManager");
SYSTEM_SERVICE_MAP.put(Context.SEARCH_SERVICE, "android.app.SearchManager")58         SYSTEM_SERVICE_MAP.put(Context.SEARCH_SERVICE, "android.app.SearchManager");
SYSTEM_SERVICE_MAP.put(Context.STORAGE_SERVICE, "android.os.storage.StorageManager")59         SYSTEM_SERVICE_MAP.put(Context.STORAGE_SERVICE, "android.os.storage.StorageManager");
SYSTEM_SERVICE_MAP.put(Context.CONNECTIVITY_SERVICE, "android.net.ConnectivityManager")60         SYSTEM_SERVICE_MAP.put(Context.CONNECTIVITY_SERVICE, "android.net.ConnectivityManager");
SYSTEM_SERVICE_MAP.put(Context.WIFI_SERVICE, "android.net.wifi.WifiManager")61         SYSTEM_SERVICE_MAP.put(Context.WIFI_SERVICE, "android.net.wifi.WifiManager");
SYSTEM_SERVICE_MAP.put(Context.AUDIO_SERVICE, "android.media.AudioManager")62         SYSTEM_SERVICE_MAP.put(Context.AUDIO_SERVICE, "android.media.AudioManager");
SYSTEM_SERVICE_MAP.put(Context.TELEPHONY_SERVICE, "android.telephony.TelephonyManager")63         SYSTEM_SERVICE_MAP.put(Context.TELEPHONY_SERVICE, "android.telephony.TelephonyManager");
SYSTEM_SERVICE_MAP.put(Context.INPUT_METHOD_SERVICE, "android.view.inputmethod.InputMethodManager")64         SYSTEM_SERVICE_MAP.put(Context.INPUT_METHOD_SERVICE, "android.view.inputmethod.InputMethodManager");
SYSTEM_SERVICE_MAP.put(Context.UI_MODE_SERVICE, "android.app.UiModeManager")65         SYSTEM_SERVICE_MAP.put(Context.UI_MODE_SERVICE, "android.app.UiModeManager");
SYSTEM_SERVICE_MAP.put(Context.DOWNLOAD_SERVICE, "android.app.DownloadManager")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      */
bind(Application application, ResourceLoader resourceLoader)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 
getShownToasts()115     public List<Toast> getShownToasts() {
116         return shownToasts;
117     }
118 
getBackgroundScheduler()119     public Scheduler getBackgroundScheduler() {
120         return backgroundScheduler;
121     }
122 
123     @Override
124     @Implementation
getApplicationContext()125     public Context getApplicationContext() {
126         return realApplication;
127     }
128 
129     @Override
130     @Implementation
getResources()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
getContentResolver()140     public ContentResolver getContentResolver() {
141         if (contentResolver == null) {
142             contentResolver = new ContentResolver(realApplication) {
143             };
144         }
145         return contentResolver;
146     }
147 
148     @Implementation
149     @Override
getSystemService(String name)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
startActivity(Intent intent)172     public void startActivity(Intent intent) {
173         startedActivities.add(intent);
174     }
175 
176     @Implementation
177     @Override
startService(Intent intent)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
stopService(Intent name)185     public boolean stopService(Intent name) {
186         stoppedServies.add(name);
187 
188         return startedServices.contains(name);
189     }
190 
setComponentNameAndServiceForBindService(ComponentName name, IBinder service)191     public void setComponentNameAndServiceForBindService(ComponentName name, IBinder service) {
192         this.componentNameForBindService = name;
193         this.serviceForBindService = service;
194     }
195 
196     @Implementation
bindService(Intent intent, final ServiceConnection serviceConnection, int i)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
unbindService(final ServiceConnection serviceConnection)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 
getUnboundServiceConnections()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
getNextStartedActivity()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
peekNextStartedActivity()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
getNextStartedService()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
peekNextStartedService()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
clearStartedServices()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
getNextStoppedService()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      */
getResourceLoader()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
sendBroadcast(Intent intent)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 
getBroadcastIntents()334     public List<Intent> getBroadcastIntents() {
335         return broadcastIntents;
336     }
337 
338     @Implementation
sendStickyBroadcast(Intent intent)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
registerReceiver(BroadcastReceiver receiver, IntentFilter filter)351     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
352         return registerReceiverWithContext(receiver, filter, realApplication);
353     }
354 
registerReceiverWithContext(BroadcastReceiver receiver, IntentFilter filter, Context context)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 
getStickyIntent(IntentFilter filter)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
unregisterReceiver(BroadcastReceiver broadcastReceiver)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      */
assertNoBroadcastListenersRegistered(Context context, String type)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 
assertNoBroadcastListenersOfActionRegistered(Context context, String action)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 
hasReceiverForIntent(Intent intent)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 
getReceiversForIntent(Intent intent)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      */
getRegisteredReceivers()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      */
getLayoutInflater()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      */
getAppWidgetManager()472     public AppWidgetManager getAppWidgetManager() {
473         return appWidgetManager;
474     }
475 
getFakeHttpLayer()476     public FakeHttpLayer getFakeHttpLayer() {
477         return fakeHttpLayer;
478     }
479 
480     @Override
481     @Implementation
getMainLooper()482     public Looper getMainLooper() {
483         return mainLooper;
484     }
485 
getSharedPreferenceMap()486     public Map<String, Map<String, Object>> getSharedPreferenceMap() {
487         return sharedPreferenceMap;
488     }
489 
getLatestAlertDialog()490     public ShadowAlertDialog getLatestAlertDialog() {
491         return latestAlertDialog;
492     }
493 
setLatestAlertDialog(ShadowAlertDialog latestAlertDialog)494     public void setLatestAlertDialog(ShadowAlertDialog latestAlertDialog) {
495         this.latestAlertDialog = latestAlertDialog;
496     }
497 
getLatestDialog()498     public ShadowDialog getLatestDialog() {
499         return latestDialog;
500     }
501 
setLatestDialog(ShadowDialog latestDialog)502     public void setLatestDialog(ShadowDialog latestDialog) {
503         this.latestDialog = latestDialog;
504     }
505 
getBluetoothAdapter()506     public Object getBluetoothAdapter() {
507         return bluetoothAdapter;
508     }
509 
declareActionUnbindable(String action)510     public void declareActionUnbindable(String action) {
511         unbindableActions.add(action);
512     }
513 
setSystemService(String key, Object service)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 
Wrapper(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, Context context)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 
getBroadcastReceiver()531         public BroadcastReceiver getBroadcastReceiver() {
532             return broadcastReceiver;
533         }
534 
getIntentFilter()535         public IntentFilter getIntentFilter() {
536             return intentFilter;
537         }
538 
getContext()539         public Context getContext() {
540             return context;
541         }
542     }
543 }
544