• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.M;
4 import static android.os.Build.VERSION_CODES.N;
5 import static android.os.Build.VERSION_CODES.O;
6 import static android.os.Build.VERSION_CODES.O_MR1;
7 import static android.os.Build.VERSION_CODES.Q;
8 import static android.os.Build.VERSION_CODES.S;
9 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
10 import static org.robolectric.util.reflector.Reflector.reflector;
11 
12 import android.annotation.AnimRes;
13 import android.annotation.ColorInt;
14 import android.annotation.RequiresApi;
15 import android.app.Activity;
16 import android.app.ActivityManager;
17 import android.app.ActivityOptions;
18 import android.app.ActivityThread;
19 import android.app.Application;
20 import android.app.Dialog;
21 import android.app.DirectAction;
22 import android.app.Instrumentation;
23 import android.app.LoadedApk;
24 import android.app.PendingIntent;
25 import android.app.PictureInPictureParams;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentSender;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.res.Configuration;
34 import android.database.Cursor;
35 import android.os.Binder;
36 import android.os.Build;
37 import android.os.Build.VERSION;
38 import android.os.Build.VERSION_CODES;
39 import android.os.Bundle;
40 import android.os.CancellationSignal;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Looper;
44 import android.os.Parcel;
45 import android.text.Selection;
46 import android.text.SpannableStringBuilder;
47 import android.util.SparseArray;
48 import android.view.Display;
49 import android.view.LayoutInflater;
50 import android.view.Menu;
51 import android.view.MenuInflater;
52 import android.view.View;
53 import android.view.ViewGroup;
54 import android.view.Window;
55 import com.android.internal.app.IVoiceInteractor;
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.function.Consumer;
61 import javax.annotation.Nullable;
62 import org.robolectric.RuntimeEnvironment;
63 import org.robolectric.android.controller.ActivityController;
64 import org.robolectric.annotation.HiddenApi;
65 import org.robolectric.annotation.Implementation;
66 import org.robolectric.annotation.Implements;
67 import org.robolectric.annotation.LooperMode;
68 import org.robolectric.annotation.RealObject;
69 import org.robolectric.fakes.RoboIntentSender;
70 import org.robolectric.fakes.RoboMenuItem;
71 import org.robolectric.fakes.RoboSplashScreen;
72 import org.robolectric.shadow.api.Shadow;
73 import org.robolectric.shadows.ShadowContextImpl._ContextImpl_;
74 import org.robolectric.shadows.ShadowInstrumentation.TargetAndRequestCode;
75 import org.robolectric.shadows.ShadowLoadedApk._LoadedApk_;
76 import org.robolectric.util.ReflectionHelpers;
77 import org.robolectric.util.reflector.ForType;
78 import org.robolectric.util.reflector.WithType;
79 
80 @SuppressWarnings("NewApi")
81 @Implements(value = Activity.class, looseSignatures = true)
82 public class ShadowActivity extends ShadowContextThemeWrapper {
83 
84   @RealObject protected Activity realActivity;
85 
86   private int resultCode;
87   private Intent resultIntent;
88   private Activity parent;
89   private int requestedOrientation = -1;
90   private View currentFocus;
91   private Integer lastShownDialogId = null;
92   private int pendingTransitionEnterAnimResId = -1;
93   private int pendingTransitionExitAnimResId = -1;
94   private SparseArray<OverriddenActivityTransition> overriddenActivityTransitions =
95       new SparseArray<>();
96   private Object lastNonConfigurationInstance;
97   private Map<Integer, Dialog> dialogForId = new HashMap<>();
98   private ArrayList<Cursor> managedCursors = new ArrayList<>();
99   private int mDefaultKeyMode = Activity.DEFAULT_KEYS_DISABLE;
100   private SpannableStringBuilder mDefaultKeySsb = null;
101   private int streamType = -1;
102   private boolean mIsTaskRoot = true;
103   private Menu optionsMenu;
104   private ComponentName callingActivity;
105   private PermissionsRequest lastRequestedPermission;
106   private ActivityController controller;
107   private boolean inMultiWindowMode = false;
108   private IntentSenderRequest lastIntentSenderRequest;
109   private boolean throwIntentSenderException;
110   private boolean hasReportedFullyDrawn = false;
111   private boolean isInPictureInPictureMode = false;
112   private Object splashScreen = null;
113   private boolean showWhenLocked = false;
114   private boolean turnScreenOn = false;
115 
setApplication(Application application)116   public void setApplication(Application application) {
117     reflector(_Activity_.class, realActivity).setApplication(application);
118   }
119 
callAttach(Intent intent)120   public void callAttach(Intent intent) {
121     callAttach(intent, /*activityOptions=*/ null, /*lastNonConfigurationInstances=*/ null);
122   }
123 
callAttach(Intent intent, @Nullable Bundle activityOptions)124   public void callAttach(Intent intent, @Nullable Bundle activityOptions) {
125     callAttach(
126         intent, /*activityOptions=*/ activityOptions, /*lastNonConfigurationInstances=*/ null);
127   }
128 
callAttach( Intent intent, @Nullable Bundle activityOptions, @Nullable @WithType("android.app.Activity$NonConfigurationInstances") Object lastNonConfigurationInstances)129   public void callAttach(
130       Intent intent,
131       @Nullable Bundle activityOptions,
132       @Nullable @WithType("android.app.Activity$NonConfigurationInstances")
133           Object lastNonConfigurationInstances) {
134     callAttach(
135         intent,
136         /* activityOptions= */ activityOptions,
137         /* lastNonConfigurationInstances= */ null,
138         /* overrideConfig= */ null);
139   }
140 
callAttach( Intent intent, @Nullable Bundle activityOptions, @Nullable @WithType("android.app.Activity$NonConfigurationInstances") Object lastNonConfigurationInstances, @Nullable Configuration overrideConfig)141   public void callAttach(
142       Intent intent,
143       @Nullable Bundle activityOptions,
144       @Nullable @WithType("android.app.Activity$NonConfigurationInstances")
145           Object lastNonConfigurationInstances,
146       @Nullable Configuration overrideConfig) {
147     Application application = RuntimeEnvironment.getApplication();
148     Context baseContext = application.getBaseContext();
149 
150     ComponentName componentName =
151         new ComponentName(application.getPackageName(), realActivity.getClass().getName());
152     ActivityInfo activityInfo;
153     PackageManager packageManager = application.getPackageManager();
154     shadowOf(packageManager).addActivityIfNotPresent(componentName);
155     try {
156       activityInfo = packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA);
157     } catch (NameNotFoundException e) {
158       throw new RuntimeException("Activity is not resolved even if we made sure it exists", e);
159     }
160     Binder token = new Binder();
161 
162     CharSequence activityTitle = activityInfo.loadLabel(baseContext.getPackageManager());
163 
164     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
165     Instrumentation instrumentation = activityThread.getInstrumentation();
166 
167     Context activityContext;
168     int displayId =
169         activityOptions != null
170             ? ActivityOptions.fromBundle(activityOptions).getLaunchDisplayId()
171             : Display.DEFAULT_DISPLAY;
172     // There's no particular reason to only do this above O, however the createActivityContext
173     // method signature changed between versions so just for convenience only the latest version is
174     // plumbed through, older versions will use the previous robolectric behavior of sharing
175     // activity and application ContextImpl objects.
176     // TODO(paulsowden): This should be enabled always but many service shadows are storing instance
177     //  state that should be represented globally, we'll have to update these one by one to use
178     //  static (i.e. global) state instead of instance state. For now enable only when the display
179     //  is requested to a non-default display which requires a separate context to function
180     //  properly.
181     if ((Boolean.getBoolean("robolectric.createActivityContexts")
182             || (displayId != Display.DEFAULT_DISPLAY && displayId != Display.INVALID_DISPLAY))
183         && RuntimeEnvironment.getApiLevel() >= O) {
184       LoadedApk loadedApk =
185           activityThread.getPackageInfo(
186               ShadowActivityThread.getApplicationInfo(), null, Context.CONTEXT_INCLUDE_CODE);
187       _LoadedApk_ loadedApkReflector = reflector(_LoadedApk_.class, loadedApk);
188       loadedApkReflector.setResources(application.getResources());
189       loadedApkReflector.setApplication(application);
190       activityContext =
191           reflector(_ContextImpl_.class)
192               .createActivityContext(
193                   activityThread, loadedApk, activityInfo, token, displayId, overrideConfig);
194       reflector(_ContextImpl_.class, activityContext).setOuterContext(realActivity);
195       // This is not what the SDK does but for backwards compatibility with previous versions of
196       // robolectric, which did not use a separate activity context, move the theme from the
197       // application context (previously tests would configure the theme on the application context
198       // with the expectation that it modify the activity).
199       if (baseContext.getThemeResId() != 0) {
200         activityContext.setTheme(baseContext.getThemeResId());
201       }
202     } else {
203       activityContext = baseContext;
204     }
205 
206     reflector(_Activity_.class, realActivity)
207         .callAttach(
208             realActivity,
209             activityContext,
210             activityThread,
211             instrumentation,
212             application,
213             intent,
214             activityInfo,
215             token,
216             activityTitle,
217             lastNonConfigurationInstances);
218 
219     int theme = activityInfo.getThemeResource();
220     if (theme != 0) {
221       realActivity.setTheme(theme);
222     }
223   }
224 
225   /**
226    * Sets the calling activity that will be reflected in {@link Activity#getCallingActivity} and
227    * {@link Activity#getCallingPackage}.
228    */
setCallingActivity(@ullable ComponentName activityName)229   public void setCallingActivity(@Nullable ComponentName activityName) {
230     callingActivity = activityName;
231   }
232 
233   @Implementation
getCallingActivity()234   protected ComponentName getCallingActivity() {
235     return callingActivity;
236   }
237 
238   /**
239    * Sets the calling package that will be reflected in {@link Activity#getCallingActivity} and
240    * {@link Activity#getCallingPackage}.
241    *
242    * <p>Activity name defaults to some default value.
243    */
setCallingPackage(@ullable String packageName)244   public void setCallingPackage(@Nullable String packageName) {
245     if (callingActivity != null && callingActivity.getPackageName().equals(packageName)) {
246       // preserve the calling activity as it was, so non-conflicting setCallingActivity followed by
247       // setCallingPackage will not erase previously set information.
248       return;
249     }
250     callingActivity =
251         packageName != null ? new ComponentName(packageName, "unknown.Activity") : null;
252   }
253 
254   @Implementation
getCallingPackage()255   protected String getCallingPackage() {
256     return callingActivity != null ? callingActivity.getPackageName() : null;
257   }
258 
259   @Implementation
setDefaultKeyMode(int keyMode)260   protected void setDefaultKeyMode(int keyMode) {
261     mDefaultKeyMode = keyMode;
262 
263     // Some modes use a SpannableStringBuilder to track & dispatch input events
264     // This list must remain in sync with the switch in onKeyDown()
265     switch (mDefaultKeyMode) {
266       case Activity.DEFAULT_KEYS_DISABLE:
267       case Activity.DEFAULT_KEYS_SHORTCUT:
268         mDefaultKeySsb = null; // not used in these modes
269         break;
270       case Activity.DEFAULT_KEYS_DIALER:
271       case Activity.DEFAULT_KEYS_SEARCH_LOCAL:
272       case Activity.DEFAULT_KEYS_SEARCH_GLOBAL:
273         mDefaultKeySsb = new SpannableStringBuilder();
274         Selection.setSelection(mDefaultKeySsb, 0);
275         break;
276       default:
277         throw new IllegalArgumentException();
278     }
279   }
280 
getDefaultKeymode()281   public int getDefaultKeymode() {
282     return mDefaultKeyMode;
283   }
284 
285   @Implementation(minSdk = O_MR1)
setShowWhenLocked(boolean showWhenLocked)286   protected void setShowWhenLocked(boolean showWhenLocked) {
287     this.showWhenLocked = showWhenLocked;
288   }
289 
290   @RequiresApi(api = O_MR1)
getShowWhenLocked()291   public boolean getShowWhenLocked() {
292     return showWhenLocked;
293   }
294 
295   @Implementation(minSdk = O_MR1)
setTurnScreenOn(boolean turnScreenOn)296   protected void setTurnScreenOn(boolean turnScreenOn) {
297     this.turnScreenOn = turnScreenOn;
298   }
299 
300   @RequiresApi(api = O_MR1)
getTurnScreenOn()301   public boolean getTurnScreenOn() {
302     return turnScreenOn;
303   }
304 
305   @Implementation
setResult(int resultCode)306   protected void setResult(int resultCode) {
307     this.resultCode = resultCode;
308   }
309 
310   @Implementation
setResult(int resultCode, Intent data)311   protected void setResult(int resultCode, Intent data) {
312     this.resultCode = resultCode;
313     this.resultIntent = data;
314   }
315 
316   @Implementation
getLayoutInflater()317   protected LayoutInflater getLayoutInflater() {
318     return LayoutInflater.from(realActivity);
319   }
320 
321   @Implementation
getMenuInflater()322   protected MenuInflater getMenuInflater() {
323     return new MenuInflater(realActivity);
324   }
325 
326   /**
327    * Checks to ensure that the{@code contentView} has been set
328    *
329    * @param id ID of the view to find
330    * @return the view
331    * @throws RuntimeException if the {@code contentView} has not been called first
332    */
333   @Implementation
findViewById(int id)334   protected View findViewById(int id) {
335     return getWindow().findViewById(id);
336   }
337 
338   @Implementation
getParent()339   protected Activity getParent() {
340     return parent;
341   }
342 
343   /**
344    * Allow setting of Parent fragmentActivity (for unit testing purposes only)
345    *
346    * @param parent Parent fragmentActivity to set on this fragmentActivity
347    */
348   @HiddenApi
349   @Implementation
setParent(Activity parent)350   public void setParent(Activity parent) {
351     this.parent = parent;
352   }
353 
354   @Implementation
onBackPressed()355   protected void onBackPressed() {
356     finish();
357   }
358 
359   @Implementation
finish()360   protected void finish() {
361     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
362     reflector(_Activity_.class, realActivity).setFinished(true);
363   }
364 
365   @Implementation
finishAndRemoveTask()366   protected void finishAndRemoveTask() {
367     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
368     reflector(_Activity_.class, realActivity).setFinished(true);
369   }
370 
371   @Implementation
finishAffinity()372   protected void finishAffinity() {
373     // Sets the mFinished field in the real activity so NoDisplay activities can be tested.
374     reflector(_Activity_.class, realActivity).setFinished(true);
375   }
376 
resetIsFinishing()377   public void resetIsFinishing() {
378     reflector(_Activity_.class, realActivity).setFinished(false);
379   }
380 
381   /**
382    * Returns whether {@link #finish()} was called.
383    *
384    * <p>Note: this method seems redundant, but removing it will cause problems for Mockito spies of
385    * Activities that call {@link Activity#finish()} followed by {@link Activity#isFinishing()}. This
386    * is because `finish` modifies the members of {@link ShadowActivity#realActivity}, so
387    * `isFinishing` should refer to those same members.
388    */
389   @Implementation
isFinishing()390   protected boolean isFinishing() {
391     return reflector(DirectActivityReflector.class, realActivity).isFinishing();
392   }
393 
394   /**
395    * Constructs a new Window (a {@link com.android.internal.policy.impl.PhoneWindow}) if no window
396    * has previously been set.
397    *
398    * @return the window associated with this Activity
399    */
400   @Implementation
getWindow()401   protected Window getWindow() {
402     Window window = reflector(DirectActivityReflector.class, realActivity).getWindow();
403 
404     if (window == null) {
405       try {
406         window = ShadowWindow.create(realActivity);
407         setWindow(window);
408       } catch (Exception e) {
409         throw new RuntimeException("Window creation failed!", e);
410       }
411     }
412 
413     return window;
414   }
415 
416   /**
417    * @return fake SplashScreen
418    */
419   @Implementation(minSdk = S)
getSplashScreen()420   protected synchronized Object getSplashScreen() {
421     if (splashScreen == null) {
422       splashScreen = new RoboSplashScreen();
423     }
424     return splashScreen;
425   }
426 
setWindow(Window window)427   public void setWindow(Window window) {
428     reflector(_Activity_.class, realActivity).setWindow(window);
429   }
430 
431   @Implementation
runOnUiThread(Runnable action)432   protected void runOnUiThread(Runnable action) {
433     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
434       ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
435     } else {
436       reflector(DirectActivityReflector.class, realActivity).runOnUiThread(action);
437     }
438   }
439 
440   @Implementation
setRequestedOrientation(int requestedOrientation)441   protected void setRequestedOrientation(int requestedOrientation) {
442     if (getParent() != null) {
443       getParent().setRequestedOrientation(requestedOrientation);
444     } else {
445       this.requestedOrientation = requestedOrientation;
446     }
447   }
448 
449   @Implementation
getRequestedOrientation()450   protected int getRequestedOrientation() {
451     if (getParent() != null) {
452       return getParent().getRequestedOrientation();
453     } else {
454       return this.requestedOrientation;
455     }
456   }
457 
458   @Implementation
getTaskId()459   protected int getTaskId() {
460     return 0;
461   }
462 
463   @Implementation
startIntentSenderForResult( IntentSender intentSender, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)464   public void startIntentSenderForResult(
465       IntentSender intentSender,
466       int requestCode,
467       @Nullable Intent fillInIntent,
468       int flagsMask,
469       int flagsValues,
470       int extraFlags,
471       Bundle options)
472       throws IntentSender.SendIntentException {
473     if (throwIntentSenderException) {
474       throw new IntentSender.SendIntentException("PendingIntent was canceled");
475     }
476     lastIntentSenderRequest =
477         new IntentSenderRequest(
478             intentSender, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
479     lastIntentSenderRequest.send();
480   }
481 
482   @Implementation
reportFullyDrawn()483   protected void reportFullyDrawn() {
484     hasReportedFullyDrawn = true;
485   }
486 
487   /**
488    * @return whether {@code ReportFullyDrawn()} methods has been called.
489    */
getReportFullyDrawn()490   public boolean getReportFullyDrawn() {
491     return hasReportedFullyDrawn;
492   }
493 
494   /**
495    * @return the {@code contentView} set by one of the {@code setContentView()} methods
496    */
getContentView()497   public View getContentView() {
498     return ((ViewGroup) getWindow().findViewById(android.R.id.content)).getChildAt(0);
499   }
500 
501   /**
502    * @return the {@code resultCode} set by one of the {@code setResult()} methods
503    */
getResultCode()504   public int getResultCode() {
505     return resultCode;
506   }
507 
508   /**
509    * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
510    */
getResultIntent()511   public Intent getResultIntent() {
512     return resultIntent;
513   }
514 
515   /**
516    * Consumes and returns the next {@code Intent} on the started activities for results stack.
517    *
518    * @return the next started {@code Intent} for an activity, wrapped in an {@link
519    *     ShadowActivity.IntentForResult} object
520    */
521   @Override
getNextStartedActivityForResult()522   public IntentForResult getNextStartedActivityForResult() {
523     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
524     ShadowInstrumentation shadowInstrumentation =
525         Shadow.extract(activityThread.getInstrumentation());
526     return shadowInstrumentation.getNextStartedActivityForResult();
527   }
528 
529   /**
530    * Returns the most recent {@code Intent} started by {@link
531    * Activity#startActivityForResult(Intent, int)} without consuming it.
532    *
533    * @return the most recently started {@code Intent}, wrapped in an {@link
534    *     ShadowActivity.IntentForResult} object
535    */
536   @Override
peekNextStartedActivityForResult()537   public IntentForResult peekNextStartedActivityForResult() {
538     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
539     ShadowInstrumentation shadowInstrumentation =
540         Shadow.extract(activityThread.getInstrumentation());
541     return shadowInstrumentation.peekNextStartedActivityForResult();
542   }
543 
544   @Implementation
getLastNonConfigurationInstance()545   protected Object getLastNonConfigurationInstance() {
546     if (lastNonConfigurationInstance != null) {
547       return lastNonConfigurationInstance;
548     }
549     return reflector(DirectActivityReflector.class, realActivity).getLastNonConfigurationInstance();
550   }
551 
552   /**
553    * @deprecated use {@link ActivityController#recreate()}.
554    */
555   @Deprecated
setLastNonConfigurationInstance(Object lastNonConfigurationInstance)556   public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) {
557     this.lastNonConfigurationInstance = lastNonConfigurationInstance;
558   }
559 
560   /**
561    * @param view View to focus.
562    */
setCurrentFocus(View view)563   public void setCurrentFocus(View view) {
564     currentFocus = view;
565   }
566 
567   @Implementation
getCurrentFocus()568   protected View getCurrentFocus() {
569     return currentFocus;
570   }
571 
getPendingTransitionEnterAnimationResourceId()572   public int getPendingTransitionEnterAnimationResourceId() {
573     return pendingTransitionEnterAnimResId;
574   }
575 
getPendingTransitionExitAnimationResourceId()576   public int getPendingTransitionExitAnimationResourceId() {
577     return pendingTransitionExitAnimResId;
578   }
579 
580   /**
581    * Get the overridden {@link Activity} transition, set by {@link
582    * Activity#overrideActivityTransition}.
583    *
584    * @param overrideType Use {@link Activity#OVERRIDE_TRANSITION_OPEN} to get the overridden
585    *     activity transition animation details when starting/entering an activity. Use {@link
586    *     Activity#OVERRIDE_TRANSITION_CLOSE} to get the overridden activity transition animation
587    *     details when finishing/closing an activity.
588    * @return overridden activity transition details after calling {@link
589    *     Activity#overrideActivityTransition(int, int, int, int)} or null if was not overridden.
590    * @see #clearOverrideActivityTransition(int)
591    */
592   @Nullable
593   @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
getOverriddenActivityTransition(int overrideType)594   public OverriddenActivityTransition getOverriddenActivityTransition(int overrideType) {
595     return overriddenActivityTransitions.get(overrideType, null);
596   }
597 
598   @Implementation
onCreateOptionsMenu(Menu menu)599   protected boolean onCreateOptionsMenu(Menu menu) {
600     optionsMenu = menu;
601     return reflector(DirectActivityReflector.class, realActivity).onCreateOptionsMenu(menu);
602   }
603 
604   /**
605    * Return the options menu.
606    *
607    * @return Options menu.
608    */
getOptionsMenu()609   public Menu getOptionsMenu() {
610     return optionsMenu;
611   }
612 
613   /**
614    * Perform a click on a menu item.
615    *
616    * @param menuItemResId Menu item resource ID.
617    * @return True if the click was handled, false otherwise.
618    */
clickMenuItem(int menuItemResId)619   public boolean clickMenuItem(int menuItemResId) {
620     final RoboMenuItem item = new RoboMenuItem(menuItemResId);
621     return realActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
622   }
623 
624   @Deprecated
callOnActivityResult(int requestCode, int resultCode, Intent resultData)625   public void callOnActivityResult(int requestCode, int resultCode, Intent resultData) {
626     reflector(_Activity_.class, realActivity).onActivityResult(requestCode, resultCode, resultData);
627   }
628 
629   /** For internal use only. Not for public use. */
internalCallDispatchActivityResult( String who, int requestCode, int resultCode, Intent data)630   public void internalCallDispatchActivityResult(
631       String who, int requestCode, int resultCode, Intent data) {
632     if (VERSION.SDK_INT >= VERSION_CODES.P) {
633       reflector(_Activity_.class, realActivity)
634           .dispatchActivityResult(who, requestCode, resultCode, data, "ACTIVITY_RESULT");
635     } else {
636       reflector(_Activity_.class, realActivity)
637           .dispatchActivityResult(who, requestCode, resultCode, data);
638     }
639   }
640 
641   /** For internal use only. Not for public use. */
attachController(ActivityController controller)642   public <T extends Activity> void attachController(ActivityController controller) {
643     this.controller = controller;
644   }
645 
646   /** Sets if startIntentSenderForRequestCode will throw an IntentSender.SendIntentException. */
setThrowIntentSenderException(boolean throwIntentSenderException)647   public void setThrowIntentSenderException(boolean throwIntentSenderException) {
648     this.throwIntentSenderException = throwIntentSenderException;
649   }
650 
651   /**
652    * Container object to hold an Intent, together with the requestCode used in a call to {@code
653    * Activity.startActivityForResult(Intent, int)}
654    */
655   public static class IntentForResult {
656     public Intent intent;
657     public int requestCode;
658     public Bundle options;
659 
IntentForResult(Intent intent, int requestCode)660     public IntentForResult(Intent intent, int requestCode) {
661       this.intent = intent;
662       this.requestCode = requestCode;
663       this.options = null;
664     }
665 
IntentForResult(Intent intent, int requestCode, Bundle options)666     public IntentForResult(Intent intent, int requestCode, Bundle options) {
667       this.intent = intent;
668       this.requestCode = requestCode;
669       this.options = options;
670     }
671 
672     @Override
toString()673     public String toString() {
674       return super.toString()
675           + "{intent="
676           + intent
677           + ", requestCode="
678           + requestCode
679           + ", options="
680           + options
681           + '}';
682     }
683   }
684 
receiveResult(Intent requestIntent, int resultCode, Intent resultIntent)685   public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) {
686     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
687     ShadowInstrumentation shadowInstrumentation =
688         Shadow.extract(activityThread.getInstrumentation());
689     TargetAndRequestCode targetAndRequestCode =
690         shadowInstrumentation.getTargetAndRequestCodeForIntent(requestIntent);
691 
692     internalCallDispatchActivityResult(
693         targetAndRequestCode.target, targetAndRequestCode.requestCode, resultCode, resultIntent);
694   }
695 
696   @Implementation
showDialog(int id)697   protected void showDialog(int id) {
698     showDialog(id, null);
699   }
700 
701   @Implementation
showDialog(int id, Bundle bundle)702   protected boolean showDialog(int id, Bundle bundle) {
703     this.lastShownDialogId = id;
704     Dialog dialog = dialogForId.get(id);
705 
706     if (dialog == null) {
707       dialog = reflector(_Activity_.class, realActivity).onCreateDialog(id);
708       if (dialog == null) {
709         return false;
710       }
711       if (bundle == null) {
712         reflector(_Activity_.class, realActivity).onPrepareDialog(id, dialog);
713       } else {
714         reflector(_Activity_.class, realActivity).onPrepareDialog(id, dialog, bundle);
715       }
716 
717       dialogForId.put(id, dialog);
718     }
719 
720     dialog.show();
721     return true;
722   }
723 
724   @Implementation
dismissDialog(int id)725   protected void dismissDialog(int id) {
726     final Dialog dialog = dialogForId.get(id);
727     if (dialog == null) {
728       throw new IllegalArgumentException();
729     }
730 
731     dialog.dismiss();
732   }
733 
734   @Implementation
removeDialog(int id)735   protected void removeDialog(int id) {
736     dialogForId.remove(id);
737   }
738 
setIsTaskRoot(boolean isRoot)739   public void setIsTaskRoot(boolean isRoot) {
740     mIsTaskRoot = isRoot;
741   }
742 
743   @Implementation
isTaskRoot()744   protected boolean isTaskRoot() {
745     return mIsTaskRoot;
746   }
747 
748   /**
749    * @return the dialog resource id passed into {@code Activity.showDialog(int, Bundle)} or {@code
750    *     Activity.showDialog(int)}
751    */
getLastShownDialogId()752   public Integer getLastShownDialogId() {
753     return lastShownDialogId;
754   }
755 
hasCancelledPendingTransitions()756   public boolean hasCancelledPendingTransitions() {
757     return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0;
758   }
759 
760   @Implementation
overridePendingTransition(int enterAnim, int exitAnim)761   protected void overridePendingTransition(int enterAnim, int exitAnim) {
762     pendingTransitionEnterAnimResId = enterAnim;
763     pendingTransitionExitAnimResId = exitAnim;
764   }
765 
766   @Implementation(minSdk = UPSIDE_DOWN_CAKE)
overrideActivityTransition( int overrideType, @AnimRes int enterAnim, @AnimRes int exitAnim, @ColorInt int backgroundColor)767   protected void overrideActivityTransition(
768       int overrideType,
769       @AnimRes int enterAnim,
770       @AnimRes int exitAnim,
771       @ColorInt int backgroundColor) {
772     overriddenActivityTransitions.put(
773         overrideType, new OverriddenActivityTransition(enterAnim, exitAnim, backgroundColor));
774 
775     reflector(DirectActivityReflector.class, realActivity)
776         .overrideActivityTransition(overrideType, enterAnim, exitAnim, backgroundColor);
777   }
778 
779   @Implementation(minSdk = UPSIDE_DOWN_CAKE)
clearOverrideActivityTransition(int overrideType)780   protected void clearOverrideActivityTransition(int overrideType) {
781     overriddenActivityTransitions.remove(overrideType);
782 
783     reflector(DirectActivityReflector.class, realActivity)
784         .clearOverrideActivityTransition(overrideType);
785   }
786 
getDialogById(int dialogId)787   public Dialog getDialogById(int dialogId) {
788     return dialogForId.get(dialogId);
789   }
790 
791   // TODO(hoisie): consider moving this to ActivityController#makeActivityEligibleForGc
792   @Implementation
onDestroy()793   protected void onDestroy() {
794     reflector(DirectActivityReflector.class, realActivity).onDestroy();
795     ShadowActivityThread activityThread = Shadow.extract(RuntimeEnvironment.getActivityThread());
796     IBinder token = reflector(_Activity_.class, realActivity).getToken();
797     activityThread.removeActivity(token);
798   }
799 
800   @Implementation
recreate()801   protected void recreate() {
802     if (controller != null) {
803       // Post the call to recreate to simulate ActivityThread behavior.
804       new Handler(Looper.getMainLooper()).post(controller::recreate);
805     } else {
806       throw new IllegalStateException(
807           "Cannot use an Activity that is not managed by an ActivityController");
808     }
809   }
810 
811   @Implementation
startManagingCursor(Cursor c)812   protected void startManagingCursor(Cursor c) {
813     managedCursors.add(c);
814   }
815 
816   @Implementation
stopManagingCursor(Cursor c)817   protected void stopManagingCursor(Cursor c) {
818     managedCursors.remove(c);
819   }
820 
getManagedCursors()821   public List<Cursor> getManagedCursors() {
822     return managedCursors;
823   }
824 
825   @Implementation
setVolumeControlStream(int streamType)826   protected void setVolumeControlStream(int streamType) {
827     this.streamType = streamType;
828   }
829 
830   @Implementation
getVolumeControlStream()831   protected int getVolumeControlStream() {
832     return streamType;
833   }
834 
835   @Implementation(minSdk = M)
requestPermissions(String[] permissions, int requestCode)836   protected void requestPermissions(String[] permissions, int requestCode) {
837     lastRequestedPermission = new PermissionsRequest(permissions, requestCode);
838     reflector(DirectActivityReflector.class, realActivity)
839         .requestPermissions(permissions, requestCode);
840   }
841 
842   /**
843    * Starts a lock task.
844    *
845    * <p>The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
846    * implementation has no effect.
847    */
848   @Implementation
startLockTask()849   protected void startLockTask() {
850     Shadow.<ShadowActivityManager>extract(getActivityManager())
851         .setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_LOCKED);
852   }
853 
854   /**
855    * Stops a lock task.
856    *
857    * <p>The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
858    * implementation has no effect.
859    */
860   @Implementation
stopLockTask()861   protected void stopLockTask() {
862     Shadow.<ShadowActivityManager>extract(getActivityManager())
863         .setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_NONE);
864   }
865 
866   /**
867    * Returns if the activity is in the lock task mode.
868    *
869    * @deprecated Use {@link ActivityManager#getLockTaskModeState} instead.
870    */
871   @Deprecated
isLockTask()872   public boolean isLockTask() {
873     return getActivityManager().isInLockTaskMode();
874   }
875 
getActivityManager()876   private ActivityManager getActivityManager() {
877     return (ActivityManager) realActivity.getSystemService(Context.ACTIVITY_SERVICE);
878   }
879 
880   /** Changes state of {@link #isInMultiWindowMode} method. */
setInMultiWindowMode(boolean value)881   public void setInMultiWindowMode(boolean value) {
882     inMultiWindowMode = value;
883   }
884 
885   @Implementation(minSdk = N)
isInMultiWindowMode()886   protected boolean isInMultiWindowMode() {
887     return inMultiWindowMode;
888   }
889 
890   @Implementation(minSdk = N)
isInPictureInPictureMode()891   protected boolean isInPictureInPictureMode() {
892     return isInPictureInPictureMode;
893   }
894 
895   @Implementation(minSdk = N)
enterPictureInPictureMode()896   protected void enterPictureInPictureMode() {
897     isInPictureInPictureMode = true;
898   }
899 
900   @Implementation(minSdk = O)
enterPictureInPictureMode(PictureInPictureParams params)901   protected boolean enterPictureInPictureMode(PictureInPictureParams params) {
902     isInPictureInPictureMode = true;
903     return true;
904   }
905 
906   @Implementation
moveTaskToBack(boolean nonRoot)907   protected boolean moveTaskToBack(boolean nonRoot) {
908     isInPictureInPictureMode = false;
909     return true;
910   }
911 
912   /**
913    * Gets the last startIntentSenderForResult request made to this activity.
914    *
915    * @return The IntentSender request details.
916    */
getLastIntentSenderRequest()917   public IntentSenderRequest getLastIntentSenderRequest() {
918     return lastIntentSenderRequest;
919   }
920 
921   /**
922    * Gets the last permission request submitted to this activity.
923    *
924    * @return The permission request details.
925    */
getLastRequestedPermission()926   public PermissionsRequest getLastRequestedPermission() {
927     return lastRequestedPermission;
928   }
929 
930   /**
931    * Initializes the associated Activity with an {@link android.app.VoiceInteractor} instance.
932    * Subsequent {@link android.app.Activity#getVoiceInteractor()} calls on the associated activity
933    * will return a {@link android.app.VoiceInteractor} instance
934    */
initializeVoiceInteractor()935   public void initializeVoiceInteractor() {
936     if (RuntimeEnvironment.getApiLevel() < N) {
937       throw new IllegalStateException("initializeVoiceInteractor requires API " + N);
938     }
939     reflector(_Activity_.class, realActivity)
940         .setVoiceInteractor(ReflectionHelpers.createDeepProxy(IVoiceInteractor.class));
941   }
942 
943   /**
944    * Calls Activity#onGetDirectActions with the given parameters. This method also simulates the
945    * Parcel serialization/deserialization which occurs when assistant requests DirectAction.
946    */
callOnGetDirectActions( CancellationSignal cancellationSignal, Consumer<List<DirectAction>> callback)947   public void callOnGetDirectActions(
948       CancellationSignal cancellationSignal, Consumer<List<DirectAction>> callback) {
949     if (RuntimeEnvironment.getApiLevel() < Q) {
950       throw new IllegalStateException("callOnGetDirectActions requires API " + Q);
951     }
952     realActivity.onGetDirectActions(
953         cancellationSignal,
954         directActions -> {
955           Parcel parcel = Parcel.obtain();
956           parcel.writeParcelableList(directActions, 0);
957           parcel.setDataPosition(0);
958           List<DirectAction> output = new ArrayList<>();
959           parcel.readParcelableList(output, DirectAction.class.getClassLoader());
960           callback.accept(output);
961         });
962   }
963 
964   /**
965    * Class to hold overridden activity transition details after calling {@link
966    * Activity#overrideActivityTransition(int, int, int, int)}
967    */
968   public static class OverriddenActivityTransition {
969     @AnimRes public final int enterAnim;
970     @AnimRes public final int exitAnim;
971     @ColorInt public final int backgroundColor;
972 
OverriddenActivityTransition(int enterAnim, int exitAnim, int backgroundColor)973     public OverriddenActivityTransition(int enterAnim, int exitAnim, int backgroundColor) {
974       this.enterAnim = enterAnim;
975       this.exitAnim = exitAnim;
976       this.backgroundColor = backgroundColor;
977     }
978   }
979 
980   /** Class to hold a permissions request, including its request code. */
981   public static class PermissionsRequest {
982     public final int requestCode;
983     public final String[] requestedPermissions;
984 
PermissionsRequest(String[] requestedPermissions, int requestCode)985     public PermissionsRequest(String[] requestedPermissions, int requestCode) {
986       this.requestedPermissions = requestedPermissions;
987       this.requestCode = requestCode;
988     }
989   }
990 
991   /** Class to holds details of a startIntentSenderForResult request. */
992   public static class IntentSenderRequest {
993     public final IntentSender intentSender;
994     public final int requestCode;
995     @Nullable public final Intent fillInIntent;
996     public final int flagsMask;
997     public final int flagsValues;
998     public final int extraFlags;
999     public final Bundle options;
1000 
IntentSenderRequest( IntentSender intentSender, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)1001     public IntentSenderRequest(
1002         IntentSender intentSender,
1003         int requestCode,
1004         @Nullable Intent fillInIntent,
1005         int flagsMask,
1006         int flagsValues,
1007         int extraFlags,
1008         Bundle options) {
1009       this.intentSender = intentSender;
1010       this.requestCode = requestCode;
1011       this.fillInIntent = fillInIntent;
1012       this.flagsMask = flagsMask;
1013       this.flagsValues = flagsValues;
1014       this.extraFlags = extraFlags;
1015       this.options = options;
1016     }
1017 
send()1018     public void send() {
1019       if (intentSender instanceof RoboIntentSender) {
1020         try {
1021           Shadow.<ShadowPendingIntent>extract(((RoboIntentSender) intentSender).getPendingIntent())
1022               .send(
1023                   RuntimeEnvironment.getApplication(),
1024                   0,
1025                   null,
1026                   null,
1027                   null,
1028                   null,
1029                   null,
1030                   requestCode);
1031         } catch (PendingIntent.CanceledException e) {
1032           throw new RuntimeException(e);
1033         }
1034       }
1035     }
1036   }
1037 
shadowOf(PackageManager packageManager)1038   private ShadowPackageManager shadowOf(PackageManager packageManager) {
1039     return Shadow.extract(packageManager);
1040   }
1041 
1042   @ForType(value = Activity.class, direct = true)
1043   interface DirectActivityReflector {
1044 
runOnUiThread(Runnable action)1045     void runOnUiThread(Runnable action);
1046 
onDestroy()1047     void onDestroy();
1048 
isFinishing()1049     boolean isFinishing();
1050 
overrideActivityTransition( int overrideType, int enterAnim, int exitAnim, int backgroundColor)1051     void overrideActivityTransition(
1052         int overrideType, int enterAnim, int exitAnim, int backgroundColor);
1053 
clearOverrideActivityTransition(int overrideType)1054     void clearOverrideActivityTransition(int overrideType);
1055 
getWindow()1056     Window getWindow();
1057 
getLastNonConfigurationInstance()1058     Object getLastNonConfigurationInstance();
1059 
onCreateOptionsMenu(Menu menu)1060     boolean onCreateOptionsMenu(Menu menu);
1061 
requestPermissions(String[] permissions, int requestCode)1062     void requestPermissions(String[] permissions, int requestCode);
1063   }
1064 }
1065