Test if soft keyboard is visible using espresso - android

I want to test keyboard visibility when an activity calls onCreate() and onResume().
How can i test whether or not the keyboard is shown using espresso?

I know, that the question is old enough, but it doesn't have any accepted answer though.
In our UI tests we use this method, which uses some shell commands:
/**
* This method works like a charm
*
* SAMPLE CMD OUTPUT:
* mShowRequested=true mShowExplicitlyRequested=true mShowForced=false mInputShown=true
*/
fun isKeyboardOpenedShellCheck(): Boolean {
val checkKeyboardCmd = "dumpsys input_method | grep mInputShown"
try {
return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand(checkKeyboardCmd).contains("mInputShown=true")
} catch (e: IOException) {
throw RuntimeException("Keyboard check failed", e)
}
}
Hope, it'll be useful for someone

fun isKeyboardShown(): Boolean {
val inputMethodManager = InstrumentationRegistry.getInstrumentation().targetContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
return inputMethodManager.isAcceptingText
}
found at Google groups

another trick could be checking for the visibility of a view that you know is going to be covered when the keyboard is showing. don't forget to take animations into consideration...
instrumentation testing using espresso and hamcrest for the NOT matcher something like:
//make sure keyboard is visible by clicking on an edit text component
ViewInteraction v = onView(withId(R.id.editText));
ViewInteraction v2 = onView(withId(R.id.componentVisibleBeforeKeyboardIsShown));
v2.check(matches(isDisplayed()));
v.perform(click());
//add a small delay because of the showing keyboard animation
SystemClock.sleep(500);
v2.check(matches(not(isDisplayed())));
hideKeyboardMethod();
//add a small delay because of the hiding keyboard animation
SystemClock.sleep(500);
v2.check(matches(isDisplayed()));

This works for me.
private boolean isSoftKeyboardShown() {
final InputMethodManager imm = (InputMethodManager) getActivityInstance()
.getSystemService(Context.INPUT_METHOD_SERVICE);
return imm.isAcceptingText();
}
Java version of #igork's answer.

This method is working for me
val isKeyboardOpened: Boolean
get() {
for (window in InstrumentationRegistry.getInstrumentation().uiAutomation.windows) {
if (window.type == AccessibilityWindowInfo.TYPE_INPUT_METHOD) {
return true
}
}
return false
}

Related

Xamarin Forms Entry Keyboard Stop From Losing Focus

I have an Entry and a Button:
<StackLayout>
<CustomViews:ChatEntryView x:Name="ChatEntry" />
<Button Text="Send" Command="SendCommand"/>
</StackLayout>
What I wanted to achieve here is that when the user starts types something on the Entry control and then presses the button, it should not hide the keyboard (or lose the Entry Focus).
The ChatEntryView here is just actually a custom view that inherits from the Entry control and what I did inside:
1.) Added an Unfocused handler
Unfocused += ChatEntryView_Unfocused;
void ChatEntryView_Unfocused(object sender, FocusEventArgs e)
{
this.Focus();
}
2.) Tried Handling on PropertyChanged
protected override void OnPropertyChanged(string propertyName = null)
{
this.Focus();
base.OnPropertyChanged(propertyName);
}
3.) Tried Handling on PropertyChanging
protected override void OnPropertyChanging(string propertyName = null)
{
this.Focus();
base.OnPropertyChanging(propertyName);
}
But all the three methods doesn't seem to work. I was able to do a work around on IOS by making a custom renderer and it's not very neat (by actually interfacing to the Control.ShouldEndEditing on IOS).
But my problem now is on Android, as I don't exactly know how to do this on Android and there's no Control.ShouldEndEditing (the interface on Android) that I can work with.
What happens by using the handlers above is that, the keyboard for the entry view still loses focus and then immediately gets focuses again which is very odd.
The keyboard pushes down (loses focus) and then pushes up (forced focus).
I know it's too late to anwser this question, but it might be helpful for someone else, I added this code to MainActivity, it might not be a neat solution, but works for me:
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
bool customEntryRendererFocused = focused != null && focused.Parent is CustomEntryRenderer_Droid;
_lieAboutCurrentFocus = customEntryRendererFocused;
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}

AIR/as3 stage keylistener overriding input textfield

I'm building a mobile AIR app (Android & IOS) with Adobe Flash Builder 4.6 and I'm having this annoying problem.
Because I want to 'catch' the back-key on Android devices I added the following code to my main class:
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
private function keyDown(k:KeyboardEvent):void {
if(k.keyCode == Keyboard.BACK) {
backClicked(); // function handling the back-action, not important
k.preventDefault();
}
Now somewhere else - nested in some classes - I've got a textfield:
TF = new TextField();
TF.type = TextFieldType.INPUT;
But when I set focus on the textfield the soft keyboard does appear, but I can't type a single character. When I disable the keylistener: no problem.
Seems like the listener is overriding my input field. Is there any workaround on this?
I have also implemented the back button functionality for my mobile apps , but i used to register keydown event only when my particular view is activated and removed the registered when view get deactivated.
in <s:view ....... viewActivate ="enableHardwareKeyListeners(event)" viewDeactivate="destroyHardwareKeyListeners(event)">
// add listener only for android device
if (Check for android device) {
NativeApplication.nativeApplication.addEventListener(KeyboardEvent.KEY_DOWN, handleHardwareKeysDown, false, 0);
NativeApplication.nativeApplication.addEventListener(KeyboardEvent.KEY_UP, handleHardwareKeysUp, false, 0);
this.setFocus();
}
private function destroyHardwareKeyListeners(event:ViewNavigatorEvent):void
{
if (NativeApplication.nativeApplication.hasEventListener(KeyboardEvent.KEY_DOWN))
NativeApplication.nativeApplication.removeEventListener(KeyboardEvent.KEY_DOWN, handleHardwareKeysDown);
if (NativeApplication.nativeApplication.hasEventListener(KeyboardEvent.KEY_UP))
NativeApplication.nativeApplication.removeEventListener(KeyboardEvent.KEY_UP, handleHardwareKeysUp);
}
private function handleHardwareKeysDown(e:KeyboardEvent):void
{
if (e.keyCode == Keyboard.BACK) {
e.preventDefault();
// your code
} else {
}
}
private function handleHardwareKeysUp(e:KeyboardEvent):void
{
if (e.keyCode == Keyboard.BACK)
e.preventDefault();
}
May this can help you.

Programatically change keyboard from custom android keyboard

I have created an android custom keyboard. After pressing a button on it, I'd like it to change the keyboard back to the previous keyboard, presumable using InputMethodManager.setInputMethod(IBinder token, String id);
However, I can't work out where to get the token from - using getCurrentInputBinding().getConnectionToken() doesn't work.
Does anyone know where to find the token?
Thanks,
Ed
Turns out that the switchInputMethod(String id) method works a treat - no need for that token.
You get the token from the view by view.getWindowToken().
You can use this Method to get Token and activate last used Keyboard
private fun switchToLastKeyboard() {
try {
val imm: InputMethodManager =
this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val token = this.window.window!!.attributes.token
//imm.setInputMethod(token, LATIN);
imm.switchToLastInputMethod(token)
} catch (t: Throwable) { // java.lang.NoSuchMethodError if API_level<11
Log.i("TAG", "onCreateInputView: Throwable " + t.message)
}
}

Android: Disable text selection in a webview

I am using a webview to present some formatted stuff in my app. For some interaction (which are specific to certain dom elements) I use javascript and WebView.addJavascriptInterface(). Now, I want to recognize a long touch. Unfortunately, onLongTouch, in Android 2.3 the handles for text selection are displayed.
How can I turn off this text selection without setting the onTouchListener and return true? (Then, the interaction with the "website" doesn't work anymore.
This worked for me
mWebView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
return true;
}
});
mWebView.setLongClickable(false);
I have not tested, if you don't want the vibration caused by the long click, you can try this:
mWebView.setHapticFeedbackEnabled(false);
Setting webkit css property -webkit-user-select to none would solve the problem.
Example CSS to disable selection:
* {
-webkit-user-select: none;
}
I figured it out!! This is how you can implement your own longtouchlistener. In the function longTouch you can make a call to your javascript interface.
var touching = null;
$('selector').each(function() {
this.addEventListener("touchstart", function(e) {
e.preventDefault();
touching = window.setTimeout(longTouch, 500, true);
}, false);
this.addEventListener("touchend", function(e) {
e.preventDefault();
window.clearTimeout(touching);
}, false);
});
function longTouch(e) {
// do something!
}
This works.
It appears that cut/paste via long press is turned off if you used
articleView.setWebChromeClient(new WebChromeClient(){...})
See https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
So if you are using setChromeClient and you WANT to have long click to start copy/paste, the do the following:
webView.setWebChromeClient(new WebChromeClient(){
[.... other overrides....]
// #Override
// https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
// If you DO NOT want to start selection by long click,
// the remove this function
// (All this is undocumented stuff...)
public void onSelectionStart(WebView view) {
// By default we cancel the selection again, thus disabling
// text selection unless the chrome client supports it.
// view.notifySelectDialogDismissed();
}
});
An alternative solution is to subclass WebView and Override performLongClick as bellow:
public class AdvanceWebView extends WebView {
//Add constructors...
#Override
public boolean performLongClick() {
return true;
}
}
It seems that the only option is to set onTouchListener and write your own code to detect long-click. Then return true if it's a long-click and false otherwise.
For kotlin i found the following to work:
webView.isLongClickable = false

Problems creating a Popup Window in Android Activity

I'm trying to create a popup window that only appears the first time the application starts. I want it to display some text and have a button to close the popup. However, I'm having troubles getting the PopupWindow to even work. I've tried two different ways of doing it:
First I have an XML file which declares the layout of the popup called popup.xml (a textview inside a linearlayout) and I've added this in the OnCreate() of my main Activity:
PopupWindow pw = new PopupWindow(findViewById(R.id.popup), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0);
Second I did the exact same with this code:
final LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.popup, (ViewGroup) findViewById(R.layout.main) ), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
The first throws a NullPointerException and the second throws a BadTokenException and says "Unable to add window -- token null is not valid"
What in the world am I doing wrong? I'm extremely novice so please bear with me.
To avoid BadTokenException, you need to defer showing the popup until after all the lifecycle methods are called (-> activity window is displayed):
findViewById(R.id.main_page_layout).post(new Runnable() {
public void run() {
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
}
});
Solution provided by Kordzik will not work if you launch 2 activities consecutively:
startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);
If you add popup that way in a case like this, you will get the same crash because ActivityWithPopup won't be attached to Window in this case.
More universal solusion is onAttachedToWindow and onDetachedFromWindow.
And also there is no need for postDelayed(Runnable, 100). Because this 100 millis does not guaranties anything
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.d(TAG, "onAttachedToWindow");
showPopup();
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d(TAG, "onDetachedFromWindow");
popup.dismiss();
}
The accepted answer did not work for me. I still received BadTokenException. So I just called the Runnable from a Handler with delay as such:
new Handler().postDelayed(new Runnable() {
public void run() {
showPopup();
}
}, 100);
use class Context eg. MainActivity.this instead of getApplicationContext()
There are two scenarios when this exception could occur. One is mentioned by kordzik. Other scenario is mentioned here: http://blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/
Make sure you handle both of them
the solution is to set the spinner mode to dialog as below:
android:spinnerMode="dialog"
or
Spinner(Context context, int mode)
tnxs RamallahDroid
See This.
Depending on the use case, for types of pop-up to display a message, setting the pop-up type to TYPE_TOAST using setWindowLayoutType() avoids the issue, as this type of pop-up is not dependent on the underlying activity.
Edit: One of the side effects: no interaction in the popup window for API <= 18, as the touchable / focusable events would be removed by the system. ( http://www.jianshu.com/p/634cd056b90c )
I end up with using TYPE_PHONE (as the app happens to have the permission SYSTEM_ALERT_WINDOW, otherwise this won't work too).
You can check the rootview if it has the token. You can get the parent layout defined from your activity xml, mRootView
if (mRootView != null && mRootView.getWindowToken() != null) {
popupWindow.showAtLocation();
}
Check that findViewById returns something - you might be calling it too early, before the layout is built
Also you may want to post logcat output for the exceptions you're getting
You can also try to use this check:
public void showPopupProgress (){
new Handler().post(new Runnable() {
#Override
public void run() {
if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
showPopupProgress();
return;
}
popup.showAtLocation(.....);
}
});
}
If you show a PopupWindow in another PopupWindow, do not use the view in first POP, use the origin parent view.
pop.showAtLocation(parentView, ... );
I had the same problem (BadTokenException) with AlertDialog on dialog.show(). I was making an AlertDialog by following some example. In my case the reason of that problem was a string
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)
Everything became working after I removed it.
Maybe it's time for a newer solution. This methods checks 5 times every 50ms if the parent view for the PopupWindow has a token. I use it inside my customized PopupWindow.
private fun tryToShowTooltip(tooltipLayout: View) {
Flowable.fromCallable { parentView.windowToken != null }
.map { hasWindowToken ->
if (hasWindowToken) {
return#map hasWindowToken
}
throw RetryException()
}
.retryWhen { errors: Flowable<Throwable> ->
errors.zipWith(
Flowable.range(1, RETRY_COUNT),
BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
if (retryCount >= RETRY_COUNT) {
throw error
} else {
retryCount
}
})
.flatMap { retryCount: Int ->
Flowable.timer(retryCount * MIN_TIME_OUT_MS, TimeUnit.MILLISECONDS)
}
}
.onErrorReturn {
false
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ hasWindowToken ->
if (hasWindowToken && !isShowing) {
showAtLocation(tooltipLayout, Gravity.NO_GRAVITY, 100, 100)
}
}, { t: Throwable? ->
//error logging
})
}
with
companion object {
private const val RETRY_COUNT = 5
private const val MIN_TIME_OUT_MS = 50L
}
class RetryException : Throwable()
You can specify the y-offset to account for the status bar from the pw.showAtLocation method...

Categories

Resources