Xamarin.AndroidでAndroid向けIMEを作る

# はじめに

Visual Studio 2017 community(2017/9/27時点)のXamarin.Androidを使ってAndroid向けのIME(カスタムキーボード)を作る方法およびコツをまとめます。

# 1.新規ソリューションを立ち上げる

メニューバーの`ファイル`->`新しいソリューション`からソリューション立ち上げのモーダルを開きます。
<img width="600" alt="説明1.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/9c51d241-d9b4-1347-f25f-47efcb8e68e3.png">
`Android` -> `アプリ` -> `空のAndroidアプリ`を選択して`次へ`を押します。

<img width="600" alt="説明2.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/8e2cda36-af88-d743-a92f-91a094f9a2dd.png">
今回はアプリ名を**DroidCustomKeyboard**にして進めていきます。

<img width="600" alt="説明3.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/5fac8564-bf48-21b7-6281-4fda4b0d0022.png">
設定をいじらず`作成`を押します。

![説明4.png](https://qiita-image-store.s3.amazonaws.com/0/67153/a2401474-7334-eeec-773d-0b1232d0798e.png)
このようにファイルが構成されていればOKです。

# 2.ファイルの下準備をする。

編集の前に必要なファイルがいくつかありますので先に準備をしてしまいます。

まずは`Resources`フォルダに中に`xml`フォルダを作ります。
<img width="500" alt="説明5.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/25fcd8ad-e0c7-f1d0-deb6-ad8b181191ad.png">

`xml`フォルダができたら、さらにその中にXMLファイルを追加します。
<img width="500" alt="説明6.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/31606bef-3286-9a69-960f-5d097ee31a4f.png">

<img width="500" alt="説明7.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/c74dc5f4-ff22-b76e-c963-808bc01543ca.png">
`XML` -> `空のXMLファイル`を選択して、ファイル名を`method`にして`新規`を押してファイルを追加してください。

同様にして`test_keyboard`というXMLファイルを追加してください。

続いて、`layout`フォルダにレイアウトファイルを追加します。
<img width="500" alt="説明8.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/b7e4981a-fa57-7aa8-9d04-6267b2bcb2d5.png">

<img width="500" alt="説明9.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/710bd670-3e47-b68c-d63a-bd736148152f.png">
`Android` -> `レイアウト`を選択して、ファイル名を`preview`にして`新規`を押してファイルを追加してください。

また、`Main.axml`の名前を変更して`keyboard.axml`にします。
![説明10.png](https://qiita-image-store.s3.amazonaws.com/0/67153/bc634849-68e5-a4db-b456-8bde692cd7c2.png)

同様に、`MainActivity.cs`の名前も変更して`TestKeyboard.cs`にします。
![説明11.png](https://qiita-image-store.s3.amazonaws.com/0/67153/be2502b1-759e-b2f1-39ca-e363ded62fdd.png)

ここまでで、下のようになっていれば下準備はOKです。
![説明12.png](https://qiita-image-store.s3.amazonaws.com/0/67153/e4035053-85df-bce0-6c2a-680f5601c4cc.png)

# 3.コーディング１：`Strings.xml`に追記

`Resources`/`values`/`Strings.xml`に一行追記します。

```xml:Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="app_name">DroidCustomKeyboard</string>
	<string name="test_keyboard">TestKeyboard</string> <!--追記-->
</resources>
```

# 4.コーディング２：`keyboard.axml`の書き換え

`Resources`/`layout`/`keyboard.axml`を書き換えます。
これがいわゆるキーボード全体のビューになります。

```xml:keyboard.axml_書き換え前
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

```xml:keyboard.axml_書き換え後
<?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_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:keyPreviewLayout="@layout/preview" />
```

# 5.コーディング３：`preview.axml`の書き換え

`Resources`/`layout`/`preview.axml`を書き換えます。
このファイルはキーボードのボタンを押した時にキーの上に表示される小さい矩形のレイアウトを担っているようです。

```xml:preview.axml書き換え前
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

```xml:preview.axml書き換え後
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="#CFD8DC"
    android:textStyle="bold"
    android:textSize="45sp" />
```

# 6.コーディング４：`method.xml`を実装

`Resources`/`xml`/`method.xml`を実装します。
この`method.xml`がきちんと実装できていないと、Androidの環境設定のIME一覧にキーボードが表示されません。
`@string/test_keyboard`は`values`/`Strings.xml`内で定義したものがそのまま呼び出されます。

```xml:method.xml
<?xml version="1.0" encoding="UTF-8" ?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
    <subtype
        android:label="@string/test_keyboard"
        android:imeSubtypeLocale="en_US"
        android:imeSubtypeMode="keyboard" >
	</subtype>
</input-method>
```

# 7.コーディング５：`test_keyboard.xml`を実装

`Resources`/`xml`/`test_keyboard.xml `を実装します。
このファイルはキーボードの中身のレイアウトを担います。キーを二段にしたい場合は`Row`タグを増やせばOKです。
`android:codes`はAndroidの[KeyEvent](https://developer.android.com/reference/android/view/KeyEvent.html "KeyEvent")を参考にしてください。

```xml:test_keyboard.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="25%p"
    android:horizontalGap="0px"
    android:verticalGap="0px"
    android:keyHeight="50sp" >

    <Row>
        <Key android:codes="29" android:keyLabel="A" android:keyEdgeFlags="left" />
        <Key android:codes="30" android:keyLabel="B" />
        <Key android:codes="31" android:keyLabel="C" />
		<Key android:codes="67" android:keyLabel="Del" android:keyEdgeFlags="right" android:isRepeatable="true"/>
    </Row>
	
</Keyboard>
```

# 8.コーディング６：`TestKeyboard.cs`の実装(書き換え)

この`cs`ファイルがキーボードの入力システム本体になります。
IMEのサービスとするためにInputMethodServiceを継承したクラスを実装します。

```c#:TestKeyboard.cs
using Android.App;
using Android.Content;
using Android.Views;
using Android.Views.InputMethods;
using Android.InputMethodServices;
using Java.Lang;

namespace DroidCustomKeyboard
{

	//チェックポイント１
	[Service(Permission = "android.permission.BIND_INPUT_METHOD")]
	[IntentFilter(new[] { "android.view.InputMethod" })]
	[MetaData("android.view.im", Resource = "@xml/method")]
	public class TestKeyboard : InputMethodService, KeyboardView.IOnKeyboardActionListener
	{
		public KeyboardView kv;
        public Keyboard k;

	    //チェックポイント２
		public override View OnCreateInputView()
		{
            kv = (KeyboardView)this.LayoutInflater.Inflate(Resource.Layout.keyboard, null);
			k = new Keyboard(this, Resource.Xml.test_keyboard);
			kv.Keyboard = k;
			kv.OnKeyboardActionListener = this;
			return kv;
		}

	    //チェックポイント３
		public void OnKey(Android.Views.Keycode primaryCode, Android.Views.Keycode[] keyCodes)
		{
			IInputConnection ic = CurrentInputConnection;

			switch (primaryCode)
			{
                case Android.Views.Keycode.Del:
					ic.DeleteSurroundingText(1, 0);
					break;
				default:
                    if (Android.Views.Keycode.A <= primaryCode && primaryCode <= Android.Views.Keycode.Z)
                    {
                        char code = (char)(primaryCode - Android.Views.Keycode.A + 65);
                        ic.CommitText(new Java.Lang.String(code.ToString()), 1);
                    }
                    else if (Android.Views.Keycode.Num0 <= primaryCode && primaryCode <= Android.Views.Keycode.Num9)
                    {
                        char code = (char)(primaryCode - Android.Views.Keycode.Num0 + 48);
                        ic.CommitText(new Java.Lang.String(code.ToString()), 1);
                    }
					break;
			}
		}

	    //チェックポイント４
		public void OnPress(Android.Views.Keycode primaryCode)
		{ }

		public void OnRelease(Android.Views.Keycode primaryCode)
		{ }

		public void OnText(ICharSequence text)
		{ }

		public void SwipeLeft()
		{ }

		public void SwipeRight()
		{ }

		public void SwipeDown()
		{ }

		public void SwipeUp()
		{ }
	}
}
```

## コードの説明

### チェックポイント１

このクラスをIMEのサービスとして認識させるために必要なコードです。これが非常に重要なようで、ブレイクスルーするのが大変でした。Android StudioでIMEを作成する場合はManifestにxml形式で記述するようですが、Xamarin.Androidの場合はこのように直接csファイルに設定を行います。

### チェックポイント２

`OnCreateInputView`にてキーボードが呼び出された時のキーボードのビューを生成しています。`Resource.Layout.keyboard`や`Resource.Xml.test_keyboard`で警告が出ないようにするには、一度プロジェクトをビルドする必要があります。（ビルドをすると`Resource.designer.cs`ファイルに呼び出し用の設定が加わります。）

### チェックポイント３

`OnKey`にてキーが押された時の反応を記述します。
キーコードが手に入るので、それで動作を分岐させます。普通の文字とDeleteやReturnなどのキーを区別するようにします。注意しなくてはならないのは、AndroidやXamarin.Androidで指定されているKeyCodeはASCIIコードとは異なるので、そのまま文字に変換できないという点です。ASCIIやUnicodeにするためのコードが余計に必要です。

### チェックポイント４

`InputMethodService`および`KeyboardView.IOnKeyboardActionListener`を実装する上で必要な最低限のメソッドたちです。とりわけ何か動作をさせたい場合は実装してあげてください。

# 6.実行（IMEのインストール）

まずは、プロジェクトの設定を変更します。
`メニューバー` -> `プロジェクト` -> `DroidCustomKeyboard オプション`を押します。
![説明13.png](https://qiita-image-store.s3.amazonaws.com/0/67153/7a9868ac-c115-909b-cd9d-7200378ef5a5.png)

`実行` -> `構成` -> `Default` の中の**エントリポイント**をサービスにし、**明示的インテント**のサービスを`DroidCustomKeyboard.TestKeyboard`にします。
<img width="700" alt="説明14.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/cc8b7af4-5ab8-2612-b586-b5a0683db101.png">

ここで一旦ビルドします。
![説明15.png](https://qiita-image-store.s3.amazonaws.com/0/67153/9743c96e-d6e1-8f3f-38ce-928f15930fb1.png)

ビルドが成功したら実行します。

設定アプリ -> 言語と入力 -> 現在のキーボード -> キーボードの選択 -> DroidCustomKeyboard をON
<img width="1000" alt="droid1.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/8e344c8c-0a80-f818-3b1b-6de6b13adcc3.png">

どこか文字入力ができるところでIMEを起動します。
IMEを切り替えたら自作のキーボードが使えます。
<img width="400" alt="droid2.png" src="https://qiita-image-store.s3.amazonaws.com/0/67153/a9875a6f-dcc4-5163-72ed-f5e9fb8a7a2b.png">

# 7.終わりに

Android Studioと同様の方法でIMEの実装を試みたところ、IMEのインストールに躓きteratailにお世話になりました。
Xamarin.Androidを使ったAndroidアプリ開発はAndroid Studioでの開発に非常に似ているようですが、実行やビルド設定など細かいところで差があったり、使用できるクラスやメソッドに差があったりして意外と厄介でした。
参考文献も異様に少ないので今後ノウハウが蓄積されると良いですね。