blob: b2e20d03ca65cfbe0b4d602ad9bfa77698dceccc [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.view.View;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.InputMethodManager;
import com.google.common.base.Optional;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
@Implements(value = InputMethodManager.class)
public class ShadowInputMethodManager {
/**
* Handler for receiving soft input visibility changed event.
*
* Since Android does not have any API for retrieving soft input status, most application
* relies on GUI layout changes to detect the soft input change event. Currently, Robolectric are
* not able to simulate the GUI change when application changes the soft input through {@code
* InputMethodManager}, this handler can be used by application to simulate GUI change in response
* of the soft input change.
*/
public interface SoftInputVisibilityChangeHandler {
void handleSoftInputVisibilityChange(boolean softInputVisible);
}
private boolean softInputVisible;
private Optional<SoftInputVisibilityChangeHandler> visibilityChangeHandler = Optional.absent();
@Implementation
protected boolean showSoftInput(View view, int flags) {
return showSoftInput(view, flags, null);
}
@Implementation
protected boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
setSoftInputVisibility(true);
return true;
}
@Implementation
protected boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
return hideSoftInputFromWindow(windowToken, flags, null);
}
@Implementation
protected boolean hideSoftInputFromWindow(
IBinder windowToken, int flags, ResultReceiver resultReceiver) {
int resultCode;
if (isSoftInputVisible()) {
setSoftInputVisibility(false);
resultCode = InputMethodManager.RESULT_HIDDEN;
} else {
resultCode = InputMethodManager.RESULT_UNCHANGED_HIDDEN;
}
if (resultReceiver != null) {
resultReceiver.send(resultCode, null);
}
return true;
}
@Implementation
protected void toggleSoftInput(int showFlags, int hideFlags) {
setSoftInputVisibility(!isSoftInputVisible());
}
public boolean isSoftInputVisible() {
return softInputVisible;
}
public void setSoftInputVisibilityHandler(
SoftInputVisibilityChangeHandler visibilityChangeHandler) {
this.visibilityChangeHandler =
Optional.<SoftInputVisibilityChangeHandler>of(visibilityChangeHandler);
}
private void setSoftInputVisibility(boolean visible) {
if (visible == softInputVisible) {
return;
}
softInputVisible = visible;
if (visibilityChangeHandler.isPresent()) {
visibilityChangeHandler.get().handleSoftInputVisibilityChange(softInputVisible);
}
}
@Implementation
protected void restartInput(View view) {}
@Implementation
protected boolean isActive(View view) {
return false;
}
@Implementation
protected boolean isActive() {
return false;
}
@Implementation
protected boolean isFullscreenMode() {
return false;
}
@Implementation
protected void focusIn(View view) {}
@Implementation(minSdk = M)
protected void onViewDetachedFromWindow(View view) {}
@Implementation
protected void displayCompletions(View view, CompletionInfo[] completions) {}
@Implementation(maxSdk = LOLLIPOP_MR1)
protected static InputMethodManager peekInstance() {
// Android has a bug pre M where peekInstance was dereferenced without a null check:-
// https://github.com/aosp-mirror/platform_frameworks_base/commit/a046faaf38ad818e6b5e981a39fd7394cf7cee03
// So for earlier versions, just call through directly to getInstance()
if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) {
return ReflectionHelpers.callStaticMethod(
InputMethodManager.class,
"getInstance",
ClassParameter.from(Looper.class, Looper.getMainLooper()));
} else if (RuntimeEnvironment.getApiLevel() <= LOLLIPOP_MR1) {
return InputMethodManager.getInstance();
}
return directlyOn(InputMethodManager.class, "peekInstance");
}
@Resetter
public static void reset() {
String instanceFieldName =
RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1 ? "mInstance" : "sInstance";
ReflectionHelpers.setStaticField(InputMethodManager.class, instanceFieldName, null);
}
}