• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN;
4 import static android.os.Build.VERSION_CODES.LOLLIPOP;
5 import static android.os.Build.VERSION_CODES.M;
6 import static org.robolectric.shadow.api.Shadow.directlyOn;
7 
8 import android.R;
9 import android.app.Activity;
10 import android.app.ActivityManager;
11 import android.app.ActivityThread;
12 import android.app.Application;
13 import android.app.Dialog;
14 import android.app.Instrumentation;
15 import android.content.ComponentName;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.pm.ActivityInfo;
19 import android.content.pm.PackageManager;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.content.res.Configuration;
22 import android.database.Cursor;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.os.IBinder;
26 import android.text.Selection;
27 import android.text.SpannableStringBuilder;
28 import android.view.LayoutInflater;
29 import android.view.Menu;
30 import android.view.MenuInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewRootImpl;
34 import android.view.Window;
35 import com.android.internal.app.IVoiceInteractor;
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import org.robolectric.RuntimeEnvironment;
43 import org.robolectric.annotation.HiddenApi;
44 import org.robolectric.annotation.Implementation;
45 import org.robolectric.annotation.Implements;
46 import org.robolectric.annotation.RealObject;
47 import org.robolectric.fakes.RoboMenuItem;
48 import org.robolectric.shadow.api.Shadow;
49 import org.robolectric.util.ReflectionHelpers;
50 
51 @Implements(Activity.class)
52 public class ShadowActivity extends ShadowContextThemeWrapper {
53 
54   @RealObject
55   protected Activity realActivity;
56 
57   private int resultCode;
58   private Intent resultIntent;
59   private Activity parent;
60   private int requestedOrientation = -1;
61   private View currentFocus;
62   private Integer lastShownDialogId = null;
63   private int pendingTransitionEnterAnimResId = -1;
64   private int pendingTransitionExitAnimResId = -1;
65   private Object lastNonConfigurationInstance;
66   private Map<Integer, Dialog> dialogForId = new HashMap<>();
67   private ArrayList<Cursor> managedCursors = new ArrayList<>();
68   private int mDefaultKeyMode = Activity.DEFAULT_KEYS_DISABLE;
69   private SpannableStringBuilder mDefaultKeySsb = null;
70   private int streamType = -1;
71   private boolean mIsTaskRoot = true;
72   private Menu optionsMenu;
73   private ComponentName callingActivity;
74   private boolean isLockTask;
75   private PermissionsRequest lastRequestedPermission;
76 
setApplication(Application application)77   public void setApplication(Application application) {
78     ReflectionHelpers.setField(realActivity, "mApplication", application);
79   }
80 
callAttach(Intent intent)81   public void callAttach(Intent intent) {
82     int apiLevel = RuntimeEnvironment.getApiLevel();
83     Application application = RuntimeEnvironment.application;
84     Context baseContext = RuntimeEnvironment.application.getBaseContext();
85     Class<?> nonConfigurationInstancesClass = getNonConfigurationClass();
86 
87     ActivityInfo activityInfo;
88     try {
89       activityInfo = application.getPackageManager().getActivityInfo(new ComponentName(application.getPackageName(), realActivity.getClass().getName()), PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
90     } catch (NameNotFoundException e) {
91       throw new RuntimeException();
92     }
93 
94     CharSequence activityTitle = activityInfo.loadLabel(baseContext.getPackageManager());
95 
96     Instrumentation instrumentation =
97         ((ActivityThread) RuntimeEnvironment.getActivityThread()).getInstrumentation();
98     if (apiLevel <= Build.VERSION_CODES.KITKAT) {
99       ReflectionHelpers.callInstanceMethod(
100           Activity.class,
101           realActivity,
102           "attach",
103           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
104           ReflectionHelpers.ClassParameter.from(
105               ActivityThread.class, RuntimeEnvironment.getActivityThread()),
106           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
107           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
108           ReflectionHelpers.ClassParameter.from(int.class, 0),
109           ReflectionHelpers.ClassParameter.from(Application.class, application),
110           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
111           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
112           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
113           ReflectionHelpers.ClassParameter.from(Activity.class, null),
114           ReflectionHelpers.ClassParameter.from(String.class, "id"),
115           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
116           ReflectionHelpers.ClassParameter.from(
117               Configuration.class, application.getResources().getConfiguration()));
118     } else if (apiLevel <= Build.VERSION_CODES.LOLLIPOP) {
119       ReflectionHelpers.callInstanceMethod(
120           Activity.class,
121           realActivity,
122           "attach",
123           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
124           ReflectionHelpers.ClassParameter.from(
125               ActivityThread.class, RuntimeEnvironment.getActivityThread()),
126           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
127           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
128           ReflectionHelpers.ClassParameter.from(int.class, 0),
129           ReflectionHelpers.ClassParameter.from(Application.class, application),
130           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
131           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
132           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
133           ReflectionHelpers.ClassParameter.from(Activity.class, null),
134           ReflectionHelpers.ClassParameter.from(String.class, "id"),
135           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
136           ReflectionHelpers.ClassParameter.from(
137               Configuration.class, application.getResources().getConfiguration()),
138           ReflectionHelpers.ClassParameter.from(IVoiceInteractor.class, null)); // ADDED
139     } else if (apiLevel <= Build.VERSION_CODES.M) {
140       ReflectionHelpers.callInstanceMethod(
141           Activity.class,
142           realActivity,
143           "attach",
144           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
145           ReflectionHelpers.ClassParameter.from(
146               ActivityThread.class, RuntimeEnvironment.getActivityThread()),
147           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
148           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
149           ReflectionHelpers.ClassParameter.from(int.class, 0),
150           ReflectionHelpers.ClassParameter.from(Application.class, application),
151           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
152           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
153           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
154           ReflectionHelpers.ClassParameter.from(Activity.class, null),
155           ReflectionHelpers.ClassParameter.from(String.class, "id"),
156           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
157           ReflectionHelpers.ClassParameter.from(
158               Configuration.class, application.getResources().getConfiguration()),
159           ReflectionHelpers.ClassParameter.from(String.class, "referrer"),
160           ReflectionHelpers.ClassParameter.from(
161               IVoiceInteractor.class, null)); // SAME AS LOLLIPOP ---------------------------
162     } else if (apiLevel <= Build.VERSION_CODES.N_MR1) {
163       ReflectionHelpers.callInstanceMethod(
164           Activity.class,
165           realActivity,
166           "attach",
167           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
168           ReflectionHelpers.ClassParameter.from(
169               ActivityThread.class, RuntimeEnvironment.getActivityThread()),
170           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
171           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
172           ReflectionHelpers.ClassParameter.from(int.class, 0),
173           ReflectionHelpers.ClassParameter.from(Application.class, application),
174           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
175           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
176           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
177           ReflectionHelpers.ClassParameter.from(Activity.class, null),
178           ReflectionHelpers.ClassParameter.from(String.class, "id"),
179           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
180           ReflectionHelpers.ClassParameter.from(
181               Configuration.class, application.getResources().getConfiguration()),
182           ReflectionHelpers.ClassParameter.from(String.class, "referrer"),
183           ReflectionHelpers.ClassParameter.from(IVoiceInteractor.class, null),
184           ReflectionHelpers.ClassParameter.from(Window.class, null) // ADDED
185           );
186     } else if (apiLevel <= Build.VERSION_CODES.P) {
187       ReflectionHelpers.callInstanceMethod(
188           Activity.class,
189           realActivity,
190           "attach",
191           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
192           ReflectionHelpers.ClassParameter.from(
193               ActivityThread.class, RuntimeEnvironment.getActivityThread()),
194           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
195           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
196           ReflectionHelpers.ClassParameter.from(int.class, 0),
197           ReflectionHelpers.ClassParameter.from(Application.class, application),
198           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
199           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
200           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
201           ReflectionHelpers.ClassParameter.from(Activity.class, null),
202           ReflectionHelpers.ClassParameter.from(String.class, "id"),
203           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
204           ReflectionHelpers.ClassParameter.from(
205               Configuration.class, application.getResources().getConfiguration()),
206           ReflectionHelpers.ClassParameter.from(String.class, "referrer"),
207           ReflectionHelpers.ClassParameter.from(IVoiceInteractor.class, null),
208           ReflectionHelpers.ClassParameter.from(Window.class, null),
209           ReflectionHelpers.ClassParameter.from(
210               ViewRootImpl.ActivityConfigCallback.class, null) // ADDED
211           );
212     } else if (apiLevel >= Build.VERSION_CODES.Q) {
213       ReflectionHelpers.callInstanceMethod(
214           Activity.class,
215           realActivity,
216           "attach",
217           ReflectionHelpers.ClassParameter.from(Context.class, baseContext),
218           ReflectionHelpers.ClassParameter.from(
219                   ActivityThread.class, RuntimeEnvironment.getActivityThread()),
220           ReflectionHelpers.ClassParameter.from(Instrumentation.class, instrumentation),
221           ReflectionHelpers.ClassParameter.from(IBinder.class, null),
222           ReflectionHelpers.ClassParameter.from(int.class, 0),
223           ReflectionHelpers.ClassParameter.from(Application.class, application),
224           ReflectionHelpers.ClassParameter.from(Intent.class, intent),
225           ReflectionHelpers.ClassParameter.from(ActivityInfo.class, activityInfo),
226           ReflectionHelpers.ClassParameter.from(CharSequence.class, activityTitle),
227           ReflectionHelpers.ClassParameter.from(Activity.class, null),
228           ReflectionHelpers.ClassParameter.from(String.class, "id"),
229           ReflectionHelpers.ClassParameter.from(nonConfigurationInstancesClass, null),
230           ReflectionHelpers.ClassParameter.from(
231                   Configuration.class, application.getResources().getConfiguration()),
232           ReflectionHelpers.ClassParameter.from(String.class, "referrer"),
233           ReflectionHelpers.ClassParameter.from(IVoiceInteractor.class, null),
234           ReflectionHelpers.ClassParameter.from(Window.class, null),
235           ReflectionHelpers.ClassParameter.from(
236                   ViewRootImpl.ActivityConfigCallback.class, null),
237           ReflectionHelpers.ClassParameter.from(IBinder.class, null) // ADDED
238       );
239     } else {
240       throw new RuntimeException("Could not find AndroidRuntimeAdapter for API level: " + apiLevel);
241     }
242 
243     int theme = activityInfo.getThemeResource();
244     if (theme != 0) {
245       realActivity.setTheme(theme);
246     }
247   }
248 
getNonConfigurationClass()249   private Class<?> getNonConfigurationClass() {
250     try {
251       return getClass().getClassLoader().loadClass("android.app.Activity$NonConfigurationInstances");
252     } catch (ClassNotFoundException e) {
253       throw new RuntimeException(e);
254     }
255   }
256 
setCallingActivity(ComponentName activityName)257   public void setCallingActivity(ComponentName activityName) {
258     callingActivity = activityName;
259   }
260 
261   @Implementation
getCallingActivity()262   protected ComponentName getCallingActivity() {
263     return callingActivity;
264   }
265 
266   @Implementation
setDefaultKeyMode(int keyMode)267   protected void setDefaultKeyMode(int keyMode) {
268     mDefaultKeyMode = keyMode;
269 
270     // Some modes use a SpannableStringBuilder to track & dispatch input events
271     // This list must remain in sync with the switch in onKeyDown()
272     switch (mDefaultKeyMode) {
273       case Activity.DEFAULT_KEYS_DISABLE:
274       case Activity.DEFAULT_KEYS_SHORTCUT:
275         mDefaultKeySsb = null;      // not used in these modes
276         break;
277       case Activity.DEFAULT_KEYS_DIALER:
278       case Activity.DEFAULT_KEYS_SEARCH_LOCAL:
279       case Activity.DEFAULT_KEYS_SEARCH_GLOBAL:
280         mDefaultKeySsb = new SpannableStringBuilder();
281         Selection.setSelection(mDefaultKeySsb, 0);
282         break;
283       default:
284         throw new IllegalArgumentException();
285     }
286   }
287 
getDefaultKeymode()288   public int getDefaultKeymode() {
289     return mDefaultKeyMode;
290   }
291 
292   @Implementation
setResult(int resultCode)293   protected final void setResult(int resultCode) {
294     this.resultCode = resultCode;
295   }
296 
297   @Implementation
setResult(int resultCode, Intent data)298   protected final void setResult(int resultCode, Intent data) {
299     this.resultCode = resultCode;
300     this.resultIntent = data;
301   }
302 
303   @Implementation
getLayoutInflater()304   protected LayoutInflater getLayoutInflater() {
305     return LayoutInflater.from(realActivity);
306   }
307 
308   @Implementation
getMenuInflater()309   protected MenuInflater getMenuInflater() {
310     return new MenuInflater(realActivity);
311   }
312 
313   /**
314    * Checks to ensure that the{@code contentView} has been set
315    *
316    * @param id ID of the view to find
317    * @return the view
318    * @throws RuntimeException if the {@code contentView} has not been called first
319    */
320   @Implementation
findViewById(int id)321   protected View findViewById(int id) {
322     return getWindow().findViewById(id);
323   }
324 
325   @Implementation
getParent()326   protected final Activity getParent() {
327     return parent;
328   }
329 
330   /**
331    * Allow setting of Parent fragmentActivity (for unit testing purposes only)
332    *
333    * @param parent Parent fragmentActivity to set on this fragmentActivity
334    */
335   @HiddenApi @Implementation
setParent(Activity parent)336   public void setParent(Activity parent) {
337     this.parent = parent;
338   }
339 
340   @Implementation
onBackPressed()341   protected void onBackPressed() {
342     finish();
343   }
344 
345   @Implementation
finish()346   protected void finish() {
347     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
348     ReflectionHelpers.setField(Activity.class, realActivity, "mFinished", true);
349   }
350 
351   @Implementation(minSdk = LOLLIPOP)
finishAndRemoveTask()352   protected void finishAndRemoveTask() {
353     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
354     ReflectionHelpers.setField(Activity.class, realActivity, "mFinished", true);
355   }
356 
357   @Implementation(minSdk = JELLY_BEAN)
finishAffinity()358   protected void finishAffinity() {
359     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
360     ReflectionHelpers.setField(Activity.class, realActivity, "mFinished", true);
361   }
362 
resetIsFinishing()363   public void resetIsFinishing() {
364     ReflectionHelpers.setField(Activity.class, realActivity, "mFinished", false);
365   }
366 
367   /**
368    * Returns whether {@link #finish()} was called.
369    *
370    * @deprecated Use {@link Activity#isFinishing()} instead.
371    */
372   @Deprecated
isFinishing()373   public boolean isFinishing() {
374     return directlyOn(realActivity, Activity.class).isFinishing();
375   }
376 
377   /**
378    * Constructs a new Window (a {@link com.android.internal.policy.impl.PhoneWindow}) if no window
379    * has previously been set.
380    *
381    * @return the window associated with this Activity
382    */
383   @Implementation
getWindow()384   protected Window getWindow() {
385     Window window = directlyOn(realActivity, Activity.class).getWindow();
386 
387     if (window == null) {
388       try {
389         window = ShadowWindow.create(realActivity);
390         setWindow(window);
391       } catch (Exception e) {
392         throw new RuntimeException("Window creation failed!", e);
393       }
394     }
395 
396     return window;
397   }
398 
setWindow(Window window)399   public void setWindow(Window window) {
400     ReflectionHelpers.setField(realActivity, "mWindow", window);
401   }
402 
403   @Implementation
runOnUiThread(Runnable action)404   protected void runOnUiThread(Runnable action) {
405     ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
406   }
407 
408   @Implementation
setRequestedOrientation(int requestedOrientation)409   protected void setRequestedOrientation(int requestedOrientation) {
410     if (getParent() != null) {
411       getParent().setRequestedOrientation(requestedOrientation);
412     } else {
413       this.requestedOrientation = requestedOrientation;
414     }
415   }
416 
417   @Implementation
getRequestedOrientation()418   protected int getRequestedOrientation() {
419     if (getParent() != null) {
420       return getParent().getRequestedOrientation();
421     } else {
422       return this.requestedOrientation;
423     }
424   }
425 
426   @Implementation
getTaskId()427   protected int getTaskId() {
428     return 0;
429   }
430 
431   /**
432    * @return the {@code contentView} set by one of the {@code setContentView()} methods
433    */
getContentView()434   public View getContentView() {
435     return ((ViewGroup) getWindow().findViewById(R.id.content)).getChildAt(0);
436   }
437 
438   /**
439    * @return the {@code resultCode} set by one of the {@code setResult()} methods
440    */
getResultCode()441   public int getResultCode() {
442     return resultCode;
443   }
444 
445   /**
446    * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
447    */
getResultIntent()448   public Intent getResultIntent() {
449     return resultIntent;
450   }
451 
452   /**
453    * Consumes and returns the next {@code Intent} on the
454    * started activities for results stack.
455    *
456    * @return the next started {@code Intent} for an activity, wrapped in
457    *         an {@link ShadowActivity.IntentForResult} object
458    */
getNextStartedActivityForResult()459   public IntentForResult getNextStartedActivityForResult() {
460     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
461     ShadowInstrumentation shadowInstrumentation = Shadow.extract(activityThread.getInstrumentation());
462     return shadowInstrumentation.getNextStartedActivityForResult();
463   }
464 
465   /**
466    * Returns the most recent {@code Intent} started by
467    * {@link Activity#startActivityForResult(Intent, int)} without consuming it.
468    *
469    * @return the most recently started {@code Intent}, wrapped in
470    *         an {@link ShadowActivity.IntentForResult} object
471    */
peekNextStartedActivityForResult()472   public IntentForResult peekNextStartedActivityForResult() {
473     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
474     ShadowInstrumentation shadowInstrumentation = Shadow.extract(activityThread.getInstrumentation());
475     return shadowInstrumentation.peekNextStartedActivityForResult();
476   }
477 
478   @Implementation
getLastNonConfigurationInstance()479   protected Object getLastNonConfigurationInstance() {
480     return lastNonConfigurationInstance;
481   }
482 
setLastNonConfigurationInstance(Object lastNonConfigurationInstance)483   public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) {
484     this.lastNonConfigurationInstance = lastNonConfigurationInstance;
485   }
486 
487   /**
488    * @param view View to focus.
489    */
setCurrentFocus(View view)490   public void setCurrentFocus(View view) {
491     currentFocus = view;
492   }
493 
494   @Implementation
getCurrentFocus()495   protected View getCurrentFocus() {
496     return currentFocus;
497   }
498 
getPendingTransitionEnterAnimationResourceId()499   public int getPendingTransitionEnterAnimationResourceId() {
500     return pendingTransitionEnterAnimResId;
501   }
502 
getPendingTransitionExitAnimationResourceId()503   public int getPendingTransitionExitAnimationResourceId() {
504     return pendingTransitionExitAnimResId;
505   }
506 
507   @Implementation
onCreateOptionsMenu(Menu menu)508   protected boolean onCreateOptionsMenu(Menu menu) {
509     optionsMenu = menu;
510     return directlyOn(realActivity, Activity.class).onCreateOptionsMenu(menu);
511   }
512 
513   /**
514    * Return the options menu.
515    *
516    * @return  Options menu.
517    */
getOptionsMenu()518   public Menu getOptionsMenu() {
519     return optionsMenu;
520   }
521 
522   /**
523    * Perform a click on a menu item.
524    *
525    * @param menuItemResId Menu item resource ID.
526    * @return True if the click was handled, false otherwise.
527    */
clickMenuItem(int menuItemResId)528   public boolean clickMenuItem(int menuItemResId) {
529     final RoboMenuItem item = new RoboMenuItem(menuItemResId);
530     return realActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
531   }
532 
533   /** For internal use only. Not for public use. */
callOnActivityResult(int requestCode, int resultCode, Intent resultData)534   public void callOnActivityResult(int requestCode, int resultCode, Intent resultData) {
535     final ActivityInvoker invoker = new ActivityInvoker();
536     invoker
537         .call("onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class)
538         .with(requestCode, resultCode, resultData);
539   }
540 
541   /**
542    * Container object to hold an Intent, together with the requestCode used
543    * in a call to {@code Activity.startActivityForResult(Intent, int)}
544    */
545   public static class IntentForResult {
546     public Intent intent;
547     public int requestCode;
548     public Bundle options;
549 
IntentForResult(Intent intent, int requestCode)550     public IntentForResult(Intent intent, int requestCode) {
551       this.intent = intent;
552       this.requestCode = requestCode;
553       this.options = null;
554     }
555 
IntentForResult(Intent intent, int requestCode, Bundle options)556     public IntentForResult(Intent intent, int requestCode, Bundle options) {
557       this.intent = intent;
558       this.requestCode = requestCode;
559       this.options = options;
560     }
561   }
562 
receiveResult(Intent requestIntent, int resultCode, Intent resultIntent)563   public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) {
564     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
565     ShadowInstrumentation shadowInstrumentation = Shadow.extract(activityThread.getInstrumentation());
566     int requestCode = shadowInstrumentation.getRequestCodeForIntent(requestIntent);
567 
568     callOnActivityResult(requestCode, resultCode, resultIntent);
569   }
570 
571   @Implementation
showDialog(int id)572   protected final void showDialog(int id) {
573     showDialog(id, null);
574   }
575 
576   @Implementation
dismissDialog(int id)577   protected final void dismissDialog(int id) {
578     final Dialog dialog = dialogForId.get(id);
579     if (dialog == null) {
580       throw new IllegalArgumentException();
581     }
582 
583     dialog.dismiss();
584   }
585 
586   @Implementation
removeDialog(int id)587   protected final void removeDialog(int id) {
588     dialogForId.remove(id);
589   }
590 
591   @Implementation
showDialog(int id, Bundle bundle)592   protected final boolean showDialog(int id, Bundle bundle) {
593     this.lastShownDialogId = id;
594     Dialog dialog = dialogForId.get(id);
595 
596     if (dialog == null) {
597       final ActivityInvoker invoker = new ActivityInvoker();
598       dialog = (Dialog) invoker.call("onCreateDialog", Integer.TYPE).with(id);
599       if (dialog == null) {
600         return false;
601       }
602       if (bundle == null) {
603         invoker.call("onPrepareDialog", Integer.TYPE, Dialog.class).with(id, dialog);
604       } else {
605         invoker.call("onPrepareDialog", Integer.TYPE, Dialog.class, Bundle.class).with(id, dialog, bundle);
606       }
607 
608       dialogForId.put(id, dialog);
609     }
610 
611     dialog.show();
612     return true;
613   }
614 
setIsTaskRoot(boolean isRoot)615   public void setIsTaskRoot(boolean isRoot) {
616     mIsTaskRoot = isRoot;
617   }
618 
619   @Implementation
isTaskRoot()620   protected final boolean isTaskRoot() {
621     return mIsTaskRoot;
622   }
623 
624   /**
625    * @return the dialog resource id passed into
626    *         {@code Activity.showDialog(int, Bundle)} or {@code Activity.showDialog(int)}
627    */
getLastShownDialogId()628   public Integer getLastShownDialogId() {
629     return lastShownDialogId;
630   }
631 
hasCancelledPendingTransitions()632   public boolean hasCancelledPendingTransitions() {
633     return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0;
634   }
635 
636   @Implementation
overridePendingTransition(int enterAnim, int exitAnim)637   protected void overridePendingTransition(int enterAnim, int exitAnim) {
638     pendingTransitionEnterAnimResId = enterAnim;
639     pendingTransitionExitAnimResId = exitAnim;
640   }
641 
getDialogById(int dialogId)642   public Dialog getDialogById(int dialogId) {
643     return dialogForId.get(dialogId);
644   }
645 
646   @Implementation
recreate()647   protected void recreate() {
648     Bundle outState = new Bundle();
649     final ActivityInvoker invoker = new ActivityInvoker();
650 
651     invoker.call("onSaveInstanceState", Bundle.class).with(outState);
652     invoker.call("onPause").withNothing();
653     invoker.call("onStop").withNothing();
654 
655     Object nonConfigInstance = invoker.call("onRetainNonConfigurationInstance").withNothing();
656     setLastNonConfigurationInstance(nonConfigInstance);
657 
658     invoker.call("onDestroy").withNothing();
659     invoker.call("onCreate", Bundle.class).with(outState);
660     invoker.call("onStart").withNothing();
661     invoker.call("onRestoreInstanceState", Bundle.class).with(outState);
662     invoker.call("onResume").withNothing();
663   }
664 
665   @Implementation
startManagingCursor(Cursor c)666   protected void startManagingCursor(Cursor c) {
667     managedCursors.add(c);
668   }
669 
670   @Implementation
stopManagingCursor(Cursor c)671   protected void stopManagingCursor(Cursor c) {
672     managedCursors.remove(c);
673   }
674 
getManagedCursors()675   public List<Cursor> getManagedCursors() {
676     return managedCursors;
677   }
678 
679   @Implementation
setVolumeControlStream(int streamType)680   protected final void setVolumeControlStream(int streamType) {
681     this.streamType = streamType;
682   }
683 
684   @Implementation
getVolumeControlStream()685   protected final int getVolumeControlStream() {
686     return streamType;
687   }
688 
689   @Implementation(minSdk = M)
requestPermissions(String[] permissions, int requestCode)690   protected final void requestPermissions(String[] permissions, int requestCode) {
691     lastRequestedPermission = new PermissionsRequest(permissions, requestCode);
692   }
693 
694   /**
695    * Starts a lock task.
696    *
697    * <p>The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
698    * implementation has no effect.
699    */
700   @Implementation(minSdk = LOLLIPOP)
startLockTask()701   protected void startLockTask() {
702     Shadow.<ShadowActivityManager>extract(getActivityManager())
703         .setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_LOCKED);
704   }
705 
706   /**
707    * Stops a lock task.
708    *
709    * <p>The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
710    * implementation has no effect.
711    */
712   @Implementation(minSdk = LOLLIPOP)
stopLockTask()713   protected void stopLockTask() {
714     Shadow.<ShadowActivityManager>extract(getActivityManager())
715         .setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_NONE);
716   }
717 
718   /**
719    * Returns if the activity is in the lock task mode.
720    *
721    * @deprecated Use {@link ActivityManager#getLockTaskModeState} instead.
722    */
723   @Deprecated
isLockTask()724   public boolean isLockTask() {
725     return getActivityManager().isInLockTaskMode();
726   }
727 
getActivityManager()728   private ActivityManager getActivityManager() {
729     return (ActivityManager) realActivity.getSystemService(Context.ACTIVITY_SERVICE);
730   }
731 
732   /**
733    * Gets the last permission request submitted to this activity.
734    *
735    * @return The permission request details.
736    */
getLastRequestedPermission()737   public PermissionsRequest getLastRequestedPermission() {
738     return lastRequestedPermission;
739   }
740 
741   private final class ActivityInvoker {
742     private Method method;
743 
call(final String methodName, final Class... argumentClasses)744     public ActivityInvoker call(final String methodName, final Class... argumentClasses) {
745       try {
746         method = Activity.class.getDeclaredMethod(methodName, argumentClasses);
747         method.setAccessible(true);
748         return this;
749       } catch (NoSuchMethodException e) {
750         throw new RuntimeException(e);
751       }
752     }
753 
withNothing()754     public Object withNothing() {
755       return with();
756     }
757 
with(final Object... parameters)758     public Object with(final Object... parameters) {
759       try {
760         return method.invoke(realActivity, parameters);
761       } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
762         throw new RuntimeException(e);
763       }
764     }
765   }
766 
767   /** Class to hold a permissions request, including its request code. */
768   public static class PermissionsRequest {
769     public final int requestCode;
770     public final String[] requestedPermissions;
771 
PermissionsRequest(String[] requestedPermissions, int requestCode)772     public PermissionsRequest(String[] requestedPermissions, int requestCode) {
773       this.requestedPermissions = requestedPermissions;
774       this.requestCode = requestCode;
775     }
776   }
777 }
778