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