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