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