1 package org.robolectric.shadows;
2 
3 import static android.content.pm.PackageManager.PERMISSION_DENIED;
4 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
5 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
6 import static android.os.Build.VERSION_CODES.LOLLIPOP;
7 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
8 import static android.os.Build.VERSION_CODES.M;
9 import static android.os.Build.VERSION_CODES.P;
10 import static com.google.common.util.concurrent.Futures.immediateFuture;
11 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
12 import static org.robolectric.shadow.api.Shadow.directlyOn;
13 
14 import android.app.Activity;
15 import android.app.ActivityThread;
16 import android.app.Fragment;
17 import android.app.Instrumentation;
18 import android.app.Instrumentation.ActivityResult;
19 import android.content.ActivityNotFoundException;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.ContextWrapper;
24 import android.content.Intent;
25 import android.content.Intent.FilterComparison;
26 import android.content.IntentFilter;
27 import android.content.ServiceConnection;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Process;
33 import android.os.UserHandle;
34 import android.util.Pair;
35 import com.google.common.util.concurrent.AsyncFunction;
36 import com.google.common.util.concurrent.Futures;
37 import com.google.common.util.concurrent.ListenableFuture;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.LinkedHashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.concurrent.ExecutionException;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 import org.robolectric.RuntimeEnvironment;
51 import org.robolectric.annotation.Implementation;
52 import org.robolectric.annotation.Implements;
53 import org.robolectric.annotation.RealObject;
54 import org.robolectric.shadow.api.Shadow;
55 import org.robolectric.shadows.ShadowActivity.IntentForResult;
56 import org.robolectric.shadows.ShadowApplication.Wrapper;
57 
58 @Implements(value = Instrumentation.class, looseSignatures = true)
59 public class ShadowInstrumentation {
60 
61   @RealObject private Instrumentation realObject;
62 
63   private List<Intent> startedActivities = new ArrayList<>();
64   private List<IntentForResult> startedActivitiesForResults = new ArrayList<>();
65   private Map<FilterComparison, Integer> intentRequestCodeMap = new HashMap<>();
66   private List<Intent.FilterComparison> startedServices = new ArrayList<>();
67   private List<Intent.FilterComparison> stoppedServices = new ArrayList<>();
68   private List<Intent> broadcastIntents = new ArrayList<>();
69   private List<ServiceConnection> boundServiceConnections = new ArrayList<>();
70   private List<ServiceConnection> unboundServiceConnections = new ArrayList<>();
71   private List<Wrapper> registeredReceivers = new ArrayList<>();
72   // map of pid+uid to granted permissions
73   private final Map<Pair<Integer, Integer>, Set<String>> grantedPermissionsMap = new HashMap<>();
74   private boolean unbindServiceShouldThrowIllegalArgument = false;
75   private Map<Intent.FilterComparison, ServiceConnectionDataWrapper>
76       serviceConnectionDataForIntent = new HashMap<>();
77   // default values for bindService
78   private ServiceConnectionDataWrapper defaultServiceConnectionData =
79       new ServiceConnectionDataWrapper(null, null);
80   private List<String> unbindableActions = new ArrayList<>();
81   private Map<String, Intent> stickyIntents = new LinkedHashMap<>();
82   private Handler mainHandler;
83   private Map<ServiceConnection, ServiceConnectionDataWrapper>
84       serviceConnectionDataForServiceConnection = new HashMap<>();
85 
86   private boolean checkActivities;
87 
88   @Implementation(minSdk = P)
startActivitySync(Intent intent, Bundle options)89   protected Activity startActivitySync(Intent intent, Bundle options) {
90     throw new UnsupportedOperationException("Implement me!!");
91   }
92 
93   @Implementation
execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)94   protected ActivityResult execStartActivity(
95       Context who,
96       IBinder contextThread,
97       IBinder token,
98       Activity target,
99       Intent intent,
100       int requestCode,
101       Bundle options) {
102 
103     verifyActivityInManifest(intent);
104     logStartedActivity(intent, requestCode, options);
105 
106     if (who == null) {
107       return null;
108     }
109     return directlyOn(realObject, Instrumentation.class)
110         .execStartActivity(who, contextThread, token, target, intent, requestCode, options);
111   }
112 
113   @Implementation(maxSdk = LOLLIPOP_MR1)
execStartActivity( Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options)114   protected ActivityResult execStartActivity(
115       Context who,
116       IBinder contextThread,
117       IBinder token,
118       Fragment target,
119       Intent intent,
120       int requestCode,
121       Bundle options) {
122     verifyActivityInManifest(intent);
123     logStartedActivity(intent, requestCode, options);
124     return null;
125   }
126 
logStartedActivity(Intent intent, int requestCode, Bundle options)127   private void logStartedActivity(Intent intent, int requestCode, Bundle options) {
128     startedActivities.add(intent);
129     intentRequestCodeMap.put(new FilterComparison(intent), requestCode);
130     startedActivitiesForResults.add(new IntentForResult(intent, requestCode, options));
131   }
132 
verifyActivityInManifest(Intent intent)133   private void verifyActivityInManifest(Intent intent) {
134     if (checkActivities
135         && RuntimeEnvironment.application.getPackageManager().resolveActivity(intent, -1) == null) {
136       throw new ActivityNotFoundException(intent.getAction());
137     }
138   }
139 
140   @Implementation
execStartActivities( Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options)141   protected void execStartActivities(
142       Context who,
143       IBinder contextThread,
144       IBinder token,
145       Activity target,
146       Intent[] intents,
147       Bundle options) {
148     for (Intent intent : intents) {
149       execStartActivity(who, contextThread, token, target, intent, -1, options);
150     }
151   }
152 
153   @Implementation(minSdk = LOLLIPOP)
execStartActivityFromAppTask( Context who, IBinder contextThread, Object appTask, Intent intent, Bundle options)154   protected void execStartActivityFromAppTask(
155       Context who, IBinder contextThread, Object appTask, Intent intent, Bundle options) {
156     throw new UnsupportedOperationException("Implement me!!");
157   }
158 
159   @Implementation(minSdk = M)
execStartActivity( Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options)160   protected ActivityResult execStartActivity(
161       Context who,
162       IBinder contextThread,
163       IBinder token,
164       String target,
165       Intent intent,
166       int requestCode,
167       Bundle options) {
168     verifyActivityInManifest(intent);
169     logStartedActivity(intent, requestCode, options);
170 
171     return directlyOn(realObject, Instrumentation.class)
172         .execStartActivity(who, contextThread, token, target, intent, requestCode, options);
173   }
174 
175   /**
176    * Behaves as {@link #execStartActivity(Context, IBinder, IBinder, String, Intent, int, Bundle).
177    *
178    * <p>Currently ignores the user.
179    */
180   @Implementation(minSdk = JELLY_BEAN_MR1)
execStartActivity( Context who, IBinder contextThread, IBinder token, String resultWho, Intent intent, int requestCode, Bundle options, UserHandle user)181   protected ActivityResult execStartActivity(
182           Context who,
183           IBinder contextThread,
184           IBinder token,
185           String resultWho,
186           Intent intent,
187           int requestCode,
188           Bundle options,
189           UserHandle user) {
190     return execStartActivity(who, contextThread, token, resultWho, intent, requestCode, options);
191   }
192 
193   @Implementation(minSdk = M)
execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, int userId)194   protected ActivityResult execStartActivityAsCaller(
195       Context who,
196       IBinder contextThread,
197       IBinder token,
198       Activity target,
199       Intent intent,
200       int requestCode,
201       Bundle options,
202       boolean ignoreTargetSecurity,
203       int userId) {
204     throw new UnsupportedOperationException("Implement me!!");
205   }
206 
sendOrderedBroadcast( Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras, Context context)207   void sendOrderedBroadcast(
208       Intent intent,
209       String receiverPermission,
210       BroadcastReceiver resultReceiver,
211       Handler scheduler,
212       int initialCode,
213       String initialData,
214       Bundle initialExtras,
215       Context context) {
216     List<Wrapper> receivers = getAppropriateWrappers(intent, receiverPermission);
217     sortByPriority(receivers);
218     receivers.add(new Wrapper(resultReceiver, null, context, null, scheduler));
219     postOrderedToWrappers(receivers, intent, initialCode, initialData, initialExtras, context);
220   }
221 
assertNoBroadcastListenersOfActionRegistered(ContextWrapper context, String action)222   void assertNoBroadcastListenersOfActionRegistered(ContextWrapper context, String action) {
223     for (Wrapper registeredReceiver : registeredReceivers) {
224       if (registeredReceiver.context == context.getBaseContext()) {
225         Iterator<String> actions = registeredReceiver.intentFilter.actionsIterator();
226         while (actions.hasNext()) {
227           if (actions.next().equals(action)) {
228             RuntimeException e =
229                 new IllegalStateException(
230                     "Unexpected BroadcastReceiver on "
231                         + context
232                         + " with action "
233                         + action
234                         + " "
235                         + registeredReceiver.broadcastReceiver
236                         + " that was originally registered here:");
237             e.setStackTrace(registeredReceiver.exception.getStackTrace());
238             throw e;
239           }
240         }
241       }
242     }
243   }
244 
245   /** Returns the BroadcaseReceivers wrappers, matching intent's action and permissions. */
getAppropriateWrappers(Intent intent, String receiverPermission)246   private List<Wrapper> getAppropriateWrappers(Intent intent, String receiverPermission) {
247     broadcastIntents.add(intent);
248 
249     List<Wrapper> result = new ArrayList<>();
250 
251     List<Wrapper> copy = new ArrayList<>();
252     copy.addAll(registeredReceivers);
253     for (Wrapper wrapper : copy) {
254       if (hasMatchingPermission(wrapper.broadcastPermission, receiverPermission)
255           && wrapper.intentFilter.matchAction(intent.getAction())) {
256         final int match =
257             wrapper.intentFilter.matchData(intent.getType(), intent.getScheme(), intent.getData());
258         if (match != IntentFilter.NO_MATCH_DATA && match != IntentFilter.NO_MATCH_TYPE) {
259           result.add(wrapper);
260         }
261       }
262     }
263     return result;
264   }
265 
postIntent( Intent intent, Wrapper wrapper, final AtomicBoolean abort, Context context)266   private void postIntent(
267       Intent intent, Wrapper wrapper, final AtomicBoolean abort, Context context) {
268     final Handler scheduler =
269         (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler(context);
270     final BroadcastReceiver receiver = wrapper.broadcastReceiver;
271     final ShadowBroadcastReceiver shReceiver = Shadow.extract(receiver);
272     final Intent broadcastIntent = intent;
273     scheduler.post(
274         new Runnable() {
275           @Override
276           public void run() {
277             receiver.setPendingResult(ShadowBroadcastPendingResult.create(0, null, null, false));
278             shReceiver.onReceive(context, broadcastIntent, abort);
279           }
280         });
281   }
282 
postToWrappers(List<Wrapper> wrappers, Intent intent, Context context)283   private void postToWrappers(List<Wrapper> wrappers, Intent intent, Context context) {
284     AtomicBoolean abort =
285         new AtomicBoolean(false); // abort state is shared among all broadcast receivers
286     for (Wrapper wrapper : wrappers) {
287       postIntent(intent, wrapper, abort, context);
288     }
289   }
290 
postOrderedToWrappers( List<Wrapper> wrappers, final Intent intent, int initialCode, String data, Bundle extras, final Context context)291   private void postOrderedToWrappers(
292       List<Wrapper> wrappers,
293       final Intent intent,
294       int initialCode,
295       String data,
296       Bundle extras,
297       final Context context) {
298     final AtomicBoolean abort =
299         new AtomicBoolean(false); // abort state is shared among all broadcast receivers
300     ListenableFuture<BroadcastResultHolder> future =
301         immediateFuture(new BroadcastResultHolder(initialCode, data, extras));
302     for (final Wrapper wrapper : wrappers) {
303       future = postIntent(wrapper, intent, future, abort, context);
304     }
305     final ListenableFuture<?> finalFuture = future;
306     future.addListener(
307         new Runnable() {
308           @Override
309           public void run() {
310             getMainHandler(context)
311                 .post(
312                     new Runnable() {
313                       @Override
314                       public void run() {
315                         try {
316                           finalFuture.get();
317                         } catch (InterruptedException | ExecutionException e) {
318                           throw new RuntimeException(e);
319                         }
320                       }
321                     });
322           }
323         },
324         directExecutor());
325   }
326 
327   /**
328    * Enforces that BroadcastReceivers invoked during an ordered broadcast run serially, passing
329    * along their results.
330    */
postIntent( final Wrapper wrapper, final Intent intent, ListenableFuture<BroadcastResultHolder> oldResult, final AtomicBoolean abort, final Context context)331   private ListenableFuture<BroadcastResultHolder> postIntent(
332       final Wrapper wrapper,
333       final Intent intent,
334       ListenableFuture<BroadcastResultHolder> oldResult,
335       final AtomicBoolean abort,
336       final Context context) {
337     final Handler scheduler =
338         (wrapper.scheduler != null) ? wrapper.scheduler : getMainHandler(context);
339     return Futures.transformAsync(
340         oldResult,
341         new AsyncFunction<BroadcastResultHolder, BroadcastResultHolder>() {
342           @Override
343           public ListenableFuture<BroadcastResultHolder> apply(
344               BroadcastResultHolder broadcastResultHolder) throws Exception {
345             final BroadcastReceiver.PendingResult result =
346                 ShadowBroadcastPendingResult.create(
347                     broadcastResultHolder.resultCode,
348                     broadcastResultHolder.resultData,
349                     broadcastResultHolder.resultExtras,
350                     true /*ordered */);
351             wrapper.broadcastReceiver.setPendingResult(result);
352             scheduler.post(
353                 () -> {
354                   ShadowBroadcastReceiver shadowBroadcastReceiver =
355                       Shadow.extract(wrapper.broadcastReceiver);
356                   shadowBroadcastReceiver.onReceive(context, intent, abort);
357                 });
358             return BroadcastResultHolder.transform(result);
359           }
360         },
361         directExecutor());
362   }
363 
364   /**
365    * Broadcasts the {@code Intent} by iterating through the registered receivers, invoking their
366    * filters including permissions, and calling {@code onReceive(Application, Intent)} as
367    * appropriate. Does not enqueue the {@code Intent} for later inspection.
368    *
369    * @param context
370    * @param intent the {@code Intent} to broadcast todo: enqueue the Intent for later inspection
371    */
372   void sendBroadcastWithPermission(Intent intent, String receiverPermission, Context context) {
373     List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
374     postToWrappers(wrappers, intent, context);
375   }
376 
377   void sendOrderedBroadcastWithPermission(
378       Intent intent, String receiverPermission, Context context) {
379     List<Wrapper> wrappers = getAppropriateWrappers(intent, receiverPermission);
380     // sort by the decrease of priorities
381     sortByPriority(wrappers);
382 
383     postOrderedToWrappers(wrappers, intent, 0, null, null, context);
384   }
385 
386   private void sortByPriority(List<Wrapper> wrappers) {
387     Collections.sort(
388         wrappers,
389         new Comparator<Wrapper>() {
390           @Override
391           public int compare(Wrapper o1, Wrapper o2) {
392             return Integer.compare(
393                 o2.getIntentFilter().getPriority(), o1.getIntentFilter().getPriority());
394           }
395         });
396   }
397 
398   List<Intent> getBroadcastIntents() {
399     return broadcastIntents;
400   }
401 
402   Intent getNextStartedActivity() {
403     if (startedActivities.isEmpty()) {
404       return null;
405     } else {
406       return startedActivities.remove(startedActivities.size() - 1);
407     }
408   }
409 
410   Intent peekNextStartedActivity() {
411     if (startedActivities.isEmpty()) {
412       return null;
413     } else {
414       return startedActivities.get(startedActivities.size() - 1);
415     }
416   }
417 
418   /**
419    * Clears all {@code Intent}s started by {@link #execStartActivity(Context, IBinder, IBinder,
420    * Activity, Intent, int, Bundle)}, {@link #execStartActivity(Context, IBinder, IBinder, Fragment,
421    * Intent, int, Bundle)}, and {@link #execStartActivity(Context, IBinder, IBinder, String, Intent,
422    * int, Bundle)}.
423    */
424   void clearNextStartedActivities() {
425     startedActivities.clear();
426   }
427 
428   IntentForResult getNextStartedActivityForResult() {
429     if (startedActivitiesForResults.isEmpty()) {
430       return null;
431     } else {
432       return startedActivitiesForResults.remove(startedActivitiesForResults.size() - 1);
433     }
434   }
435 
436   IntentForResult peekNextStartedActivityForResult() {
437     if (startedActivitiesForResults.isEmpty()) {
438       return null;
439     } else {
440       return startedActivitiesForResults.get(startedActivitiesForResults.size() - 1);
441     }
442   }
443 
444   void checkActivities(boolean checkActivities) {
445     this.checkActivities = checkActivities;
446   }
447 
448   int getRequestCodeForIntent(Intent requestIntent) {
449     Integer requestCode = intentRequestCodeMap.get(new Intent.FilterComparison(requestIntent));
450     if (requestCode == null) {
451       throw new RuntimeException(
452           "No intent matches " + requestIntent + " among " + intentRequestCodeMap.keySet());
453     }
454     return requestCode;
455   }
456 
457   protected ComponentName startService(Intent intent) {
458     startedServices.add(new Intent.FilterComparison(intent));
459     if (intent.getComponent() != null) {
460       return intent.getComponent();
461     }
462     return new ComponentName("some.service.package", "SomeServiceName-FIXME");
463   }
464 
465   boolean stopService(Intent name) {
466     stoppedServices.add(new Intent.FilterComparison(name));
467     return startedServices.contains(new Intent.FilterComparison(name));
468   }
469 
470   void setComponentNameAndServiceForBindService(ComponentName name, IBinder service) {
471     defaultServiceConnectionData = new ServiceConnectionDataWrapper(name, service);
472   }
473 
474   void setComponentNameAndServiceForBindServiceForIntent(
475       Intent intent, ComponentName name, IBinder service) {
476     serviceConnectionDataForIntent.put(
477         new Intent.FilterComparison(intent), new ServiceConnectionDataWrapper(name, service));
478   }
479 
480   protected boolean bindService(
481       final Intent intent, final ServiceConnection serviceConnection, int i) {
482     boundServiceConnections.add(serviceConnection);
483     unboundServiceConnections.remove(serviceConnection);
484     if (unbindableActions.contains(intent.getAction())) {
485       return false;
486     }
487     startedServices.add(new Intent.FilterComparison(intent));
488     ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
489     shadowLooper.post(
490         () -> {
491           final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
492           final Intent.FilterComparison filterComparison = new Intent.FilterComparison(intent);
493           if (serviceConnectionDataForIntent.containsKey(filterComparison)) {
494             serviceConnectionDataWrapper = serviceConnectionDataForIntent.get(filterComparison);
495           } else {
496             serviceConnectionDataWrapper = defaultServiceConnectionData;
497           }
498           serviceConnectionDataForServiceConnection.put(
499               serviceConnection, serviceConnectionDataWrapper);
500           serviceConnection.onServiceConnected(
501               serviceConnectionDataWrapper.componentNameForBindService,
502               serviceConnectionDataWrapper.binderForBindService);
503         },
504         0);
505     return true;
506   }
507 
508   protected void unbindService(final ServiceConnection serviceConnection) {
509     if (unbindServiceShouldThrowIllegalArgument) {
510       throw new IllegalArgumentException();
511     }
512 
513     unboundServiceConnections.add(serviceConnection);
514     boundServiceConnections.remove(serviceConnection);
515     ShadowLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
516     shadowLooper.post(
517         () -> {
518           final ServiceConnectionDataWrapper serviceConnectionDataWrapper;
519           if (serviceConnectionDataForServiceConnection.containsKey(serviceConnection)) {
520             serviceConnectionDataWrapper =
521                 serviceConnectionDataForServiceConnection.get(serviceConnection);
522           } else {
523             serviceConnectionDataWrapper = defaultServiceConnectionData;
524           }
525           serviceConnection.onServiceDisconnected(
526               serviceConnectionDataWrapper.componentNameForBindService);
527         },
528         0);
529   }
530 
531   protected List<ServiceConnection> getBoundServiceConnections() {
532     return boundServiceConnections;
533   }
534 
535   void setUnbindServiceShouldThrowIllegalArgument(boolean flag) {
536     unbindServiceShouldThrowIllegalArgument = flag;
537   }
538 
539   protected List<ServiceConnection> getUnboundServiceConnections() {
540     return unboundServiceConnections;
541   }
542 
543   void declareActionUnbindable(String action) {
544     unbindableActions.add(action);
545   }
546 
547   public List<String> getUnbindableActions() {
548     return unbindableActions;
549   }
550 
551   /**
552    * Consumes the most recent {@code Intent} started by {@link
553    * #startService(android.content.Intent)} and returns it.
554    *
555    * @return the most recently started {@code Intent}
556    */
557   Intent getNextStartedService() {
558     if (startedServices.isEmpty()) {
559       return null;
560     } else {
561       return startedServices.remove(0).getIntent();
562     }
563   }
564 
565   /**
566    * Returns the most recent {@code Intent} started by {@link #startService(android.content.Intent)}
567    * without consuming it.
568    *
569    * @return the most recently started {@code Intent}
570    */
571   Intent peekNextStartedService() {
572     if (startedServices.isEmpty()) {
573       return null;
574     } else {
575       return startedServices.get(0).getIntent();
576     }
577   }
578 
579   /** Clears all {@code Intent} started by {@link #startService(android.content.Intent)}. */
580   void clearStartedServices() {
581     startedServices.clear();
582   }
583 
584   /**
585    * Consumes the {@code Intent} requested to stop a service by {@link
586    * #stopService(android.content.Intent)} from the bottom of the stack of stop requests.
587    */
588   Intent getNextStoppedService() {
589     if (stoppedServices.isEmpty()) {
590       return null;
591     } else {
592       return stoppedServices.remove(0).getIntent();
593     }
594   }
595 
596   void sendStickyBroadcast(Intent intent, Context context) {
597     stickyIntents.put(intent.getAction(), intent);
598     sendBroadcast(intent, context);
599   }
600 
601   void sendBroadcast(Intent intent, Context context) {
602     sendBroadcastWithPermission(intent, null, context);
603   }
604 
605   Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Context context) {
606     return registerReceiver(receiver, filter, null, null, context);
607   }
608 
609   Intent registerReceiver(
610       BroadcastReceiver receiver,
611       IntentFilter filter,
612       String broadcastPermission,
613       Handler scheduler,
614       Context context) {
615     return registerReceiverWithContext(receiver, filter, broadcastPermission, scheduler, context);
616   }
617 
618   Intent registerReceiverWithContext(
619       BroadcastReceiver receiver,
620       IntentFilter filter,
621       String broadcastPermission,
622       Handler scheduler,
623       Context context) {
624     if (receiver != null) {
625       registeredReceivers.add(
626           new Wrapper(receiver, filter, context, broadcastPermission, scheduler));
627     }
628     return processStickyIntents(filter, receiver, context);
629   }
630 
631   private Intent processStickyIntents(
632       IntentFilter filter, BroadcastReceiver receiver, Context context) {
633     Intent result = null;
634     for (Intent stickyIntent : stickyIntents.values()) {
635       if (filter.matchAction(stickyIntent.getAction())) {
636         if (result == null) {
637           result = stickyIntent;
638         }
639         if (receiver != null) {
640           receiver.setPendingResult(ShadowBroadcastPendingResult.createSticky(stickyIntent));
641           receiver.onReceive(context, stickyIntent);
642           receiver.setPendingResult(null);
643         } else if (result != null) {
644           break;
645         }
646       }
647     }
648     return result;
649   }
650 
651   void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
652     boolean found = false;
653     Iterator<Wrapper> iterator = registeredReceivers.iterator();
654     while (iterator.hasNext()) {
655       Wrapper wrapper = iterator.next();
656       if (wrapper.broadcastReceiver == broadcastReceiver) {
657         iterator.remove();
658         found = true;
659       }
660     }
661     if (!found) {
662       throw new IllegalArgumentException("Receiver not registered: " + broadcastReceiver);
663     }
664   }
665 
666   /** @deprecated use PackageManager.queryBroadcastReceivers instead */
667   @Deprecated
668   boolean hasReceiverForIntent(Intent intent) {
669     for (Wrapper wrapper : registeredReceivers) {
670       if (wrapper.intentFilter.matchAction(intent.getAction())) {
671         return true;
672       }
673     }
674     return false;
675   }
676 
677   /** @deprecated use PackageManager.queryBroadcastReceivers instead */
678   @Deprecated
679   List<BroadcastReceiver> getReceiversForIntent(Intent intent) {
680     ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
681     for (Wrapper wrapper : registeredReceivers) {
682       if (wrapper.intentFilter.matchAction(intent.getAction())) {
683         broadcastReceivers.add(wrapper.getBroadcastReceiver());
684       }
685     }
686     return broadcastReceivers;
687   }
688 
689   /** @return list of {@link Wrapper}s for registered receivers */
690   List<Wrapper> getRegisteredReceivers() {
691     return registeredReceivers;
692   }
693 
694   int checkPermission(String permission, int pid, int uid) {
695     Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair(pid, uid));
696     return grantedPermissionsForPidUid != null && grantedPermissionsForPidUid.contains(permission)
697         ? PERMISSION_GRANTED
698         : PERMISSION_DENIED;
699   }
700 
701   void grantPermissions(String... permissionNames) {
702     grantPermissions(Process.myPid(), Process.myUid(), permissionNames);
703   }
704 
705   void grantPermissions(int pid, int uid, String... permissions) {
706     Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair<>(pid, uid));
707     if (grantedPermissionsForPidUid == null) {
708       grantedPermissionsForPidUid = new HashSet<>();
709       grantedPermissionsMap.put(new Pair<>(pid, uid), grantedPermissionsForPidUid);
710     }
711     Collections.addAll(grantedPermissionsForPidUid, permissions);
712   }
713 
714   void denyPermissions(String... permissionNames) {
715     denyPermissions(Process.myPid(), Process.myUid(), permissionNames);
716   }
717 
718   void denyPermissions(int pid, int uid, String... permissions) {
719     Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair<>(pid, uid));
720     if (grantedPermissionsForPidUid != null) {
721       for (String permissionName : permissions) {
722         grantedPermissionsForPidUid.remove(permissionName);
723       }
724     }
725   }
726 
727   private boolean hasMatchingPermission(String permission1, String permission2) {
728     return permission1 == null ? permission2 == null : permission1.equals(permission2);
729   }
730 
731   private Handler getMainHandler(Context context) {
732     if (mainHandler == null) {
733       mainHandler = new Handler(context.getMainLooper());
734     }
735     return mainHandler;
736   }
737 
738 
739 
740 
741   private static final class BroadcastResultHolder {
742     private final int resultCode;
743     private final String resultData;
744     private final Bundle resultExtras;
745 
746     private BroadcastResultHolder(int resultCode, String resultData, Bundle resultExtras) {
747       this.resultCode = resultCode;
748       this.resultData = resultData;
749       this.resultExtras = resultExtras;
750     }
751 
752     private static ListenableFuture<BroadcastResultHolder> transform(
753         BroadcastReceiver.PendingResult result) {
754       ShadowBroadcastPendingResult shadowBroadcastPendingResult = Shadow.extract(result);
755       return Futures.transform(
756           shadowBroadcastPendingResult.getFuture(),
757           pendingResult ->
758               new BroadcastResultHolder(
759                   pendingResult.getResultCode(),
760                   pendingResult.getResultData(),
761                   pendingResult.getResultExtras(false)),
762           directExecutor());
763     }
764   }
765 
766   private static class ServiceConnectionDataWrapper {
767     public final ComponentName componentNameForBindService;
768     public final IBinder binderForBindService;
769 
770     private ServiceConnectionDataWrapper(
771         ComponentName componentNameForBindService, IBinder binderForBindService) {
772       this.componentNameForBindService = componentNameForBindService;
773       this.binderForBindService = binderForBindService;
774     }
775   }
776 
777   public static Instrumentation getInstrumentation() {
778     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
779     return activityThread.getInstrumentation();
780   }
781 }
782