/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package com.badlogic.gdx.backends.android;
import android.app.Dialog;
import android.content.Context;
import android.os.Handler;
import android.text.Editable;
import android.text.InputFilter;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.badlogic.gdx.Input.Peripheral;
/**
* Responsible for showing and hiding the Android onscreen keyboard (aka softkeyboard). Uses a dialog with an invisible
* TextView and injects key down/up and typed events into AndroidInput. Only the delete and back keys will trigger key
* down/up events. Alphanumeric keys will be directly injected as key typed events which is sufficient to implement
* things like text fields.
*
* Since the input mechanism for softkeyboards is a bit complex, we don't directly get key events from the softkeyboard.
* Instead we intercept calls to the Editable of the invisible TextView which we translate into delete key events and
* key typed events.
*
* @author mzechner
*/
class AndroidOnscreenKeyboard implements OnKeyListener, OnTouchListener {
final Context context;
final Handler handler;
final AndroidInput input;
Dialog dialog;
TextView textView;
public AndroidOnscreenKeyboard(Context context, Handler handler, AndroidInput input) {
this.context = context;
this.handler = handler;
this.input = input;
}
Dialog createDialog() {
textView = createView(context);
textView.setOnKeyListener(this);
FrameLayout.LayoutParams textBoxLayoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.FILL_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
textView.setLayoutParams(textBoxLayoutParams);
textView.setFocusable(true);
textView.setFocusableInTouchMode(true);
textView.setImeOptions(textView.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
final FrameLayout layout = new FrameLayout(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 0);
layout.setLayoutParams(layoutParams);
layout.addView(textView);
layout.setOnTouchListener(this);
dialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
dialog.setContentView(layout);
return dialog;
}
public static TextView createView(Context context) {
final TextView view = new TextView(context) {
Editable editable = new PassThroughEditable();
@Override
protected boolean getDefaultEditable() {
return true;
}
@Override
public Editable getEditableText() {
return editable;
}
@Override
protected MovementMethod getDefaultMovementMethod() {
return ArrowKeyMovementMethod.getInstance();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d("Test", "down keycode: " + event.getKeyCode());
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Log.d("Test", "up keycode: " + event.getKeyCode());
return super.onKeyUp(keyCode, event);
}
};
// view.setCursorVisible(false);
return view;
}
public void setVisible(boolean visible) {
if (visible && dialog != null) {
dialog.dismiss();
dialog = null;
}
if (visible && dialog == null && !input.isPeripheralAvailable(Peripheral.HardwareKeyboard)) {
handler.post(new Runnable() {
@Override
public void run() {
dialog = createDialog();
dialog.show();
handler.post(new Runnable() {
@Override
public void run() {
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
InputMethodManager input = (InputMethodManager) context
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (input != null)
input.showSoftInput(textView, InputMethodManager.SHOW_FORCED);
}
});
final View content = dialog.getWindow().findViewById(Window.ID_ANDROID_CONTENT);
content.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
int[] screenloc = new int[2];
private int keyboardHeight;
private boolean keyboardShowing;
@Override
public boolean onPreDraw() {
content.getLocationOnScreen(screenloc);
keyboardHeight = Math.abs(screenloc[1]);
if (keyboardHeight > 0)
keyboardShowing = true;
if (keyboardHeight == 0 && keyboardShowing) {
dialog.dismiss();
dialog = null;
}
return true;
}
});
}
});
} else {
if (!visible && dialog != null) {
dialog.dismiss();
}
}
}
public static class PassThroughEditable implements Editable {
@Override
public char charAt(int index) {
Log.d("Editable", "charAt");
return 0;
}
@Override
public int length() {
Log.d("Editable", "length");
return 0;
}
@Override
public CharSequence subSequence(int start, int end) {
Log.d("Editable", "subSequence");
return null;
}
@Override
public void getChars(int start, int end, char[] dest, int destoff) {
Log.d("Editable", "getChars");
}
@Override
public void removeSpan(Object what) {
Log.d("Editable", "removeSpan");
}
@Override
public void setSpan(Object what, int start, int end, int flags) {
Log.d("Editable", "setSpan");
}
@Override
public int getSpanEnd(Object tag) {
Log.d("Editable", "getSpanEnd");
return 0;
}
@Override
public int getSpanFlags(Object tag) {
Log.d("Editable", "getSpanFlags");
return 0;
}
@Override
public int getSpanStart(Object tag) {
Log.d("Editable", "getSpanStart");
return 0;
}
@Override
public <T> T[] getSpans(int arg0, int arg1, Class<T> arg2) {
Log.d("Editable", "getSpans");
return null;
}
@Override
public int nextSpanTransition(int start, int limit, Class type) {
Log.d("Editable", "nextSpanTransition");
return 0;
}
@Override
public Editable append(CharSequence text) {
Log.d("Editable", "append: " + text);
return this;
}
@Override
public Editable append(char text) {
Log.d("Editable", "append: " + text);
return this;
}
@Override
public Editable append(CharSequence text, int start, int end) {
Log.d("Editable", "append: " + text);
return this;
}
@Override
public void clear() {
Log.d("Editable", "clear");
}
@Override
public void clearSpans() {
Log.d("Editable", "clearSpanes");
}
@Override
public Editable delete(int st, int en) {
Log.d("Editable", "delete, " + st + ", " + en);
return this;
}
@Override
public InputFilter[] getFilters() {
Log.d("Editable", "getFilters");
return new InputFilter[0];
}
@Override
public Editable insert(int where, CharSequence text) {
Log.d("Editable", "insert: " + text);
return this;
}
@Override
public Editable insert(int where, CharSequence text, int start, int end) {
Log.d("Editable", "insert: " + text);
return this;
}
@Override
public Editable replace(int st, int en, CharSequence text) {
Log.d("Editable", "replace: " + text);
return this;
}
@Override
public Editable replace(int st, int en, CharSequence source, int start, int end) {
Log.d("Editable", "replace: " + source);
return this;
}
@Override
public void setFilters(InputFilter[] filters) {
Log.d("Editable", "setFilters");
}
}
@Override
public boolean onTouch(View view, MotionEvent e) {
return false;
}
@Override
public boolean onKey(View view, int keycode, KeyEvent e) {
return false;
}
}