Home | History | Annotate | Download | only in shadows
      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 
     77   public void setApplication(Application application) {
     78     ReflectionHelpers.setField(realActivity, "mApplication", application);
     79   }
     80 
     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 
    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 
    257   public void setCallingActivity(ComponentName activityName) {
    258     callingActivity = activityName;
    259   }
    260 
    261   @Implementation
    262   protected ComponentName getCallingActivity() {
    263     return callingActivity;
    264   }
    265 
    266   @Implementation
    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 
    288   public int getDefaultKeymode() {
    289     return mDefaultKeyMode;
    290   }
    291 
    292   @Implementation
    293   protected final void setResult(int resultCode) {
    294     this.resultCode = resultCode;
    295   }
    296 
    297   @Implementation
    298   protected final void setResult(int resultCode, Intent data) {
    299     this.resultCode = resultCode;
    300     this.resultIntent = data;
    301   }
    302 
    303   @Implementation
    304   protected LayoutInflater getLayoutInflater() {
    305     return LayoutInflater.from(realActivity);
    306   }
    307 
    308   @Implementation
    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
    321   protected View findViewById(int id) {
    322     return getWindow().findViewById(id);
    323   }
    324 
    325   @Implementation
    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
    336   public void setParent(Activity parent) {
    337     this.parent = parent;
    338   }
    339 
    340   @Implementation
    341   protected void onBackPressed() {
    342     finish();
    343   }
    344 
    345   @Implementation
    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)
    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)
    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 
    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
    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
    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 
    399   public void setWindow(Window window) {
    400     ReflectionHelpers.setField(realActivity, "mWindow", window);
    401   }
    402 
    403   @Implementation
    404   protected void runOnUiThread(Runnable action) {
    405     ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
    406   }
    407 
    408   @Implementation
    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
    418   protected int getRequestedOrientation() {
    419     if (getParent() != null) {
    420       return getParent().getRequestedOrientation();
    421     } else {
    422       return this.requestedOrientation;
    423     }
    424   }
    425 
    426   @Implementation
    427   protected int getTaskId() {
    428     return 0;
    429   }
    430 
    431   /**
    432    * @return the {@code contentView} set by one of the {@code setContentView()} methods
    433    */
    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    */
    441   public int getResultCode() {
    442     return resultCode;
    443   }
    444 
    445   /**
    446    * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
    447    */
    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    */
    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    */
    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
    479   protected Object getLastNonConfigurationInstance() {
    480     return lastNonConfigurationInstance;
    481   }
    482 
    483   public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) {
    484     this.lastNonConfigurationInstance = lastNonConfigurationInstance;
    485   }
    486 
    487   /**
    488    * @param view View to focus.
    489    */
    490   public void setCurrentFocus(View view) {
    491     currentFocus = view;
    492   }
    493 
    494   @Implementation
    495   protected View getCurrentFocus() {
    496     return currentFocus;
    497   }
    498 
    499   public int getPendingTransitionEnterAnimationResourceId() {
    500     return pendingTransitionEnterAnimResId;
    501   }
    502 
    503   public int getPendingTransitionExitAnimationResourceId() {
    504     return pendingTransitionExitAnimResId;
    505   }
    506 
    507   @Implementation
    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    */
    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    */
    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. */
    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 
    550     public IntentForResult(Intent intent, int requestCode) {
    551       this.intent = intent;
    552       this.requestCode = requestCode;
    553       this.options = null;
    554     }
    555 
    556     public IntentForResult(Intent intent, int requestCode, Bundle options) {
    557       this.intent = intent;
    558       this.requestCode = requestCode;
    559       this.options = options;
    560     }
    561   }
    562 
    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
    572   protected final void showDialog(int id) {
    573     showDialog(id, null);
    574   }
    575 
    576   @Implementation
    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
    587   protected final void removeDialog(int id) {
    588     dialogForId.remove(id);
    589   }
    590 
    591   @Implementation
    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 
    615   public void setIsTaskRoot(boolean isRoot) {
    616     mIsTaskRoot = isRoot;
    617   }
    618 
    619   @Implementation
    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    */
    628   public Integer getLastShownDialogId() {
    629     return lastShownDialogId;
    630   }
    631 
    632   public boolean hasCancelledPendingTransitions() {
    633     return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0;
    634   }
    635 
    636   @Implementation
    637   protected void overridePendingTransition(int enterAnim, int exitAnim) {
    638     pendingTransitionEnterAnimResId = enterAnim;
    639     pendingTransitionExitAnimResId = exitAnim;
    640   }
    641 
    642   public Dialog getDialogById(int dialogId) {
    643     return dialogForId.get(dialogId);
    644   }
    645 
    646   @Implementation
    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
    666   protected void startManagingCursor(Cursor c) {
    667     managedCursors.add(c);
    668   }
    669 
    670   @Implementation
    671   protected void stopManagingCursor(Cursor c) {
    672     managedCursors.remove(c);
    673   }
    674 
    675   public List<Cursor> getManagedCursors() {
    676     return managedCursors;
    677   }
    678 
    679   @Implementation
    680   protected final void setVolumeControlStream(int streamType) {
    681     this.streamType = streamType;
    682   }
    683 
    684   @Implementation
    685   protected final int getVolumeControlStream() {
    686     return streamType;
    687   }
    688 
    689   @Implementation(minSdk = M)
    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)
    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)
    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
    724   public boolean isLockTask() {
    725     return getActivityManager().isInLockTaskMode();
    726   }
    727 
    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    */
    737   public PermissionsRequest getLastRequestedPermission() {
    738     return lastRequestedPermission;
    739   }
    740 
    741   private final class ActivityInvoker {
    742     private Method method;
    743 
    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 
    754     public Object withNothing() {
    755       return with();
    756     }
    757 
    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 
    772     public PermissionsRequest(String[] requestedPermissions, int requestCode) {
    773       this.requestedPermissions = requestedPermissions;
    774       this.requestCode = requestCode;
    775     }
    776   }
    777 }
    778