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