Image Keyboardをいらすとやで作ってみた

# 作ったもの
https://github.com/oxsoft/Irasutoya

![device-2017-01-09-204311.gif](https://qiita-image-store.s3.amazonaws.com/0/92361/20f77c37-e214-806d-6ba9-f4a84a7195b7.gif)

手っ取り早く試したい場合は[apk](https://github.com/oxsoft/Irasutoya/releases/tag/v1.0)をどうぞ。

# Image Keyboard
前回はこんな記事を書きました。

App ShortcutsはいいからまずはImage Keyboardをサポートしたほうがいい
http://qiita.com/oxsoft/items/47337bfa137e2b03b66c

サポートライブラリを使うことで、キーボードから画像を入力することができるようになりました。
今回はこれを、みんな大好き[いらすとや](http://www.irasutoya.com)で作ってみました :muscle: 

# キーボードの作成
## Serviceの作成
キーボードを作るので、`InputMethodService`を継承したクラスを作成します。

```java:MainService.java
public class MainService extends InputMethodService {
    @Override
    public View onCreateInputView() {
        View inputView = getLayoutInflater().inflate(R.layout.view_keyboard, null);
        // inputViewの設定
        return inputView;
    }

    @Override
    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
        // 入力欄に応じた処理
    }
}
```

## xmlの作成
キーボードの情報をシステムに伝えるために、res/xmlにmethod.xmlを作成します。

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

## AndroidManifestの修正
作成したServiceとxmlをAndroidManifestで宣言します。

```xml:AndroidManifest.xml
<manifest ...>
    <application ...>
        ...
        <service
            android:name=".MainService"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_INPUT_METHOD">
            <meta-data
                android:name="android.view.im"
                android:resource="@xml/method" />
            <intent-filter>
                <action android:name="android.view.InputMethod" />
            </intent-filter>
        </service>
    </application>
</manifest>
```

ここまでは、今までのキーボード作成と同じですね。

# Image Keyboardをサポートしているかチェック
入力欄がImage Keyboardをサポートしていなければ何もできないので、onStartInputView内でチェックします。
基本的には[Adding Image Support to IMEs](https://developer.android.com/guide/topics/text/image-keyboard.html#adding_image_support_to_imes)に書いてある通りですが、今回扱う画像はpngなので、そこだけ修正します。

```java:MainService.java
@Override
public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
    String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);

    boolean pngSupported = false;
    for (String mimeType : mimeTypes) {
        if (ClipDescription.compareMimeTypes(mimeType, "image/png")) {
            pngSupported = true;
        }
    }

    // サポートしていない場合はその旨を表示
    unsupported.setVisibility(pngSupported ? View.GONE : View.VISIBLE);
}
```

# 画像を送信
これも[Adding Image Support to IMEs](https://developer.android.com/guide/topics/text/image-keyboard.html#adding_image_support_to_imes)に書いてある通りですが、gifをpngにしました。
ただ今回は、web上にある画像を使っているので、付加情報としてlinkUriを付けるようにしました。（Gboardもそうなっていたので）

```java:MainService.java
private void commitPngImage(Uri contentUri, String imageDescription, Uri linkUri) {
    InputContentInfoCompat inputContentInfo = new InputContentInfoCompat(contentUri, new ClipDescription(imageDescription, new String[]{"image/png"}), linkUri);
    InputConnection inputConnection = getCurrentInputConnection();
    EditorInfo editorInfo = getCurrentInputEditorInfo();
    int flags = 0;
    if (android.os.Build.VERSION.SDK_INT >= 25) {
        flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
    }
    InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null);
}
```

# 画像の取得
今回は[いらすとや](http://www.irasutoya.com)を題材にしているので、クロールして画像をアプリ内にキャッシュします。okhttp3を使うと以下のような感じです。詳しくはGitHubの方を参照してください。

```java
Request request = new Request.Builder().url("画像のURL").build();
Response response = new OkHttpClient().newCall(request).execute();
InputStream input = response.body().byteStream();
OutputStream output = openFileOutput("画像のファイル名", MODE_PRIVATE);
byte[] buffer = new byte[65536];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
    output.write(buffer, 0, bytesRead);
}
response.body().close();
```

# ContentProviderの設定
ContentProviderを用意することで、アプリ内のデータを外部に提供することができます。[FileProvider](https://developer.android.com/reference/android/support/v4/content/FileProvider.html)を使うことで、独自クラスを作成することなく、簡単に実装できます。

## file_pathsの用意
xml/file_paths.xmlを以下のように作成します。

```xml:file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="images"
        path="/" />
</paths>
```

今回はルートディレクトリにしていますが、ディレクトリを分けた方がいいかもしれません。

## AndroidManifestの修正
FileProviderのauthoritiesを設定し、先ほどのxmlを指定します。

```xml:AndroidManifest.xml
<manifest ...>
    <application ...>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.oxsoft.irasutoya.content"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
</manifest>
```

## uriの取得
以上の設定をすることで、以下のように画像のuriを取得することができます。authoritiesをAndroidManifestに書いたものと一致させる必要があります。

```java
File file = getFileStreamPath("画像のファイル名");
Uri uri = FileProvider.getUriForFile(this, "com.oxsoft.irasutoya.content", file);
```

このuriを、上で作成したcommitPngImageに渡せば、画像が入力できるというわけです。

# まとめ
意外と簡単に画像入力を実装できることが分かりました。とはいえまだまだ画像入力に対応しているアプリが少ないですね :sweat_smile: 

また、日本語入力を独自に実装することが難しかったため、Gboardにあるような検索機能をつけることができませんでした :sweat_smile: 

何か間違いがあれば指摘していただけると幸いです :bow: