- Home /
[solved] If TouchScreenKeyboard.hideInput=true, non-ASCII letters aren't registered - why? Work-around?
Well, I managed to solve this by writing my own C# wrapper (having a similar API with TouchScreenKeyboard) over my own View that takes care of the soft keyboard.
If on Android a soft keyboard is opened while having TouchScreenKeyboard.hideInput set to true, non-ASCII letters (eg. ä, ö, €, ...) are not registered. They can't be read from TouchScreenKeyboard.text nor are they visible on GUI text input field. Why? Is there any work-around for this matter (other than keeping hideInput as false)
Answer by codehead · Dec 18, 2012 at 04:20 PM
I just noticed a new Unity 3.5.7 release:
Android: Fixed a problem with non-ASCII characters and keyboard input.
I haven't tested the new one YET but I'm assuming it would fix it :) However, it probably doesn't support detecting the back button/cancelling the input... That seems to be in the 4.0 API. So here we go. Code is not clean, but it's a prototype. If and when you use it, please give my credit ;)
You need Android Pro because this relies upon a plugin. It should be pretty easy to follow the AndroidKeyboard.cs & AndroidKeyboard.java to figure out how it's used ;)
Best regards, Samuli
AndroidKeyboard.cs
using System;
using UnityEngine;
/**
* Unity3D's TouchScreenKeyboard is flawed, here's our own implementation to make up its shortcomings while having a similar API.
*
* @author SamuliPiela / One Digit Ltd
*/
public static class AndroidKeyboard
{
static string _className = "fi.onedigit.mortimer.util.AndroidKeyboard";
static string _methodOpen = "open";
static string _methodClose = "close";
static string _methodIsDone = "isDone";
static string _methodIsCanceled = "isCanceled";
static string _methodIsVisible = "isVisible";
static string _methodGetText = "getText";
/**
* true if the keyboard is not open, false if the keyboard is still open
*/
public static bool IsDone { get {
return AndroidKeyboard._IsDone();
}
}
/**
* true if the keyboard has gone away without the end-user hitting the action button first.
*/
public static bool IsCanceled { get {
return AndroidKeyboard._IsCanceled();
}
}
public static bool IsVisible { get {
return AndroidKeyboard._IsVisible();
}
}
/**
* The text that is currently being input or if the keyboard is not open, the last inputted text
*/
public static string Text { get {
return AndroidKeyboard._GetText();
}
}
public static void Open()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
jc.CallStatic(_methodOpen);
}
}
public static void Close()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
jc.CallStatic(_methodClose);
}
}
private static bool _IsDone()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
return jc.CallStatic<bool>(_methodIsDone);
}
}
private static bool _IsCanceled()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
return jc.CallStatic<bool>(_methodIsCanceled);
}
}
private static bool _IsVisible()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
return jc.CallStatic<bool>(_methodIsVisible);
}
}
private static string _GetText()
{
using (AndroidJavaClass jc = new AndroidJavaClass(_className)) {
return jc.CallStatic<string>(_methodGetText);
}
}
}
Then, the corresponding Java part:
AndroidKeyboard.java
package fi.onedigit.mortimer.util;
import android.app.Activity;
import android.text.Editable;
import com.unity3d.player.UnityPlayer;
import fi.onedigit.mortimer.manglingmachine.Dbg;
/**
* A match for Mortimers AndroidKeyboard.cs
*
* @author Samuli Piela
*/
public class AndroidKeyboard implements KBInputListener {
static final String logTag = "AndroidKeyboard";
private static final AndroidKeyboard instance = new AndroidKeyboard();
private KBDialog inputDialog = null;
private String lastKnownInputText = "";
private boolean wasActionButtonPressed = false;
private AndroidKeyboard() {
}
// Static wrappers to make it a simple to use API
public synchronized static void open() {
AndroidKeyboard.instance.doOpen();
}
public synchronized static void close() {
AndroidKeyboard.instance.doClose();
}
public synchronized static boolean isDone() {
return AndroidKeyboard.instance.doIsDone();
}
public synchronized static boolean isCanceled() {
return AndroidKeyboard.instance.doIsCanceled();
}
public synchronized static boolean isVisible() {
return AndroidKeyboard.instance.doIsVisible();
}
public synchronized static String getText() {
return AndroidKeyboard.instance.doGetText();
}
//
private Activity getActivity() {
return UnityPlayer.currentActivity;
}
private void doOpen() {
// Need to reset the flags right away, therefore the double check
if (this.doIsVisible()) {
Dbg.e(AndroidKeyboard.logTag, "open() Already visible. Ignore.");
return;
}
this.wasActionButtonPressed = false;
this.lastKnownInputText = "";
this.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (AndroidKeyboard.this.doIsVisible()) {
Dbg.e(AndroidKeyboard.logTag, "open() Already visible. Ignore.");
return;
}
AndroidKeyboard.this.inputDialog = new KBDialog(AndroidKeyboard.this.getActivity(), AndroidKeyboard.this);
AndroidKeyboard.this.inputDialog.show();
}
});
}
private void doClose() {
this.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (AndroidKeyboard.this.inputDialog == null) {
Dbg.e(AndroidKeyboard.logTag, "close() Already closed. Ignore.");
return;
}
AndroidKeyboard.this.inputDialog.dismiss();
AndroidKeyboard.this.inputDialog = null;
}
});
}
private boolean doIsVisible() {
return this.inputDialog != null && this.inputDialog.isShowing();
}
public boolean doIsDone() {
return !this.doIsVisible() || this.wasActionButtonPressed;
}
public boolean doIsCanceled() {
return this.doIsDone() && !this.wasActionButtonPressed;
}
public String doGetText() {
return this.lastKnownInputText;
}
public void afterTextChanged(final Editable s) {
}
public void beforeTextChanged(final CharSequence s, final int start, final int count, int after) {
}
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
this.lastKnownInputText = s.toString();
}
public void onTextCompleted(final boolean wasActionButtonPressed) {
Dbg.d(AndroidKeyboard.logTag, "onTextCompleted(" + wasActionButtonPressed + ")");
this.wasActionButtonPressed = wasActionButtonPressed;
}
}
KBDialog.java
package fi.onedigit.mortimer.util;
import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.TextView;
import fi.onedigit.mortimer.manglingmachine.Dbg;
public class KBDialog extends Dialog {
private final KBInputListener listener;
private final KBView v;
private boolean textCompleted = false;
private class KBView extends EditText implements TextView.OnEditorActionListener {
public KBView(final Context context) {
super(context);
this.setOnEditorActionListener(this);
}
@Override
public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {
outAttrs.actionLabel = null;
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN
| EditorInfo.IME_ACTION_SEARCH;
return new BaseInputConnection(this, false);
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
KBDialog.this.onTextCompleted(false);
return true;
}
return super.onKeyPreIme(keyCode, event);
}
// Action button won't be sent to onKeyPreIme() so it needs its own
// listener
public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
KBDialog.this.onTextCompleted(true);
}
return true;
}
}
public KBDialog(final Context context, final KBInputListener listener) {
super(context);
super.getWindow().setGravity(80);
super.getWindow().requestFeature(1);
super.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
// To make the window transparent, you can use an empty drawable (for
// instance a ColorDrawable with the color 0
final Drawable d = new ColorDrawable(0);
super.getWindow().setBackgroundDrawable(d);
this.listener = listener;
this.v = new KBView(context);
this.v.addTextChangedListener(this.listener);
super.setContentView(v);
this.v.setFocusableInTouchMode(true);
this.v.setHeight(1);
this.v.setWidth(1);
this.v.setWillNotDraw(true);
this.v.setCursorVisible(false);
this.v.setAlpha(0f);
super.getWindow().clearFlags(Window.FEATURE_PROGRESS);
}
@Override
public void show() {
super.show();
this.v.requestFocus();
}
@Override
public void dismiss() {
Dbg.d(AndroidKeyboard.logTag, "dismiss()");
// Dialog got dismissed before back or action button got hit?
// Fire onTextCompleted().
if (!this.textCompleted) {
this.listener.onTextCompleted(false);
}
super.dismiss();
}
@Override
protected void onStop() {
Dbg.d(AndroidKeyboard.logTag, "onStop()");
super.onStop();
this.v.removeTextChangedListener(this.listener);
}
// In this case, back button or action button got hit. Keyboard wasn't
// automatically closed so lets close it first.
private void onTextCompleted(final boolean wasActionButtonPressed) {
this.textCompleted = true;
this.dismiss();
this.listener.onTextCompleted(wasActionButtonPressed);
}
}
KBInputListener.java
package fi.onedigit.mortimer.util;
import android.text.TextWatcher;
public interface KBInputListener extends TextWatcher {
/**
* Our addition to TextWatcher. Invoked as a final step, also in cases where
* the keyboard got closed due to hitting action button, losing focus, back
* button etc.
*
* @param wasActionButtonPressed
*/
public void onTextCompleted(final boolean wasActionButtonPressed);
}