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