org.robolectric.shadows.ShadowActivity Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
import android.app.Dialog;
import android.app.DirectAction;
import android.app.Instrumentation;
import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Binder;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.RequiresApi;
import com.android.internal.app.IVoiceInteractor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.fakes.RoboIntentSender;
import org.robolectric.fakes.RoboMenuItem;
import org.robolectric.fakes.RoboSplashScreen;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextImpl._ContextImpl_;
import org.robolectric.shadows.ShadowInstrumentation.TargetAndRequestCode;
import org.robolectric.shadows.ShadowLoadedApk._LoadedApk_;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;
@SuppressWarnings("NewApi")
@Implements(value = Activity.class, looseSignatures = true)
public class ShadowActivity extends ShadowContextThemeWrapper {
@RealObject protected Activity realActivity;
private int resultCode;
private Intent resultIntent;
private Activity parent;
private int requestedOrientation = -1;
private View currentFocus;
private Integer lastShownDialogId = null;
private int pendingTransitionEnterAnimResId = -1;
private int pendingTransitionExitAnimResId = -1;
private Object lastNonConfigurationInstance;
private Map dialogForId = new HashMap<>();
private ArrayList managedCursors = new ArrayList<>();
private int mDefaultKeyMode = Activity.DEFAULT_KEYS_DISABLE;
private SpannableStringBuilder mDefaultKeySsb = null;
private int streamType = -1;
private boolean mIsTaskRoot = true;
private Menu optionsMenu;
private ComponentName callingActivity;
private PermissionsRequest lastRequestedPermission;
private ActivityController controller;
private boolean inMultiWindowMode = false;
private IntentSenderRequest lastIntentSenderRequest;
private boolean throwIntentSenderException;
private boolean hasReportedFullyDrawn = false;
private boolean isInPictureInPictureMode = false;
private Object splashScreen = null;
private boolean showWhenLocked = false;
private boolean turnScreenOn = false;
public void setApplication(Application application) {
reflector(_Activity_.class, realActivity).setApplication(application);
}
public void callAttach(Intent intent) {
callAttach(intent, /*activityOptions=*/ null, /*lastNonConfigurationInstances=*/ null);
}
public void callAttach(Intent intent, @Nullable Bundle activityOptions) {
callAttach(
intent, /*activityOptions=*/ activityOptions, /*lastNonConfigurationInstances=*/ null);
}
public void callAttach(
Intent intent,
@Nullable Bundle activityOptions,
@Nullable @WithType("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances) {
callAttach(
intent,
/* activityOptions= */ activityOptions,
/* lastNonConfigurationInstances= */ null,
/* overrideConfig= */ null);
}
public void callAttach(
Intent intent,
@Nullable Bundle activityOptions,
@Nullable @WithType("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances,
@Nullable Configuration overrideConfig) {
Application application = RuntimeEnvironment.getApplication();
Context baseContext = application.getBaseContext();
ComponentName componentName =
new ComponentName(application.getPackageName(), realActivity.getClass().getName());
ActivityInfo activityInfo;
PackageManager packageManager = application.getPackageManager();
shadowOf(packageManager).addActivityIfNotPresent(componentName);
try {
activityInfo = packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
throw new RuntimeException("Activity is not resolved even if we made sure it exists", e);
}
Binder token = new Binder();
CharSequence activityTitle = activityInfo.loadLabel(baseContext.getPackageManager());
ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
Instrumentation instrumentation = activityThread.getInstrumentation();
Context activityContext;
int displayId =
activityOptions != null
? ActivityOptions.fromBundle(activityOptions).getLaunchDisplayId()
: Display.DEFAULT_DISPLAY;
// There's no particular reason to only do this above O, however the createActivityContext
// method signature changed between versions so just for convenience only the latest version is
// plumbed through, older versions will use the previous robolectric behavior of sharing
// activity and application ContextImpl objects.
// TODO(paulsowden): This should be enabled always but many service shadows are storing instance
// state that should be represented globally, we'll have to update these one by one to use
// static (i.e. global) state instead of instance state. For now enable only when the display
// is requested to a non-default display which requires a separate context to function
// properly.
if ((Boolean.getBoolean("robolectric.createActivityContexts")
|| (displayId != Display.DEFAULT_DISPLAY && displayId != Display.INVALID_DISPLAY))
&& RuntimeEnvironment.getApiLevel() >= O) {
LoadedApk loadedApk =
activityThread.getPackageInfo(
ShadowActivityThread.getApplicationInfo(), null, Context.CONTEXT_INCLUDE_CODE);
_LoadedApk_ loadedApkReflector = reflector(_LoadedApk_.class, loadedApk);
loadedApkReflector.setResources(application.getResources());
loadedApkReflector.setApplication(application);
activityContext =
reflector(_ContextImpl_.class)
.createActivityContext(
activityThread, loadedApk, activityInfo, token, displayId, overrideConfig);
reflector(_ContextImpl_.class, activityContext).setOuterContext(realActivity);
// This is not what the SDK does but for backwards compatibility with previous versions of
// robolectric, which did not use a separate activity context, move the theme from the
// application context (previously tests would configure the theme on the application context
// with the expectation that it modify the activity).
if (baseContext.getThemeResId() != 0) {
activityContext.setTheme(baseContext.getThemeResId());
}
} else {
activityContext = baseContext;
}
reflector(_Activity_.class, realActivity)
.callAttach(
realActivity,
activityContext,
activityThread,
instrumentation,
application,
intent,
activityInfo,
token,
activityTitle,
lastNonConfigurationInstances);
int theme = activityInfo.getThemeResource();
if (theme != 0) {
realActivity.setTheme(theme);
}
}
/**
* Sets the calling activity that will be reflected in {@link Activity#getCallingActivity} and
* {@link Activity#getCallingPackage}.
*/
public void setCallingActivity(@Nullable ComponentName activityName) {
callingActivity = activityName;
}
@Implementation
protected ComponentName getCallingActivity() {
return callingActivity;
}
/**
* Sets the calling package that will be reflected in {@link Activity#getCallingActivity} and
* {@link Activity#getCallingPackage}.
*
* Activity name defaults to some default value.
*/
public void setCallingPackage(@Nullable String packageName) {
if (callingActivity != null && callingActivity.getPackageName().equals(packageName)) {
// preserve the calling activity as it was, so non-conflicting setCallingActivity followed by
// setCallingPackage will not erase previously set information.
return;
}
callingActivity =
packageName != null ? new ComponentName(packageName, "unknown.Activity") : null;
}
@Implementation
protected String getCallingPackage() {
return callingActivity != null ? callingActivity.getPackageName() : null;
}
@Implementation
protected void setDefaultKeyMode(int keyMode) {
mDefaultKeyMode = keyMode;
// Some modes use a SpannableStringBuilder to track & dispatch input events
// This list must remain in sync with the switch in onKeyDown()
switch (mDefaultKeyMode) {
case Activity.DEFAULT_KEYS_DISABLE:
case Activity.DEFAULT_KEYS_SHORTCUT:
mDefaultKeySsb = null; // not used in these modes
break;
case Activity.DEFAULT_KEYS_DIALER:
case Activity.DEFAULT_KEYS_SEARCH_LOCAL:
case Activity.DEFAULT_KEYS_SEARCH_GLOBAL:
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
break;
default:
throw new IllegalArgumentException();
}
}
public int getDefaultKeymode() {
return mDefaultKeyMode;
}
@Implementation(minSdk = O_MR1)
protected void setShowWhenLocked(boolean showWhenLocked) {
this.showWhenLocked = showWhenLocked;
}
@RequiresApi(api = O_MR1)
public boolean getShowWhenLocked() {
return showWhenLocked;
}
@Implementation(minSdk = O_MR1)
protected void setTurnScreenOn(boolean turnScreenOn) {
this.turnScreenOn = turnScreenOn;
}
@RequiresApi(api = O_MR1)
public boolean getTurnScreenOn() {
return turnScreenOn;
}
@Implementation
protected final void setResult(int resultCode) {
this.resultCode = resultCode;
}
@Implementation
protected final void setResult(int resultCode, Intent data) {
this.resultCode = resultCode;
this.resultIntent = data;
}
@Implementation
protected LayoutInflater getLayoutInflater() {
return LayoutInflater.from(realActivity);
}
@Implementation
protected MenuInflater getMenuInflater() {
return new MenuInflater(realActivity);
}
/**
* Checks to ensure that the{@code contentView} has been set
*
* @param id ID of the view to find
* @return the view
* @throws RuntimeException if the {@code contentView} has not been called first
*/
@Implementation
protected View findViewById(int id) {
return getWindow().findViewById(id);
}
@Implementation
protected final Activity getParent() {
return parent;
}
/**
* Allow setting of Parent fragmentActivity (for unit testing purposes only)
*
* @param parent Parent fragmentActivity to set on this fragmentActivity
*/
@HiddenApi
@Implementation
public void setParent(Activity parent) {
this.parent = parent;
}
@Implementation
protected void onBackPressed() {
finish();
}
@Implementation
protected void finish() {
// Sets the mFinished field in the real activity so NoDisplay activities can be tested.
reflector(_Activity_.class, realActivity).setFinished(true);
}
@Implementation(minSdk = LOLLIPOP)
protected void finishAndRemoveTask() {
// Sets the mFinished field in the real activity so NoDisplay activities can be tested.
reflector(_Activity_.class, realActivity).setFinished(true);
}
@Implementation
protected void finishAffinity() {
// Sets the mFinished field in the real activity so NoDisplay activities can be tested.
reflector(_Activity_.class, realActivity).setFinished(true);
}
public void resetIsFinishing() {
reflector(_Activity_.class, realActivity).setFinished(false);
}
/**
* Returns whether {@link #finish()} was called.
*
*
Note: this method seems redundant, but removing it will cause problems for Mockito spies of
* Activities that call {@link Activity#finish()} followed by {@link Activity#isFinishing()}. This
* is because `finish` modifies the members of {@link ShadowActivity#realActivity}, so
* `isFinishing` should refer to those same members.
*/
@Implementation(minSdk = JELLY_BEAN)
protected boolean isFinishing() {
return reflector(DirectActivityReflector.class, realActivity).isFinishing();
}
/**
* Constructs a new Window (a {@link com.android.internal.policy.impl.PhoneWindow}) if no window
* has previously been set.
*
* @return the window associated with this Activity
*/
@Implementation
protected Window getWindow() {
Window window = reflector(DirectActivityReflector.class, realActivity).getWindow();
if (window == null) {
try {
window = ShadowWindow.create(realActivity);
setWindow(window);
} catch (Exception e) {
throw new RuntimeException("Window creation failed!", e);
}
}
return window;
}
/**
* @return fake SplashScreen
*/
@Implementation(minSdk = S)
protected synchronized Object getSplashScreen() {
if (splashScreen == null) {
splashScreen = new RoboSplashScreen();
}
return splashScreen;
}
public void setWindow(Window window) {
reflector(_Activity_.class, realActivity).setWindow(window);
}
@Implementation
protected void runOnUiThread(Runnable action) {
if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
reflector(DirectActivityReflector.class, realActivity).runOnUiThread(action);
} else {
ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
}
}
@Implementation
protected void setRequestedOrientation(int requestedOrientation) {
if (getParent() != null) {
getParent().setRequestedOrientation(requestedOrientation);
} else {
this.requestedOrientation = requestedOrientation;
}
}
@Implementation
protected int getRequestedOrientation() {
if (getParent() != null) {
return getParent().getRequestedOrientation();
} else {
return this.requestedOrientation;
}
}
@Implementation
protected int getTaskId() {
return 0;
}
@Implementation
public void startIntentSenderForResult(
IntentSender intentSender,
int requestCode,
@Nullable Intent fillInIntent,
int flagsMask,
int flagsValues,
int extraFlags,
Bundle options)
throws IntentSender.SendIntentException {
if (throwIntentSenderException) {
throw new IntentSender.SendIntentException("PendingIntent was canceled");
}
lastIntentSenderRequest =
new IntentSenderRequest(
intentSender, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
lastIntentSenderRequest.send();
}
@Implementation(minSdk = KITKAT)
protected void reportFullyDrawn() {
hasReportedFullyDrawn = true;
}
/**
* @return whether {@code ReportFullyDrawn()} methods has been called.
*/
public boolean getReportFullyDrawn() {
return hasReportedFullyDrawn;
}
/**
* @return the {@code contentView} set by one of the {@code setContentView()} methods
*/
public View getContentView() {
return ((ViewGroup) getWindow().findViewById(android.R.id.content)).getChildAt(0);
}
/**
* @return the {@code resultCode} set by one of the {@code setResult()} methods
*/
public int getResultCode() {
return resultCode;
}
/**
* @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
*/
public Intent getResultIntent() {
return resultIntent;
}
/**
* Consumes and returns the next {@code Intent} on the started activities for results stack.
*
* @return the next started {@code Intent} for an activity, wrapped in an {@link
* ShadowActivity.IntentForResult} object
*/
public IntentForResult getNextStartedActivityForResult() {
ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
ShadowInstrumentation shadowInstrumentation =
Shadow.extract(activityThread.getInstrumentation());
return shadowInstrumentation.getNextStartedActivityForResult();
}
/**
* Returns the most recent {@code Intent} started by {@link
* Activity#startActivityForResult(Intent, int)} without consuming it.
*
* @return the most recently started {@code Intent}, wrapped in an {@link
* ShadowActivity.IntentForResult} object
*/
public IntentForResult peekNextStartedActivityForResult() {
ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
ShadowInstrumentation shadowInstrumentation =
Shadow.extract(activityThread.getInstrumentation());
return shadowInstrumentation.peekNextStartedActivityForResult();
}
@Implementation
protected Object getLastNonConfigurationInstance() {
if (lastNonConfigurationInstance != null) {
return lastNonConfigurationInstance;
}
return reflector(DirectActivityReflector.class, realActivity).getLastNonConfigurationInstance();
}
/**
* @deprecated use {@link ActivityController#recreate()}.
*/
@Deprecated
public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) {
this.lastNonConfigurationInstance = lastNonConfigurationInstance;
}
/**
* @param view View to focus.
*/
public void setCurrentFocus(View view) {
currentFocus = view;
}
@Implementation
protected View getCurrentFocus() {
return currentFocus;
}
public int getPendingTransitionEnterAnimationResourceId() {
return pendingTransitionEnterAnimResId;
}
public int getPendingTransitionExitAnimationResourceId() {
return pendingTransitionExitAnimResId;
}
@Implementation
protected boolean onCreateOptionsMenu(Menu menu) {
optionsMenu = menu;
return reflector(DirectActivityReflector.class, realActivity).onCreateOptionsMenu(menu);
}
/**
* Return the options menu.
*
* @return Options menu.
*/
public Menu getOptionsMenu() {
return optionsMenu;
}
/**
* Perform a click on a menu item.
*
* @param menuItemResId Menu item resource ID.
* @return True if the click was handled, false otherwise.
*/
public boolean clickMenuItem(int menuItemResId) {
final RoboMenuItem item = new RoboMenuItem(menuItemResId);
return realActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
}
@Deprecated
public void callOnActivityResult(int requestCode, int resultCode, Intent resultData) {
reflector(_Activity_.class, realActivity).onActivityResult(requestCode, resultCode, resultData);
}
/** For internal use only. Not for public use. */
public void internalCallDispatchActivityResult(
String who, int requestCode, int resultCode, Intent data) {
if (VERSION.SDK_INT >= VERSION_CODES.P) {
reflector(_Activity_.class, realActivity)
.dispatchActivityResult(who, requestCode, resultCode, data, "ACTIVITY_RESULT");
} else {
reflector(_Activity_.class, realActivity)
.dispatchActivityResult(who, requestCode, resultCode, data);
}
}
/** For internal use only. Not for public use. */
public void attachController(ActivityController controller) {
this.controller = controller;
}
/** Sets if startIntentSenderForRequestCode will throw an IntentSender.SendIntentException. */
public void setThrowIntentSenderException(boolean throwIntentSenderException) {
this.throwIntentSenderException = throwIntentSenderException;
}
/**
* Container object to hold an Intent, together with the requestCode used in a call to {@code
* Activity.startActivityForResult(Intent, int)}
*/
public static class IntentForResult {
public Intent intent;
public int requestCode;
public Bundle options;
public IntentForResult(Intent intent, int requestCode) {
this.intent = intent;
this.requestCode = requestCode;
this.options = null;
}
public IntentForResult(Intent intent, int requestCode, Bundle options) {
this.intent = intent;
this.requestCode = requestCode;
this.options = options;
}
@Override
public String toString() {
return super.toString()
+ "{intent="
+ intent
+ ", requestCode="
+ requestCode
+ ", options="
+ options
+ '}';
}
}
public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) {
ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
ShadowInstrumentation shadowInstrumentation =
Shadow.extract(activityThread.getInstrumentation());
TargetAndRequestCode targetAndRequestCode =
shadowInstrumentation.getTargetAndRequestCodeForIntent(requestIntent);
internalCallDispatchActivityResult(
targetAndRequestCode.target, targetAndRequestCode.requestCode, resultCode, resultIntent);
}
@Implementation
protected final void showDialog(int id) {
showDialog(id, null);
}
@Implementation
protected final void dismissDialog(int id) {
final Dialog dialog = dialogForId.get(id);
if (dialog == null) {
throw new IllegalArgumentException();
}
dialog.dismiss();
}
@Implementation
protected final void removeDialog(int id) {
dialogForId.remove(id);
}
@Implementation
protected final boolean showDialog(int id, Bundle bundle) {
this.lastShownDialogId = id;
Dialog dialog = dialogForId.get(id);
if (dialog == null) {
dialog = reflector(_Activity_.class, realActivity).onCreateDialog(id);
if (dialog == null) {
return false;
}
if (bundle == null) {
reflector(_Activity_.class, realActivity).onPrepareDialog(id, dialog);
} else {
reflector(_Activity_.class, realActivity).onPrepareDialog(id, dialog, bundle);
}
dialogForId.put(id, dialog);
}
dialog.show();
return true;
}
public void setIsTaskRoot(boolean isRoot) {
mIsTaskRoot = isRoot;
}
@Implementation
protected final boolean isTaskRoot() {
return mIsTaskRoot;
}
/**
* @return the dialog resource id passed into {@code Activity.showDialog(int, Bundle)} or {@code
* Activity.showDialog(int)}
*/
public Integer getLastShownDialogId() {
return lastShownDialogId;
}
public boolean hasCancelledPendingTransitions() {
return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0;
}
@Implementation
protected void overridePendingTransition(int enterAnim, int exitAnim) {
pendingTransitionEnterAnimResId = enterAnim;
pendingTransitionExitAnimResId = exitAnim;
}
public Dialog getDialogById(int dialogId) {
return dialogForId.get(dialogId);
}
// TODO(hoisie): consider moving this to ActivityController#makeActivityEligibleForGc
@Implementation
protected void onDestroy() {
reflector(DirectActivityReflector.class, realActivity).onDestroy();
ShadowActivityThread activityThread = Shadow.extract(RuntimeEnvironment.getActivityThread());
IBinder token = reflector(_Activity_.class, realActivity).getToken();
activityThread.removeActivity(token);
}
@Implementation
protected void recreate() {
if (controller != null) {
// Post the call to recreate to simulate ActivityThread behavior.
new Handler(Looper.getMainLooper()).post(controller::recreate);
} else {
throw new IllegalStateException(
"Cannot use an Activity that is not managed by an ActivityController");
}
}
@Implementation
protected void startManagingCursor(Cursor c) {
managedCursors.add(c);
}
@Implementation
protected void stopManagingCursor(Cursor c) {
managedCursors.remove(c);
}
public List getManagedCursors() {
return managedCursors;
}
@Implementation
protected final void setVolumeControlStream(int streamType) {
this.streamType = streamType;
}
@Implementation
protected final int getVolumeControlStream() {
return streamType;
}
@Implementation(minSdk = M)
protected final void requestPermissions(String[] permissions, int requestCode) {
lastRequestedPermission = new PermissionsRequest(permissions, requestCode);
reflector(DirectActivityReflector.class, realActivity)
.requestPermissions(permissions, requestCode);
}
/**
* Starts a lock task.
*
* The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
* implementation has no effect.
*/
@Implementation(minSdk = LOLLIPOP)
protected void startLockTask() {
Shadow.extract(getActivityManager())
.setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_LOCKED);
}
/**
* Stops a lock task.
*
* The status of the lock task can be verified using {@link #isLockTask} method. Otherwise this
* implementation has no effect.
*/
@Implementation(minSdk = LOLLIPOP)
protected void stopLockTask() {
Shadow.extract(getActivityManager())
.setLockTaskModeState(ActivityManager.LOCK_TASK_MODE_NONE);
}
/**
* Returns if the activity is in the lock task mode.
*
* @deprecated Use {@link ActivityManager#getLockTaskModeState} instead.
*/
@Deprecated
public boolean isLockTask() {
return getActivityManager().isInLockTaskMode();
}
private ActivityManager getActivityManager() {
return (ActivityManager) realActivity.getSystemService(Context.ACTIVITY_SERVICE);
}
/** Changes state of {@link #isInMultiWindowMode} method. */
public void setInMultiWindowMode(boolean value) {
inMultiWindowMode = value;
}
@Implementation(minSdk = N)
protected boolean isInMultiWindowMode() {
return inMultiWindowMode;
}
@Implementation(minSdk = N)
protected boolean isInPictureInPictureMode() {
return isInPictureInPictureMode;
}
@Implementation(minSdk = N)
protected void enterPictureInPictureMode() {
isInPictureInPictureMode = true;
}
@Implementation(minSdk = O)
protected boolean enterPictureInPictureMode(PictureInPictureParams params) {
isInPictureInPictureMode = true;
return true;
}
@Implementation
protected boolean moveTaskToBack(boolean nonRoot) {
isInPictureInPictureMode = false;
return true;
}
/**
* Gets the last startIntentSenderForResult request made to this activity.
*
* @return The IntentSender request details.
*/
public IntentSenderRequest getLastIntentSenderRequest() {
return lastIntentSenderRequest;
}
/**
* Gets the last permission request submitted to this activity.
*
* @return The permission request details.
*/
public PermissionsRequest getLastRequestedPermission() {
return lastRequestedPermission;
}
/**
* Initializes the associated Activity with an {@link android.app.VoiceInteractor} instance.
* Subsequent {@link android.app.Activity#getVoiceInteractor()} calls on the associated activity
* will return a {@link android.app.VoiceInteractor} instance
*/
public void initializeVoiceInteractor() {
if (RuntimeEnvironment.getApiLevel() < N) {
throw new IllegalStateException("initializeVoiceInteractor requires API " + N);
}
reflector(_Activity_.class, realActivity)
.setVoiceInteractor(ReflectionHelpers.createDeepProxy(IVoiceInteractor.class));
}
/**
* Calls Activity#onGetDirectActions with the given parameters. This method also simulates the
* Parcel serialization/deserialization which occurs when assistant requests DirectAction.
*/
public void callOnGetDirectActions(
CancellationSignal cancellationSignal, Consumer> callback) {
if (RuntimeEnvironment.getApiLevel() < Q) {
throw new IllegalStateException("callOnGetDirectActions requires API " + Q);
}
realActivity.onGetDirectActions(
cancellationSignal,
directActions -> {
Parcel parcel = Parcel.obtain();
parcel.writeParcelableList(directActions, 0);
parcel.setDataPosition(0);
List output = new ArrayList<>();
parcel.readParcelableList(output, DirectAction.class.getClassLoader());
callback.accept(output);
});
}
/** Class to hold a permissions request, including its request code. */
public static class PermissionsRequest {
public final int requestCode;
public final String[] requestedPermissions;
public PermissionsRequest(String[] requestedPermissions, int requestCode) {
this.requestedPermissions = requestedPermissions;
this.requestCode = requestCode;
}
}
/** Class to holds details of a startIntentSenderForResult request. */
public static class IntentSenderRequest {
public final IntentSender intentSender;
public final int requestCode;
@Nullable public final Intent fillInIntent;
public final int flagsMask;
public final int flagsValues;
public final int extraFlags;
public final Bundle options;
public IntentSenderRequest(
IntentSender intentSender,
int requestCode,
@Nullable Intent fillInIntent,
int flagsMask,
int flagsValues,
int extraFlags,
Bundle options) {
this.intentSender = intentSender;
this.requestCode = requestCode;
this.fillInIntent = fillInIntent;
this.flagsMask = flagsMask;
this.flagsValues = flagsValues;
this.extraFlags = extraFlags;
this.options = options;
}
public void send() {
if (intentSender instanceof RoboIntentSender) {
try {
Shadow.extract(((RoboIntentSender) intentSender).getPendingIntent())
.send(
RuntimeEnvironment.getApplication(),
0,
null,
null,
null,
null,
null,
requestCode);
} catch (PendingIntent.CanceledException e) {
throw new RuntimeException(e);
}
}
}
}
private ShadowPackageManager shadowOf(PackageManager packageManager) {
return Shadow.extract(packageManager);
}
@ForType(value = Activity.class, direct = true)
interface DirectActivityReflector {
void runOnUiThread(Runnable action);
void onDestroy();
boolean isFinishing();
Window getWindow();
Object getLastNonConfigurationInstance();
boolean onCreateOptionsMenu(Menu menu);
void requestPermissions(String[] permissions, int requestCode);
}
}