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