InputMethodServiceを使ってIMを作るメモ

2013-11-06T00:00:00+00:00 Android Java Python

ネタ帳からの大体そのままな引用(要は雑って事)。以前、Androidのソフトウェアキーボードを作って、PC側とbluetoothを経由してスマフォ上のテキスト入力をPCから入力させるとかっていうのをやってた。とりあえず書いておこうかと

方式

bluetooth RFCOMMなプロファイルを利用してPC側からスマフォ側にbluetooth経由でテキストを送信。それをアクティブなIMで受け取って入力を反映させるっていうだけ

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.kinjouj.app.bluetooth_keyboard"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <service android:name=".BluetoothKeyboardService" android:label="@string/app_name" android:permission="android.permission.BIND_INPUT_METHOD">
            <intent-filter>
                <action android:name="android.view.InputMethod" />
            </intent-filter>
            <meta-data android:name="android.view.im" android:resource="@xml/inputmethod" />
        </service>
    </application>
</manifest>

res/layout/input.xml

<?xml version="1.0" encoding="utf-8"?>
<android.inputmethodservice.KeyboardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/keyboard"
    android:layout_alignParentBottom="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

後々必要になるので

res/xml/inputmethod.xml

<?xml version="1.0" ?>
<input-method
    xmlns:android="http://schemas.android.com/apk/res/android" />

設定要件はR.styleable#InputMethodを参照

res/xml/keyboard.xml

ソフトウェアキーボードなレイアウトを定義

<?xml version="1.0" ?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android">
    <Row android:rowEdgeFlags="top" android:keyHeight="100px">
        <Key
            android:codes="-5"
            android:keyIcon="@*android:drawable/sym_keyboard_delete"
            android:keyWidth="50%p"
            android:keyEdgeFlags="right"
            android:isRepeatable="true" />

        <Key
            android:codes="10"
            android:keyIcon="@*android:drawable/sym_keyboard_return"
            android:keyWidth="50%p"
            android:keyEdgeFlags="right"/>

    </Row>
</Keyboard>

codesで指定されたコードはキーをタッチしたい等な場合に引数に指定される(primaryCodeとか)。又、設定要件は http://developer.android.com/reference/android/inputmethodservice/Keyboard.html を参照

まぁ設定ファイル系はこれだけ。あとはサービスクラスを書いたりとか

BluetoothKeyboardService.java

package net.kinjouj.app.bluetooth_keyboard;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.KeyboardView;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;

import static android.view.KeyEvent.*;

public class BluetoothKeyboardService extends InputMethodService implements KeyboardView.OnKeyboardActionListener {

    private final String TAG = getClass().getSimpleName();

    private BluetoothServerSocket server;
    private BluetoothSocket socket;
    private KeyboardView kv;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "onCreate");
    }

    @Override
    public void onInitializeInterface() {
        super.onInitializeInterface();
        Log.v(TAG,"onInitializeInterface");
    }

    @Override
    public View onCreateInputView() {
        kv = (KeyboardView)getLayoutInflater().inflate(R.layout.input, null);
        kv.setKeyboard(new Keyboard(this, R.xml.keyboard));
        kv.setOnKeyboardActionListener(this);

        return kv;
    }

    @Override
    public View onCreateCandidatesView() {
        return super.onCreateCandidatesView();
    }

    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);

        Log.v(TAG, "onStartInput");

        if(server == null) {
            startBluetooth();
        }
    }

    @Override
    public void onFinishInput() {
        super.onFinishInput();

        if(kv != null) {
            kv.closing();
        }
    }

    public void onKey(int primaryCode, int[] keyCodes) {
    }

    public void onPress(int primaryCode) {
        Log.v(TAG, "onPress");
        Log.v(TAG, "primaryCode: " + primaryCode);

        InputConnection conn = getCurrentInputConnection();
        switch(primaryCode) {
            case 10:
                //shutdownBluetooth();

                conn.sendKeyEvent(new KeyEvent(ACTION_DOWN, KEYCODE_ENTER));
                conn.sendKeyEvent(new KeyEvent(ACTION_UP, KEYCODE_ENTER));

                break;

            case -5:
                // deleteキーを押した場合の動作
                break;
        }
    }

    public void onRelease(int primaryCode) {
    }

    public void onText(CharSequence text) {
    }

    public void swipeDown() {
    }

    public void swipeLeft() {
    }

    public void swipeRight() {
    }

    public void swipeUp() {
    }

    private void startBluetooth() {
        try {
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

            server = adapter.listenUsingRfcommWithServiceRecord(
                "RFCOMM Service",
                UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
            );

            new Thread("server") {
                @Override
                public void run() {
                    while(true) {
                        try {
                            socket = server.accept();

                            if(socket != null) {
                                InputStream is = null;

                                try {
                                    is = socket.getInputStream();

                                    BufferedReader br = null;

                                    try {
                                        br = new BufferedReader(new InputStreamReader(is));

                                        String str = null;

                                        while((str = br.readLine()) != null) {
                                            InputConnection conn = getCurrentInputConnection();

                                            if(conn != null) {
                                                if(!TextUtils.isEmpty(str)) {
                                                    conn.commitText(str, 0);
                                                }
                                            }
                                        }
                                    } catch(IOException e) {
                                        e.printStackTrace();
                                    } finally {
                                        if(br != null) {
                                            br.close();
                                        }
                                    }
                                } catch(IOException e) {
                                    e.printStackTrace();

                                    break;
                                }
                            }
                        } catch(IOException e) {
                            e.printStackTrace();

                            break;
                        }
                    }
                }
            }.start();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    private void shutdownBluetooth() {
        try {
            if(socket != null) {
                socket.close();
                socket = null;
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

キーボード入力が開始される(ソフトウェアキーボードが起動される?)際にbluetoothの接続を行なってRFCOMMプロファイルで要求を待ち受ける。でテキストな要求が来た際にはソフトウェアキーボードに対してテキストをコミットする。まぁそんだけ

動かしてみる

アプリを入れるとキーボード設定で

っていうように表示されるようになる。でチェックを入れておかないと使えるようにならないのでチェックしておく

あとはテキストを入力するフォーカスで入力方式でBluetoothKeyboardを選択すると

って言うように出る。あとはPC側から入力を反映させる適当なスクリプトを書く

from bluetooth import find_service,RFCOMM,BluetoothSocket,BluetoothError

host = None
port = 0

for service in find_service():
    if service["protocol"] == "RFCOMM" and service["name"] == "RFCOMM Service":
        host = service["host"]
        port = service["port"]

if host is not None:
    sock = BluetoothSocket(RFCOMM)
    sock.connect((host, port))

    print "Connected: %s:%d" % (host, port)

    while True:
        data = raw_input("message: ")

        if data is not None and len(data) > 0:
            try:
                sock.send("%sn" % data)
            except BluetoothError, e:
                break

    sock.close()

っていう感じでやればPC側からスマフォ側にbluetoothを経由してIMでキャッチしてテキスト入力をさせたりとかっていう事案もまぁ出来るっちゃ出来る

FuelPHPをやってみる (30) - Model_Nestedset - SpeechRecognizerを使って音声テキスト及び音声データを取得する方法