Move a bunch of @Implementation methods from ShadowApplication to ShadowContextWrapper.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
index 1b3dbe8..aed4649 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java
@@ -1,48 +1,26 @@
 package org.robolectric.shadows;
 
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static com.google.common.util.concurrent.Futures.immediateFuture;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 import static org.robolectric.shadow.api.Shadow.newInstanceOf;
 
 import android.app.ActivityThread;
 import android.app.Application;
 import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.view.LayoutInflater;
 import android.widget.ListPopupWindow;
 import android.widget.PopupWindow;
 import android.widget.Toast;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
 import org.robolectric.RoboSettings;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.shadow.api.Shadow;
@@ -53,14 +31,6 @@
 public class ShadowApplication extends ShadowContextWrapper {
   @RealObject private Application realApplication;
 
-  private List<Intent.FilterComparison> startedServices = new ArrayList<>();
-  private List<Intent.FilterComparison> stoppedServices = new ArrayList<>();
-  private List<Intent> broadcastIntents = new ArrayList<>();
-  private List<ServiceConnection> boundServiceConnections = new ArrayList<>();
-  private List<ServiceConnection> unboundServiceConnections = new ArrayList<>();
-  private List<Wrapper> registeredReceivers = new ArrayList<>();
-  private Map<String, Intent> stickyIntents = new LinkedHashMap<>();
-  private Handler mainHandler;
   private Scheduler backgroundScheduler = RoboSettings.isUseGlobalScheduler() ? getForegroundThreadScheduler() : new Scheduler();
   private List<android.widget.Toast> shownToasts = new ArrayList<>();
   private PowerManager.WakeLock latestWakeLock;
@@ -68,18 +38,10 @@
   private ShadowDialog latestDialog;
   private ShadowPopupMenu latestPopupMenu;
   private Object bluetoothAdapter = newInstanceOf("android.bluetooth.BluetoothAdapter");
-  private Set<String> grantedPermissions = new HashSet<>();
-
-  private boolean unbindServiceShouldThrowIllegalArgument = false;
-  private Map<Intent.FilterComparison, ServiceConnectionDataWrapper> serviceConnectionDataForIntent = new HashMap<>();
-  private Map<ServiceConnection, ServiceConnectionDataWrapper> serviceConnectionDataForServiceConnection = new HashMap<>();
-  //default values for bindService
-  private ServiceConnectionDataWrapper defaultServiceConnectionData = new ServiceConnectionDataWrapper(null, null);
 
   // these are managed by the AppSingletonizer... kinda gross, sorry [xw]
   LayoutInflater layoutInflater;
   AppWidgetManager appWidgetManager;
-  private List<String> unbindableActions = new ArrayList<>();
 
 
   private PopupWindow latestPopupWindow;
@@ -101,7 +63,6 @@
   /**
    * Attaches an application to a base context.
    *
-   * @param application The application to attach.
    * @param context The context with which to initialize the application, whose base context will
    *                be attached to the application
    */
@@ -132,416 +93,6 @@
     return backgroundScheduler;
   }
 
-  @Implementation
-  public ComponentName startService(Intent intent) {
-    startedServices.add(new Intent.FilterComparison(intent));
-    if (intent.getComponent() != null) {
-      return intent.getComponent();
-    }
-    return new ComponentName("some.service.package", "SomeServiceName-FIXME");
-  }
-
-  @Implementation
-  public boolean stopService(Intent name) {
-    stoppedServices.add(new Intent.FilterComparison(name));
-    return startedServices.contains(new Intent.FilterComparison(name));
-  }
-
-  public void setComponentNameAndServiceForBindService(ComponentName name, IBinder service) {
-    defaultServiceConnectionData = new ServiceConnectionDataWrapper(name, service);
-  }
-
-  public void setComponentNameAndServiceForBindServiceForIntent(Intent intent, ComponentName name, IBinder service) {
-    serviceConnectionDataForIntent.put(new Intent.FilterComparison(intent),
-        new ServiceConnectionDataWrapper(name, service));
-  }
-
-  @Implementation
-  public boolean bindService(final Intent intent, final ServiceConnection serviceConnection, int i) {
-    boundServiceConnections.add(serviceConnection);
-    unboundServiceConnections.remove(serviceConnection);
-    if (unbindableActions.contains(intent.getAction())) {
-      return false;
-    }
-    startedServices.add(new Intent.FilterComparison(intent));
-    ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
-    shadowLooper.post(() -> {
-      final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
-      final Intent.FilterComparison filterComparison = new Intent.FilterComparison(intent);
-      if (serviceConnectionDataForIntent.containsKey(filterComparison)) {
-        serviceConnectionDataWrapper = serviceConnectionDataForIntent.get(filterComparison);
-      } else {
-        serviceConnectionDataWrapper = defaultServiceConnectionData;
-      }
-      serviceConnectionDataForServiceConnection.put(serviceConnection, serviceConnectionDataWrapper);
-      serviceConnection.onServiceConnected(serviceConnectionDataWrapper.componentNameForBindService, serviceConnectionDataWrapper.binderForBindService);
-    }, 0);
-    return true;
-  }
-
-  public List<ServiceConnection> getBoundServiceConnections() {
-    return boundServiceConnections;
-  }
-
-  public void setUnbindServiceShouldThrowIllegalArgument(boolean flag) {
-    unbindServiceShouldThrowIllegalArgument = flag;
-  }
-
-  @Implementation
-  public void unbindService(final ServiceConnection serviceConnection) {
-    if (unbindServiceShouldThrowIllegalArgument) {
-      throw new IllegalArgumentException();
-    }
-
-    unboundServiceConnections.add(serviceConnection);
-    boundServiceConnections.remove(serviceConnection);
-    ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
-    shadowLooper.post(() -> {
-      final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
-      if (serviceConnectionDataForServiceConnection.containsKey(serviceConnection)) {
-        serviceConnectionDataWrapper = serviceConnectionDataForServiceConnection.get(serviceConnection);
-      } else {
-        serviceConnectionDataWrapper = defaultServiceConnectionData;
-      }
-      serviceConnection.onServiceDisconnected(serviceConnectionDataWrapper.componentNameForBindService);
-    }, 0);
-  }
-
-  public List<ServiceConnection> getUnboundServiceConnections() {
-    return unboundServiceConnections;
-  }
-
-  /**
-   * 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).getIntent();
-    }
-  }
-
-  /**
-   * 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).getIntent();
-    }
-  }
-
-  /**
-   * Clears all {@code Intent} started by {@link #startService(android.content.Intent)}
-   */
-  @Override
-  public void clearStartedServices() {
-    startedServices.clear();
-  }
-
-  /**
-   * Consumes the {@code Intent} requested to stop a service by {@link #stopService(android.content.Intent)}
-   * from the bottom of the stack of stop requests.
-   */
-  @Override
-  public Intent getNextStoppedService() {
-    if (stoppedServices.isEmpty()) {
-      return null;
-    } else {
-      return stoppedServices.remove(0).getIntent();
-    }
-  }
-
-  @Implementation
-  public void sendBroadcast(Intent intent) {
-    sendBroadcastWithPermission(intent, null);
-  }
-
-  @Implementation
-  public void sendBroadcast(Intent intent, String receiverPermission) {
-    sendBroadcastWithPermission(intent, receiverPermission);
-  }
-
-  @Implementation
-  public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
-    sendOrderedBroadcastWithPermission(intent, receiverPermission);
-  }
-
-  @Implementation
-  public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
-                                   Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
-    List<Wrapper> receivers = getAppropriateWrappers(intent, receiverPermission);
-    sortByPriority(receivers);
-    receivers.add(new Wrapper(resultReceiver, null, this.realApplication, null, scheduler));
-    postOrderedToWrappers(receivers, intent, initialCode, initialData, initialExtras);
-  }
-
-  /*
-    Returns the BroadcaseReceivers wrappers, matching intent's action and permissions.
-   */
-  private List<Wrapper> getAppropriateWrappers(Intent intent, String receiverPermission) {
-    broadcastIntents.add(intent);
-
-    List<Wrapper> result = new ArrayList<>();
-
-    List<Wrapper> copy = new ArrayList<>();
-    copy.addAll(registeredReceivers);
-    for (Wrapper wrapper : copy) {
-      if (hasMatchingPermission(wrapper.broadcastPermission, receiverPermission)
-          && wrapper.intentFilter.matchAction(intent.getAction())) {
-        final int match = wrapper.intentFilter.matchData(intent.getType(), intent.getScheme(), intent.getData());
-        if (match != IntentFilter.NO_MATCH_DATA && match != IntentFilter.NO_MATCH_TYPE) {
-          result.add(wrapper);
-        }
-      }
-    }
-    return result;
-  }
-
-  private void postIntent(Intent intent, Wrapper wrapper, final AtomicBoolean abort) {
-    final Handler scheduler = (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler();
-    final BroadcastReceiver receiver = wrapper.broadcastReceiver;
-    final ShadowBroadcastReceiver shReceiver = Shadow.extract(receiver);
-    final Intent broadcastIntent = intent;
-    scheduler.post(new Runnable() {
-      @Override
-      public void run() {
-        receiver.setPendingResult(ShadowBroadcastPendingResult.create(0, null, null, false));
-        shReceiver.onReceive(realApplication, broadcastIntent, abort);
-      }
-    });
-  }
-
-  private void postToWrappers(List<Wrapper> wrappers, Intent intent) {
-    AtomicBoolean abort = new AtomicBoolean(false); // abort state is shared among all broadcast receivers
-    for (Wrapper wrapper: wrappers) {
-      postIntent(intent, wrapper, abort);
-    }
-  }
-
-  private void postOrderedToWrappers(List<Wrapper> wrappers, final Intent intent, int initialCode, String data, Bundle extras) {
-    final AtomicBoolean abort = new AtomicBoolean(false); // abort state is shared among all broadcast receivers
-    ListenableFuture<BroadcastResultHolder> future = immediateFuture(new BroadcastResultHolder(initialCode, data, extras));
-    for (final Wrapper wrapper : wrappers) {
-      future = postIntent(wrapper, intent, future, abort);
-    }
-    final ListenableFuture<?> finalFuture = future;
-    future.addListener(new Runnable() {
-      @Override
-      public void run() {
-        getMainHandler().post(new Runnable() {
-          @Override
-          public void run() {
-            try {
-              finalFuture.get();
-            } catch (InterruptedException | ExecutionException e) {
-              throw new RuntimeException(e);
-            }
-          }
-        });
-      }
-    }, directExecutor());
-  }
-
-  /** Enforces that BroadcastReceivers invoked during an ordered broadcast run serially, passing along their results.*/
-  private ListenableFuture<BroadcastResultHolder> postIntent(final Wrapper wrapper,
-                                                             final Intent intent,
-                                                             ListenableFuture<BroadcastResultHolder> oldResult,
-                                                             final AtomicBoolean abort) {
-    final Handler scheduler = (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler();
-    return Futures.transformAsync(oldResult, new AsyncFunction<BroadcastResultHolder, BroadcastResultHolder>() {
-      @Override
-      public ListenableFuture<BroadcastResultHolder> apply(BroadcastResultHolder broadcastResultHolder) throws Exception {
-        final BroadcastReceiver.PendingResult result = ShadowBroadcastPendingResult.create(
-                broadcastResultHolder.resultCode,
-                broadcastResultHolder.resultData,
-                broadcastResultHolder.resultExtras,
-                true /*ordered */);
-        wrapper.broadcastReceiver.setPendingResult(result);
-        scheduler.post(() -> {
-          ShadowBroadcastReceiver shadowBroadcastReceiver =
-              Shadow.extract(wrapper.broadcastReceiver);
-          shadowBroadcastReceiver.onReceive(realApplication, intent, abort);
-        });
-        return BroadcastResultHolder.transform(result);
-      }
-
-    }, directExecutor());
-  }
-
-  private static final class BroadcastResultHolder {
-    private final int resultCode;
-    private final String resultData;
-    private final Bundle resultExtras;
-
-    private BroadcastResultHolder(int resultCode, String resultData, Bundle resultExtras) {
-      this.resultCode = resultCode;
-      this.resultData = resultData;
-      this.resultExtras = resultExtras;
-    }
-
-    private static ListenableFuture<BroadcastResultHolder> transform(BroadcastReceiver.PendingResult result) {
-      ShadowBroadcastPendingResult shadowBroadcastPendingResult = Shadow.extract(result);
-      return Futures.transform(shadowBroadcastPendingResult.getFuture(),
-          pendingResult -> new BroadcastResultHolder(pendingResult.getResultCode(),
-                  pendingResult.getResultData(),
-                  pendingResult.getResultExtras(false)), directExecutor());
-    }
-  }
-
-  /**
-   * Broadcasts the {@code Intent} by iterating through the registered receivers, invoking their filters including
-   * permissions, and calling {@code onReceive(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
-   */
-  private void sendBroadcastWithPermission(Intent intent, String receiverPermission) {
-    List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
-    postToWrappers(wrappers, intent);
-  }
-
-  private void sendOrderedBroadcastWithPermission(Intent intent, String receiverPermission) {
-    List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
-    // sort by the decrease of priorities
-    sortByPriority(wrappers);
-
-    postOrderedToWrappers(wrappers, intent, 0, null, null);
-  }
-
-  private void sortByPriority(List<Wrapper> wrappers) {
-    Collections.sort(wrappers, new Comparator<Wrapper>() {
-      @Override
-      public int compare(Wrapper o1, Wrapper o2) {
-        return Integer.compare(o2.getIntentFilter().getPriority(), o1.getIntentFilter().getPriority());
-      }
-    });
-  }
-
-  @Override public List<Intent> getBroadcastIntents() {
-    return broadcastIntents;
-  }
-
-  @Implementation
-  public void sendStickyBroadcast(Intent intent) {
-    stickyIntents.put(intent.getAction(), intent);
-    sendBroadcast(intent);
-  }
-
-  /**
-   * Always returns {@code null}
-   *
-   * @return {@code null}
-   */
-  @Implementation
-  public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-    return registerReceiverWithContext(receiver, filter, null, null, realApplication);
-  }
-
-  @Implementation
-  public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) {
-    return registerReceiverWithContext(receiver, filter, broadcastPermission, scheduler, realApplication);
-  }
-
-  Intent registerReceiverWithContext(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) {
-    if (receiver != null) {
-      registeredReceivers.add(new Wrapper(receiver, filter, context, broadcastPermission, scheduler));
-    }
-    return processStickyIntents(filter, receiver, context);
-  }
-
-  private Intent processStickyIntents(IntentFilter filter, BroadcastReceiver receiver, Context context) {
-    Intent result = null;
-    for (Intent stickyIntent : stickyIntents.values()) {
-      if (filter.matchAction(stickyIntent.getAction())) {
-        if (result == null) {
-          result = stickyIntent;
-        }
-        if (receiver != null) {
-          receiver.setPendingResult(ShadowBroadcastPendingResult.createSticky(stickyIntent));
-          receiver.onReceive(context, stickyIntent);
-          receiver.setPendingResult(null);
-        } else if (result != null) {
-          break;
-        }
-      }
-    }
-    return result;
-  }
-
-  @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);
-    }
-  }
-
-  public void assertNoBroadcastListenersOfActionRegistered(ContextWrapper context, String action) {
-    for (Wrapper registeredReceiver : registeredReceivers) {
-      if (registeredReceiver.context == context.getBaseContext()) {
-        Iterator<String> actions = registeredReceiver.intentFilter.actionsIterator();
-        while (actions.hasNext()) {
-          if (actions.next().equals(action)) {
-            RuntimeException e = new IllegalStateException("Unexpected BroadcastReceiver on " + context +
-                " with action " + action + " "
-                + registeredReceiver.broadcastReceiver + " that was originally registered here:");
-            e.setStackTrace(registeredReceiver.exception.getStackTrace());
-            throw e;
-          }
-        }
-      }
-    }
-  }
-
-  /** @deprecated use PackageManager.queryBroadcastReceivers instead */
-  @Deprecated
-  public boolean hasReceiverForIntent(Intent intent) {
-    for (Wrapper wrapper : registeredReceivers) {
-      if (wrapper.intentFilter.matchAction(intent.getAction())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /** @deprecated use PackageManager.queryBroadcastReceivers instead */
-  @Deprecated
-  public List<BroadcastReceiver> getReceiversForIntent(Intent intent) {
-    ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
-    for (Wrapper wrapper : registeredReceivers) {
-      if (wrapper.intentFilter.matchAction(intent.getAction())) {
-        broadcastReceivers.add(wrapper.getBroadcastReceiver());
-      }
-    }
-    return broadcastReceivers;
-  }
-
-  /**
-   * @return list of {@link Wrapper}s for registered receivers
-   */
-  public List<Wrapper> getRegisteredReceivers() {
-    return registeredReceivers;
-  }
-
   /**
    * @return the layout inflater used by this {@code Application}
    */
@@ -576,10 +127,6 @@
     return bluetoothAdapter;
   }
 
-  public void declareActionUnbindable(String action) {
-    unbindableActions.add(action);
-  }
-
   public PowerManager.WakeLock getLatestWakeLock() {
     return latestWakeLock;
   }
@@ -675,42 +222,6 @@
     }
   }
 
-  @Implementation
-  public int checkPermission(String permission, int pid, int uid) {
-    return grantedPermissions.contains(permission) ? PERMISSION_GRANTED : PERMISSION_DENIED;
-  }
-
-  @Override public void grantPermissions(String... permissionNames) {
-    Collections.addAll(grantedPermissions, permissionNames);
-  }
-
-  @Override public void denyPermissions(String... permissionNames) {
-    for (String permissionName : permissionNames) {
-      grantedPermissions.remove(permissionName);
-    }
-  }
-
-  private boolean hasMatchingPermission(String permission1, String permission2) {
-    return permission1 == null ? permission2 == null : permission1.equals(permission2);
-  }
-
-  private static class ServiceConnectionDataWrapper {
-    public final ComponentName componentNameForBindService;
-    public final IBinder binderForBindService;
-
-    private ServiceConnectionDataWrapper(ComponentName componentNameForBindService, IBinder binderForBindService) {
-      this.componentNameForBindService = componentNameForBindService;
-      this.binderForBindService = binderForBindService;
-    }
-  }
-
-  private Handler getMainHandler() {
-    if (mainHandler == null) {
-      mainHandler = new Handler(realApplication.getMainLooper());
-    }
-    return mainHandler;
-  }
-
   /**
    * @deprecated Do not depend on this method to override services as it will be removed in a future update.
    * The preferered method is use the shadow of the corresponding service.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
index 28e8883..e0e7621 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
@@ -1,18 +1,19 @@
 package org.robolectric.shadows;
 
-import static android.os.Build.VERSION_CODES.*;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
 import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
 import android.content.res.XmlResourceParser;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.os.ParcelFileDescriptor;
 import android.util.SparseArray;
 import android.util.TypedValue;
@@ -422,7 +423,7 @@
   @HiddenApi @Implementation(maxSdk = O_MR1)
   abstract protected Number getNativeStringBlock(int block);
 
-  @Implementation(maxSdk = O_MR1)
+  @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
   abstract public SparseArray<String> getAssignedPackageIdentifiers();
 
   @HiddenApi @Implementation(maxSdk = O_MR1)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextWrapper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextWrapper.java
index 99bcb17..d585080 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextWrapper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextWrapper.java
@@ -1,18 +1,232 @@
 package org.robolectric.shadows;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
 import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
 import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplication.Wrapper;
 
 @Implements(ContextWrapper.class)
 public class ShadowContextWrapper {
 
+  @RealObject
+  private ContextWrapper realContextWrapper;
+  private List<Intent.FilterComparison> startedServices = new ArrayList<>();
+  private List<Intent.FilterComparison> stoppedServices = new ArrayList<>();
+  private List<Intent> broadcastIntents = new ArrayList<>();
+  private List<ServiceConnection> boundServiceConnections = new ArrayList<>();
+  private List<ServiceConnection> unboundServiceConnections = new ArrayList<>();
+  private List<Wrapper> registeredReceivers = new ArrayList<>();
+  private Set<String> grantedPermissions = new HashSet<>();
+  private boolean unbindServiceShouldThrowIllegalArgument = false;
+  private Map<Intent.FilterComparison, ServiceConnectionDataWrapper> serviceConnectionDataForIntent = new HashMap<>();
+  //default values for bindService
+  private ServiceConnectionDataWrapper defaultServiceConnectionData = new ServiceConnectionDataWrapper(null, null);
+  private List<String> unbindableActions = new ArrayList<>();
+  private Map<String, Intent> stickyIntents = new LinkedHashMap<>();
+  private Handler mainHandler;
+  private Map<ServiceConnection, ServiceConnectionDataWrapper> serviceConnectionDataForServiceConnection = new HashMap<>();
+
+  @Implementation
+  public void sendBroadcast(Intent intent) {
+    sendBroadcastWithPermission(intent, null);
+  }
+
+  @Implementation
+  public void sendBroadcast(Intent intent, String receiverPermission) {
+    sendBroadcastWithPermission(intent, receiverPermission);
+  }
+
+  @Implementation
+  public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+    sendOrderedBroadcastWithPermission(intent, receiverPermission);
+  }
+
+  @Implementation
+  public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
+                                   Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+    List<Wrapper> receivers = getAppropriateWrappers(intent, receiverPermission);
+    sortByPriority(receivers);
+    receivers.add(new Wrapper(resultReceiver, null, this.realContextWrapper, null, scheduler));
+    postOrderedToWrappers(receivers, intent, initialCode, initialData, initialExtras);
+  }
+
+  public void assertNoBroadcastListenersOfActionRegistered(ContextWrapper context, String action) {
+    for (Wrapper registeredReceiver : registeredReceivers) {
+      if (registeredReceiver.context == context.getBaseContext()) {
+        Iterator<String> actions = registeredReceiver.intentFilter.actionsIterator();
+        while (actions.hasNext()) {
+          if (actions.next().equals(action)) {
+            RuntimeException e = new IllegalStateException("Unexpected BroadcastReceiver on " + context +
+                " with action " + action + " "
+                + registeredReceiver.broadcastReceiver + " that was originally registered here:");
+            e.setStackTrace(registeredReceiver.exception.getStackTrace());
+            throw e;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the BroadcaseReceivers wrappers, matching intent's action and permissions.
+   */
+  private List<Wrapper> getAppropriateWrappers(Intent intent, String receiverPermission) {
+    broadcastIntents.add(intent);
+
+    List<Wrapper> result = new ArrayList<>();
+
+    List<Wrapper> copy = new ArrayList<>();
+    copy.addAll(registeredReceivers);
+    for (Wrapper wrapper : copy) {
+      if (hasMatchingPermission(wrapper.broadcastPermission, receiverPermission)
+          && wrapper.intentFilter.matchAction(intent.getAction())) {
+        final int match = wrapper.intentFilter.matchData(intent.getType(), intent.getScheme(), intent.getData());
+        if (match != IntentFilter.NO_MATCH_DATA && match != IntentFilter.NO_MATCH_TYPE) {
+          result.add(wrapper);
+        }
+      }
+    }
+    return result;
+  }
+
+  private void postIntent(Intent intent, Wrapper wrapper, final AtomicBoolean abort) {
+    final Handler scheduler = (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler();
+    final BroadcastReceiver receiver = wrapper.broadcastReceiver;
+    final ShadowBroadcastReceiver shReceiver = Shadow.extract(receiver);
+    final Intent broadcastIntent = intent;
+    scheduler.post(new Runnable() {
+      @Override
+      public void run() {
+        receiver.setPendingResult(ShadowBroadcastPendingResult.create(0, null, null, false));
+        shReceiver.onReceive(realContextWrapper, broadcastIntent, abort);
+      }
+    });
+  }
+
+  private void postToWrappers(List<Wrapper> wrappers, Intent intent) {
+    AtomicBoolean abort = new AtomicBoolean(false); // abort state is shared among all broadcast receivers
+    for (Wrapper wrapper: wrappers) {
+      postIntent(intent, wrapper, abort);
+    }
+  }
+
+  private void postOrderedToWrappers(List<Wrapper> wrappers, final Intent intent, int initialCode, String data, Bundle extras) {
+    final AtomicBoolean abort = new AtomicBoolean(false); // abort state is shared among all broadcast receivers
+    ListenableFuture<BroadcastResultHolder> future = immediateFuture(new BroadcastResultHolder(initialCode, data, extras));
+    for (final Wrapper wrapper : wrappers) {
+      future = postIntent(wrapper, intent, future, abort);
+    }
+    final ListenableFuture<?> finalFuture = future;
+    future.addListener(new Runnable() {
+      @Override
+      public void run() {
+        getMainHandler().post(new Runnable() {
+          @Override
+          public void run() {
+            try {
+              finalFuture.get();
+            } catch (InterruptedException | ExecutionException e) {
+              throw new RuntimeException(e);
+            }
+          }
+        });
+      }
+    }, directExecutor());
+  }
+
+  /** Enforces that BroadcastReceivers invoked during an ordered broadcast run serially, passing along their results.*/
+  private ListenableFuture<BroadcastResultHolder> postIntent(final Wrapper wrapper,
+                                                             final Intent intent,
+                                                             ListenableFuture<BroadcastResultHolder> oldResult,
+                                                             final AtomicBoolean abort) {
+    final Handler scheduler = (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler();
+    return Futures
+        .transformAsync(oldResult, new AsyncFunction<BroadcastResultHolder, BroadcastResultHolder>() {
+      @Override
+      public ListenableFuture<BroadcastResultHolder> apply(BroadcastResultHolder broadcastResultHolder) throws Exception {
+        final BroadcastReceiver.PendingResult result = ShadowBroadcastPendingResult.create(
+                broadcastResultHolder.resultCode,
+                broadcastResultHolder.resultData,
+                broadcastResultHolder.resultExtras,
+                true /*ordered */);
+        wrapper.broadcastReceiver.setPendingResult(result);
+        scheduler.post(() -> {
+          ShadowBroadcastReceiver shadowBroadcastReceiver =
+              Shadow.extract(wrapper.broadcastReceiver);
+          shadowBroadcastReceiver.onReceive(realContextWrapper, intent, abort);
+        });
+        return BroadcastResultHolder.transform(result);
+      }
+
+    }, directExecutor());
+  }
+
+  /**
+   * Broadcasts the {@code Intent} by iterating through the registered receivers, invoking their filters including
+   * permissions, and calling {@code onReceive(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
+   */
+  private void sendBroadcastWithPermission(Intent intent, String receiverPermission) {
+    List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
+    postToWrappers(wrappers, intent);
+  }
+
+  private void sendOrderedBroadcastWithPermission(Intent intent, String receiverPermission) {
+    List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
+    // sort by the decrease of priorities
+    sortByPriority(wrappers);
+
+    postOrderedToWrappers(wrappers, intent, 0, null, null);
+  }
+
+  private void sortByPriority(List<Wrapper> wrappers) {
+    Collections.sort(wrappers, new Comparator<Wrapper>() {
+      @Override
+      public int compare(Wrapper o1, Wrapper o2) {
+        return Integer.compare(o2.getIntentFilter().getPriority(), o1.getIntentFilter().getPriority());
+      }
+    });
+  }
+
   public List<Intent> getBroadcastIntents() {
-    return ShadowApplication.getInstance().getBroadcastIntents();
+    return broadcastIntents;
   }
 
   /**
@@ -39,47 +253,283 @@
     return shadowInstrumentation.peekNextStartedActivity();
   }
 
+  @Implementation
+  public ComponentName startService(Intent intent) {
+    startedServices.add(new Intent.FilterComparison(intent));
+    if (intent.getComponent() != null) {
+      return intent.getComponent();
+    }
+    return new ComponentName("some.service.package", "SomeServiceName-FIXME");
+  }
+
+  @Implementation
+  public boolean stopService(Intent name) {
+    stoppedServices.add(new Intent.FilterComparison(name));
+    return startedServices.contains(new Intent.FilterComparison(name));
+  }
+
+  public void setComponentNameAndServiceForBindService(ComponentName name, IBinder service) {
+    defaultServiceConnectionData = new ServiceConnectionDataWrapper(name, service);
+  }
+
+  public void setComponentNameAndServiceForBindServiceForIntent(Intent intent, ComponentName name, IBinder service) {
+    serviceConnectionDataForIntent.put(new Intent.FilterComparison(intent),
+        new ServiceConnectionDataWrapper(name, service));
+  }
+
+  @Implementation
+  public boolean bindService(final Intent intent, final ServiceConnection serviceConnection, int i) {
+    boundServiceConnections.add(serviceConnection);
+    unboundServiceConnections.remove(serviceConnection);
+    if (unbindableActions.contains(intent.getAction())) {
+      return false;
+    }
+    startedServices.add(new Intent.FilterComparison(intent));
+    ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
+    shadowLooper.post(() -> {
+      final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
+      final Intent.FilterComparison filterComparison = new Intent.FilterComparison(intent);
+      if (serviceConnectionDataForIntent.containsKey(filterComparison)) {
+        serviceConnectionDataWrapper = serviceConnectionDataForIntent.get(filterComparison);
+      } else {
+        serviceConnectionDataWrapper = defaultServiceConnectionData;
+      }
+      serviceConnectionDataForServiceConnection.put(serviceConnection, serviceConnectionDataWrapper);
+      serviceConnection.onServiceConnected(serviceConnectionDataWrapper.componentNameForBindService, serviceConnectionDataWrapper.binderForBindService);
+    }, 0);
+    return true;
+  }
+
+  @Implementation
+  public void unbindService(final ServiceConnection serviceConnection) {
+    if (unbindServiceShouldThrowIllegalArgument) {
+      throw new IllegalArgumentException();
+    }
+
+    unboundServiceConnections.add(serviceConnection);
+    boundServiceConnections.remove(serviceConnection);
+    ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
+    shadowLooper.post(() -> {
+      final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
+      if (serviceConnectionDataForServiceConnection.containsKey(serviceConnection)) {
+        serviceConnectionDataWrapper = serviceConnectionDataForServiceConnection.get(serviceConnection);
+      } else {
+        serviceConnectionDataWrapper = defaultServiceConnectionData;
+      }
+      serviceConnection.onServiceDisconnected(serviceConnectionDataWrapper.componentNameForBindService);
+    }, 0);
+  }
+
+  public List<ServiceConnection> getBoundServiceConnections() {
+    return boundServiceConnections;
+  }
+
+  public void setUnbindServiceShouldThrowIllegalArgument(boolean flag) {
+    unbindServiceShouldThrowIllegalArgument = flag;
+  }
+
+  public List<ServiceConnection> getUnboundServiceConnections() {
+    return unboundServiceConnections;
+  }
+
+  public void declareActionUnbindable(String action) {
+    unbindableActions.add(action);
+  }
+
   /**
-   * Delegates to the application to consume and return the next {@code Intent} on the
-   * started services stack.
+   * Consumes the most recent {@code Intent} started by
+   * {@link #startService(android.content.Intent)} and returns it.
    *
-   * @return the next started {@code Intent} for a service
+   * @return the most recently started {@code Intent}
    */
   public Intent getNextStartedService() {
-    return ShadowApplication.getInstance().getNextStartedService();
+    if (startedServices.isEmpty()) {
+      return null;
+    } else {
+      return startedServices.remove(0).getIntent();
+    }
   }
 
   /**
-   * Delegates to the application to clear the stack of started service intents.
-   */
-  public void clearStartedServices() {
-    ShadowApplication.getInstance().clearStartedServices();
-  }
-
-  /**
-   * Return (without consuming) the next {@code Intent} on the started services stack.
+   * Returns the most recent {@code Intent} started by {@link #startService(android.content.Intent)}
+   * without consuming it.
    *
-   * @return the next started {@code Intent} for a service
+   * @return the most recently started {@code Intent}
    */
   public Intent peekNextStartedService() {
-    return ShadowApplication.getInstance().peekNextStartedService();
+    if (startedServices.isEmpty()) {
+      return null;
+    } else {
+      return startedServices.get(0).getIntent();
+    }
   }
 
   /**
-   * Delegates to the application to return the next {@code Intent} to stop
-   * a service (irrespective of if the service was running)
-   *
-   * @return {@code Intent} for the next service requested to be stopped
+   * Clears all {@code Intent} started by {@link #startService(android.content.Intent)}.
+   */
+  public void clearStartedServices() {
+    startedServices.clear();
+  }
+
+  /**
+   * Consumes the {@code Intent} requested to stop a service by {@link #stopService(android.content.Intent)}
+   * from the bottom of the stack of stop requests.
    */
   public Intent getNextStoppedService() {
-    return ShadowApplication.getInstance().getNextStoppedService();
+    if (stoppedServices.isEmpty()) {
+      return null;
+    } else {
+      return stoppedServices.remove(0).getIntent();
+    }
+  }
+
+  @Implementation
+  public void sendStickyBroadcast(Intent intent) {
+    stickyIntents.put(intent.getAction(), intent);
+    sendBroadcast(intent);
+  }
+
+  /**
+   * Always returns {@code null}
+   *
+   * @return {@code null}
+   */
+  @Implementation
+  public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+    return registerReceiverWithContext(receiver, filter, null, null, realContextWrapper);
+  }
+
+  @Implementation
+  public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) {
+    return registerReceiverWithContext(receiver, filter, broadcastPermission, scheduler,
+        realContextWrapper);
+  }
+
+  Intent registerReceiverWithContext(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) {
+    if (receiver != null) {
+      registeredReceivers.add(new Wrapper(receiver, filter, context, broadcastPermission, scheduler));
+    }
+    return processStickyIntents(filter, receiver, context);
+  }
+
+  private Intent processStickyIntents(IntentFilter filter, BroadcastReceiver receiver, Context context) {
+    Intent result = null;
+    for (Intent stickyIntent : stickyIntents.values()) {
+      if (filter.matchAction(stickyIntent.getAction())) {
+        if (result == null) {
+          result = stickyIntent;
+        }
+        if (receiver != null) {
+          receiver.setPendingResult(ShadowBroadcastPendingResult.createSticky(stickyIntent));
+          receiver.onReceive(context, stickyIntent);
+          receiver.setPendingResult(null);
+        } else if (result != null) {
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  @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);
+    }
+  }
+
+  /** @deprecated use PackageManager.queryBroadcastReceivers instead */
+  @Deprecated
+  public boolean hasReceiverForIntent(Intent intent) {
+    for (Wrapper wrapper : registeredReceivers) {
+      if (wrapper.intentFilter.matchAction(intent.getAction())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** @deprecated use PackageManager.queryBroadcastReceivers instead */
+  @Deprecated
+  public List<BroadcastReceiver> getReceiversForIntent(Intent intent) {
+    ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
+    for (Wrapper wrapper : registeredReceivers) {
+      if (wrapper.intentFilter.matchAction(intent.getAction())) {
+        broadcastReceivers.add(wrapper.getBroadcastReceiver());
+      }
+    }
+    return broadcastReceivers;
+  }
+
+  /**
+   * @return list of {@link Wrapper}s for registered receivers
+   */
+  public List<Wrapper> getRegisteredReceivers() {
+    return registeredReceivers;
+  }
+
+  @Implementation
+  public int checkPermission(String permission, int pid, int uid) {
+    return grantedPermissions.contains(permission) ? PERMISSION_GRANTED : PERMISSION_DENIED;
   }
 
   public void grantPermissions(String... permissionNames) {
-    ShadowApplication.getInstance().grantPermissions(permissionNames);
+    Collections.addAll(grantedPermissions, permissionNames);
   }
 
   public void denyPermissions(String... permissionNames) {
-    ShadowApplication.getInstance().denyPermissions(permissionNames);
+    for (String permissionName : permissionNames) {
+      grantedPermissions.remove(permissionName);
+    }
+  }
+
+  private boolean hasMatchingPermission(String permission1, String permission2) {
+    return permission1 == null ? permission2 == null : permission1.equals(permission2);
+  }
+
+  private Handler getMainHandler() {
+    if (mainHandler == null) {
+      mainHandler = new Handler(realContextWrapper.getMainLooper());
+    }
+    return mainHandler;
+  }
+
+  private static final class BroadcastResultHolder {
+    private final int resultCode;
+    private final String resultData;
+    private final Bundle resultExtras;
+
+    private BroadcastResultHolder(int resultCode, String resultData, Bundle resultExtras) {
+      this.resultCode = resultCode;
+      this.resultData = resultData;
+      this.resultExtras = resultExtras;
+    }
+
+    private static ListenableFuture<BroadcastResultHolder> transform(BroadcastReceiver.PendingResult result) {
+      ShadowBroadcastPendingResult shadowBroadcastPendingResult = Shadow.extract(result);
+      return Futures.transform(shadowBroadcastPendingResult.getFuture(),
+          pendingResult -> new BroadcastResultHolder(pendingResult.getResultCode(),
+              pendingResult.getResultData(),
+              pendingResult.getResultExtras(false)), directExecutor());
+    }
+  }
+
+  private static class ServiceConnectionDataWrapper {
+    public final ComponentName componentNameForBindService;
+    public final IBinder binderForBindService;
+
+    private ServiceConnectionDataWrapper(ComponentName componentNameForBindService, IBinder binderForBindService) {
+      this.componentNameForBindService = componentNameForBindService;
+      this.binderForBindService = binderForBindService;
+    }
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTabWidget.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTabWidget.java
index 9afb079..556463d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTabWidget.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTabWidget.java
@@ -1,5 +1,7 @@
 package org.robolectric.shadows;
 
+import static android.os.Build.VERSION_CODES.M;
+
 import android.widget.TabWidget;
 import org.robolectric.annotation.HiddenApi;
 import org.robolectric.annotation.Implementation;
@@ -8,7 +10,7 @@
 @Implements(TabWidget.class)
 public class ShadowTabWidget extends ShadowLinearLayout {
 
-  @HiddenApi @Implementation
+  @HiddenApi @Implementation(maxSdk = M)
   public void initTabWidget() {
   }
 }