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