android keyboard show on the web

Android에서 Keyboard 작동을 위한 요소

  • Input Method Manager : 클라이언트 Side의 application context에 위치 하면서 Android 전체 시스템 상 프로세스 간의 통로 역할을 한다
  • Input Method (IME & Keyboard) : 쉽게 keyboard라고 생각하면 된다. 사용자의 text를 생성하고 입력할 수있게 하는 역할을 한다. IME는 오직 한번에 하나만 화면에 표시 될 수있다.
  • Client Application : IME을 사용하는 대상 application이 되며, 한번에 오직 하나의 입력 화면(소스를 보면 View라고 생각된다)과 Active 상태를 유지 할 수있다.
InputMethd Framework (IMF) 의 보안적 제한

Input Method의 경우 사용자의 모든 입력을 확인 할 수있기 때문에, 보안적인 제한이 걸려있다.

  • IME interface 접근 : 오직 Manifest.permission.BIND_INPUT_METHOD 권한을 통해서만 접근 가능
  • Clinet는 Input method를 사용할때 InputMethodSession 인터페이스에서 주어진 Access를 통해서만 가능하다. 아래 코드는 InputMethodManager의 코드 일부분이다 이때 windowToken을 통해 hide 시킬 권한을 얻어오는 것을 볼 수있다
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
        return hideSoftInputFromWindow(windowToken, flags, null);
    }
  • Input Method는 화면이 off 되어있는 상태에서 절대로 사용될 수 없다.
  • 신규 IME를 인스톨 할경우 IME의 변환은 반듯이 사용자의 손에 의해서 만 변경 될 수있다.

IME의 Lifecycle

이하 Lifecycle은 InputMethodService : https://developer.android.com/reference/android/inputmethodservice/InputMethodService가 작동하는 순서를 나타내고 있다

  • onCreateInputView() : 사용자 입력을 받기 위해 View를 init한다. (여기서 init되는 화면은 단순히 softkey 뿐만이 아닌 Draw 입력 등도 포함된다) User interface의 생성이라고 생각하면 될 듯 하다. 아래 코드는 IMS를 Override하여 어떻게 UI를 생성하는지 보여주는 예이다.

        public View onCreateInputView() {
            if (mKeyboardView != null) {
                mKeyboardView.closing();
            }
    
            updateKeyboardThemeAndContextThemeWrapper(
                    mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
            mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
                    R.layout.input_view, null);
            mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
    
            mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
            mKeyboardView.setKeyboardActionListener(mLatinIME);
            return mCurrentInputView;
        }
  • onEvaluateInputViewShown() : hard keyboard 등이 달려 있는 단말의 경우 soft keyboard가 보일 필요가 없다. 이와같이 keyboard를 사용자에게 보여줄지를 확인 하는 메서드이다. (물론 softkeyboard가 열려 있다면 return 은 true가 될 것이다)
    아래는 IMS의 code이다. false가 return 되면 사용자에게 보여주면 된다.

    public boolean onEvaluateInputViewShown() {
        if (mSettingsObserver == null) {
            Log.w(TAG, "onEvaluateInputViewShown: mSettingsObserver must not be null here.");
            return false;
        }
        if (mSettingsObserver.shouldShowImeWithHardKeyboard()) {
            return true;
        }
        Configuration config = getResources().getConfiguration();
        return config.keyboard == Configuration.KEYBOARD_NOKEYS
                || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
    }
  • onStartInput() & onStartInputView() : text 입력의 시작을 알리는 부분으로, 해당 메서드의 call 순서는 onStartInput이 일어나고 그다음에 onStartInputView가 Call되게 된다. 해당 Call의 순서처리에 따라서 lifecycle이 다소 달라지는데 해당 내용은 "Webview에서의 Keyboard 처리"에서 상세하게 다룰것이다. IMS에서는 doStartInput이 발생할 경우 onStartInput을 요청하고 이후에 InputView를 call하는 것을 확인 할 수 있다.

    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;
        mStartedInputConnection = ic;
        mInputEditorInfo = attribute;
        initialize();
        if (DEBUG) Log.v(TAG, "CALL: onStartInput");
        onStartInput(attribute, restarting);
        if (mWindowVisible) {
            if (mShowInputRequested) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
                if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
                mCandidatesViewStarted = true;
                onStartCandidatesView(mInputEditorInfo, restarting);
            }
        }
    }

onStartInput과 onStartInputView의 차이점을 문서 상으로 확인하기는 쉽지 않다. 코드상으로 보면 onStartInput의 경우는 Local과 같은 설정, onStartInputView는 실제로 보여줄 keyboard에 대한 모든 설정을 처리 하는 것으로 보인다.

Webview에서의 Keyboard 처리

Webview를 이용한 키보드 처리는 크게 3가지로 나눌 수 있다.

  • Webview에서 load된 html의 input 입력시 자동으로 IME Show
  • Webview의 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {을 Override 시켜서 특정 키보드 환경을 지정
  • showSoftInput을 활용 하는 방법
onCreateInputConnection() Override

android.webkit.Webview 객체를 override 하면 된다.

package com.example.app;
...
public class BaseWebView extends WebView {

...

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection ic = super.onCreateInputConnection(outAttrs);


        int original = outAttrs.imeOptions;

        int notMask = ~EditorInfo.IME_MASK_ACTION;

        int maskResult = original & notMask;

        if((outAttrs.inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER){
            Log.d(TAG,"IME Options is Number");
            outAttrs.privateImeOptions="defaultInputmode=numeric;";
        } else {
            Log.d(TAG,"IME Options is English");
            outAttrs.privateImeOptions="defaultInputmode=english;";
        }

        return ic;
    }

InputConnection ic = super.onCreateInputConnection(outAttrs);이 부분은 반듯이 첫번째 code로 와야 한다. 해당 코드가 최초로 오지 않게 되면 load된 html이 어떤 input tag를 focus하고 있는지 알 수가 없게 된다. 이 코드를 통과 하고 나야 비로소 EditorInfo에 의미 있는 값들이 저장되게 된다.

이때 개발자가 확인 가능한 값은 2가지가 있는데 imeOptionsinputType인데

  • imeOptions는 IME가 어떤 형태로 표현될지 어떤 기능을 제공할지에 대한 표현을 제공한다. 해당 값에 대한 표현 방법은 EditorInfo.IME_MASK_ACTION을 기준으로 Action 처리와 FLAG 처리로 분리 된다.
    • Action : GO, NEXT, NONE, PREVIOUS, SEARCH, SEND 등 엔터 버튼에 대한 기능을 정의하게 된다.
    • Flag : ASCII, NAVIGATE,ACCESSORY,NO_ENTER,EXTRACT, FULLSCREEN 등 키보드의 형태를 나타낸다

상위 값들에 대한 정확한 OPTION은 다음 페이지를 참조 해야하나, 실질적으로 해당 Options을 모두 적용한 IME가 많지는 않다.

일단 해당 값들에 대한 마스킹 방법은 다음과 같이 처리 하면된다.

  • FLAG 값을 읽어 올 수있는 방법 (Webview를 이용해서는 해당 옵션을 얻어 올수 없었다.) 비록 html style에 ime-mode가 있다고는 하지만 이와는 관련점이 없었다
        int original = outAttrs.imeOptions;
        int notMask = ~EditorInfo.IME_MASK_ACTION;
        int flag = original & notMask;

ime-mode는 크롬등에서 호환되지 않고, deprecated 된 기능이다

  • Action 값을 읽어 올수 있는 방법 (역시 Webview에서는 의미 있는데이터를 얻어 올 수 없었다)
    int action = original & EditorInfo.IME_MASK_ACTION;

결과적으로 이시점에 개발자가 처리 할 수있는 값은 inputType이다. 해당 값은 다음과 같은 코드로 읽어 올 수있다.

int inputType = outAttrs.inputType & EditorInfo.TYPE_MASK_CLASS

MASK로 얻어올수 있는 값은 text, number, phone, datetime 정도이고 webview에서 의미 있는 값은 text와 number 정도가 될 것이다.

TYPE_CLASS_TEXT, TYPE_CLASS_NUMBER, TYPE_CLASS_PHONE, TYPE_CLASS_DATETIME

아래 코드는 inputType을 바탕으로 어떤 keyboard를 표현 하게 할지 나타내는 코드이다.

    if((outAttrs.inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER)     {
        Log.d(TAG,"IME Options is Number");
       ...
    } else {
        Log.d(TAG,"IME Options is English");
        ...
    }

그러나 상위와 같은 코드는 아주 특별한 경우를 제외하고는 사용하지 않는다. 그 이유는 어떤 종류의 keyboard를 보여 줄지를 정의 하는건 IME의 onStartInputView내부에서 처리가 될것이기 때문이다.

일반적으로 onCreateInputConnection을 사용하는 이유는 IME로 넘어 가기전에 EditorInfo를 수정하기 위해서이다. 예를 들자면 outAttrs.inputType에 EditorInfo.TYPE_CLASS_NUMBER를 주입하게 되면 해당 webview를 사용한 html은 number type의 키보드만 보게 되는 것이다.

InputMethodManager를 이용한 showSoftInput처리
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mWebView, 0);

InputMethodManager를 시스템 서비스에서 직점 instance를 얻어와서 특정 view (여기서는 mWebView)와 IME를 연결시키는 코드이다. 해당 코드는 Webview상 어떤 html element와 access를 해야할지 알기가 어렵기 때문에 어떤 keyboard를 강제로 뛰울 수가 없다.

비록 Webview를 상속 받아서 onCreateInputConnection()을 재정의 한다고 하더라도 이경우에는 해당 Method를 타지 않는다. 그렇기 때문에 EditorInfo 역시 추출 할 수가 없는 상태가 된다. 이렇다 보니 무조건 default keyboard만 나타나게 되는데, 이 부분을 해결 하는 방법은 webview에 inputType을 추가 해주는 것이다.

public class BaseWebView extends WebView {

    int inputType = EditorInfo.TYPE_NULL;

    public void setInputType(int type) {
        inputType = type;
    }

    public int getInputType() {
        return inputType;
    }

그리고 해당 내용을 아래와 같이 추가해 준다.

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
mWebView.setInputType(InputType.TYPE_CLASS_NUMBER);
imm.showSoftInput(mWebView, 0);

onStartInputView에서 inputType을 확인 한 후 number keyboard를 보여 주게 될것이다.

imeOptions나 다른 파라메터들은 상위와 같이 강제 Setting을 해도 keyboard에 별다를 영향을 주지 못했다.

Keyboard 처리에 대한 IME Call Flow

사실 상위 두 가지의 Call Flow가 다를 거라고 크게 생각하지 못했으나 의외로 전혀 다른 process를 타고 있었다.

아래는 https://github.com/rkkr/simple-keyboard의 소스에 Log를 붙여서 어떤 flow를 trace한 표이다.

html에서 input tag를 눌러서 keyboard 표시 했을 때

//input을 이용한 show
05-24 09:53:41.362 13828 13828 D LatinIME: onStartInput = 24578: rkr.simplekeyboard.inputmethod
05-24 09:53:41.362 13828 13828 D LatinIME: onStartInput editorInfo.inputyType 24578: rkr.simplekeyboard.inputmethod
05-24 09:53:41.363 13828 13828 D LatinIME: executePendingImsCallback editorInfo.inputyType 24578: rkr.simplekeyboard.inputmethod
05-24 09:53:41.363 13828 13828 D LatinIME: onStartInputInternal editorInfo inputType24578: rkr.simplekeyboard.inputmethod
05-24 09:53:41.364 13828 13828 D LatinIME: onShowInputRequested: rkr.simplekeyboard.inputmethod
05-24 09:53:41.364 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:53:41.364 13828 13828 D LatinIME: onEvaluateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:53:41.364 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:53:41.371 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:53:41.371 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:53:41.375 13828 13828 D LatinIME: onStartInputView: rkr.simplekeyboard.inputmethod
05-24 09:53:41.376 13828 13828 D LatinIME: executePendingImsCallback editorInfo.inputyType 24578: rkr.simplekeyboard.inputmethod
05-24 09:53:41.395 13828 13828 I LatinIME: Starting input. Cursor position = 1,1: rkr.simplekeyboard.inputmethod
05-24 09:53:41.395 13828 13828 D LatinIME: onEvaluateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:53:41.395 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:53:41.395 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:53:41.395 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:53:41.396 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:53:41.411 13828 13828 D LatinIME: loadSettings: rkr.simplekeyboard.inputmethod
05-24 09:53:41.422 13828 13828 D LatinIME: getCurrentAutoCapsState: rkr.simplekeyboard.inputmethod
05-24 09:53:41.422 13828 13828 D LatinIME: getCurrentRecapitalizeState: rkr.simplekeyboard.inputmethod
05-24 09:53:41.426 13828 13828 D LatinIME: shouldShowLanguageSwitchKey: rkr.simplekeyboard.inputmethod
05-24 09:53:41.487 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:53:41.487 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:53:41.498 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:53:41.498 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod



//input hide
05-24 09:54:38.197 13828 13828 D LatinIME: hideWindow: rkr.simplekeyboard.inputmethod
05-24 09:54:38.197 13828 13828 D LatinIME: isShowingOptionDialog: rkr.simplekeyboard.inputmethod
05-24 09:54:38.197 13828 13828 D LatinIME: onFinishInputView: rkr.simplekeyboard.inputmethod
05-24 09:54:38.197 13828 13828 D LatinIME: onFinishInputViewInternal: rkr.simplekeyboard.inputmethod
05-24 09:54:38.200 13828 13828 D LatinIME: onWindowHidden: rkr.simplekeyboard.inputmethod
05-24 09:54:38.202 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:54:38.202 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:54:38.230 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:54:38.230 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
showSoftInput을 처리 했을 때

//keyboard show
05-24 09:48:48.369 13828 13828 D LatinIME: onShowInputRequested: rkr.simplekeyboard.inputmethod
05-24 09:48:48.369 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:48:48.369 13828 13828 D LatinIME: onEvaluateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:48:48.369 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:48:48.370 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:48:48.370 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:48:48.371 13828 13828 D LatinIME: onStartInputView: rkr.simplekeyboard.inputmethod
05-24 09:48:48.372 13828 13828 D LatinIME: executePendingImsCallback editorInfo.inputyType 49313: rkr.simplekeyboard.inputmethod
05-24 09:48:48.382 13828 13828 I LatinIME: Starting input. Cursor position = 0,0: rkr.simplekeyboard.inputmethod
05-24 09:48:48.382 13828 13828 D LatinIME: onEvaluateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:48:48.382 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:48:48.383 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:48:48.383 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:48:48.383 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:48:48.395 13828 13828 D LatinIME: loadSettings: rkr.simplekeyboard.inputmethod
05-24 09:48:48.402 13828 13828 D LatinIME: getCurrentAutoCapsState: rkr.simplekeyboard.inputmethod
05-24 09:48:48.402 13828 13828 D LatinIME: getCurrentRecapitalizeState: rkr.simplekeyboard.inputmethod
05-24 09:48:48.404 13828 13828 D LatinIME: shouldShowLanguageSwitchKey: rkr.simplekeyboard.inputmethod
05-24 09:48:48.440 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:48:48.440 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
05-24 09:48:48.452 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:48:48.452 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod

//keyboard hide
05-24 09:52:19.722 13828 13828 D LatinIME: hideWindow: rkr.simplekeyboard.inputmethod
05-24 09:52:19.723 13828 13828 D LatinIME: isShowingOptionDialog: rkr.simplekeyboard.inputmethod
05-24 09:52:19.723 13828 13828 D LatinIME: onFinishInputView: rkr.simplekeyboard.inputmethod
05-24 09:52:19.723 13828 13828 D LatinIME: onFinishInputViewInternal: rkr.simplekeyboard.inputmethod
05-24 09:52:19.725 13828 13828 D LatinIME: onWindowHidden: rkr.simplekeyboard.inputmethod
05-24 09:52:19.728 13828 13828 D LatinIME: updateFullscreenMode: rkr.simplekeyboard.inputmethod
05-24 09:52:19.728 13828 13828 D LatinIME: updateSoftInputWindowLayoutParameters: rkr.simplekeyboard.inputmethod
05-24 09:52:19.748 13828 13828 D LatinIME: onComputeInsets: rkr.simplekeyboard.inputmethod
05-24 09:52:19.748 13828 13828 D LatinIME: isImeSuppressedByHardwareKeyboard: rkr.simplekeyboard.inputmethod
728x90
반응형

'Android' 카테고리의 다른 글

AOSP system app install  (0) 2020.08.20
Android Grafika Texture Surface  (0) 2020.03.27
android memory leak 처리  (0) 2020.03.27
android graphic architecture  (0) 2020.03.27
android ART GC Log  (0) 2020.03.27