Android では、FileProvider を使って他のアプリから自分のアプリ内のファイルへ読み取り権限を与えることができる。それを Unity でやる。

概要

  • Twitter アプリにスクリーンショット画像ファイルの読み取り権限を与えるために、FileProvider を使う
  • res ディレクトリをビルドに含めるために、project.properties ファイルを使って Unity に Android Library として認識させる

環境

  • Android 6.0.1
  • Unity 5.3.1p3

手順

スクリーンショットを撮る

Application.CaptureScreenshot で撮る。

FileProvider を定義する

に従って定義すれば良い。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.mygame">
    <application>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.mygame.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>
    </application>
</manifest>

res/xml/filepaths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path path="shared_images/" name="images" />
</paths>

FileProvider を使う

FileProvider を使って、共有可能な URI を取得して、それを Intent に渡す。さらに、URI に読み取り権限を与える必要がある。

詳しくは、FileProvider ドキュメントの「Granting Temporary Permissions to a URI」の項を読む。今回は Intent.setFlags することにした。

DDDTwitter.java

package com.kakeragames.dddtwitter;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.content.FileProvider;

import com.unity3d.player.UnityPlayer;

import java.io.File;
import java.io.IOException;

public class DDDTwitter {

    private static final String SHARED_IMAGES_PATH = "shared_images";
    private static final String SHARED_IMAGES_AUTHORITY = "com.example.mygame.fileprovider";

    public void post(String message, String imagePath) {
        Activity currentActivity = UnityPlayer.currentActivity;

        IntentBuilder intentBuilder = new IntentBuilder();
        intentBuilder.setMessage(message);

        if (imagePath != null && !imagePath.isEmpty()) {
            FileSharer sharer = new FileSharer(currentActivity);
            try {
                File sharedImage = sharer.share(new File(imagePath), SHARED_IMAGES_PATH);
                Uri sharedImageUri = FileProvider.getUriForFile(currentActivity, SHARED_IMAGES_AUTHORITY, sharedImage);
                intentBuilder.setImage(sharedImageUri);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        Intent tweet = intentBuilder.build();
        tweet.setPackage("com.twitter.android");

        if (tweet.resolveActivity(currentActivity.getPackageManager()) != null) {
            currentActivity.startActivity(tweet);
            return;
        }

        Intent share = intentBuilder.build();
        currentActivity.startActivity(share);
    }
}

IntentBuilder

Intent は Builder のクラスに作らせてみた。setXxxxx するものがそんなに無いので、あんまり効いた感じがしなくもない。

IntentBuilder.java

package com.kakeragames.dddtwitter;

import android.content.Intent;
import android.net.Uri;

public class IntentBuilder {

    private String mMessage;
    private Uri mImageUri;

    public IntentBuilder setMessage(String message) {
        mMessage = message;
        return this;
    }

    public IntentBuilder setImage(Uri imageUri) {
        mImageUri = imageUri;
        return this;
    }

    public Intent build() {
        Intent intent = new Intent(Intent.ACTION_SEND);

        if (mImageUri != null) {
            intent.setType("image/png");
            intent.putExtra(Intent.EXTRA_STREAM, mImageUri);

            // For images shared with FileProvider
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setType("text/plain");
        }

        intent.putExtra(Intent.EXTRA_TEXT, mMessage);
        return intent;
    }
}

共有ディレクトリ以外のファイルへの対応

DDDTwitter.postimagePath に、共有ディレクトリ(shared_images)以外のファイルが投げられても良いように、 ファイルを共有ディレクトリ内にコピーするようにした。

ファイル操作するなら Commons IO - Commons IO Overview を使いたいところではあるけど、 依存する jar が増えるのと、そもそもコード量が少ないことから、今回は使わなかった。

FileSharer.java

package com.kakeragames.dddtwitter;

import android.content.Context;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class FileSharer {

    private Context mContext;

    public FileSharer(Context context) {
        mContext = context;
    }

    public File share(File original, String sharedPath) throws IOException {
        File sharedDirectory = new File(mContext.getFilesDir(), sharedPath);
        sharedDirectory.mkdirs();

        File sharedFile = new File(sharedDirectory, original.getName());
        FileSharer.copy(original, sharedFile);

        return sharedFile;
    }

    private static void copy(File from, File to) throws IOException {
        FileChannel input = null;
        FileChannel output = null;
        try {
            input = new FileInputStream(from).getChannel();
            output = new FileOutputStream(to).getChannel();
            output.transferFrom(input, 0, input.size());
        } finally {
            if (input != null) {
                input.close();
            }
            if (output != null) {
                output.close();
            }
        }
    }
}

Unity に取り込む

Assets/Plugins/Android/DDDTwitter/AndroidManifest.xml
Assets/Plugins/Android/DDDTwitter/libs/dddtwitter.jar
Assets/Plugins/Android/DDDTwitter/project.properties
Assets/Plugins/Android/DDDTwitter/res/xml/filepaths.xml
Assets/Plugins/Android/android-support-v4.jar

という構成で取り込む。

注意点 1: Android Library の定義ファイルを追加する

project.properties

android.library=true

というファイルを追加する必要がある。これがないと、Unity が DDDTwitter を android-libraries として認識してくれない。

Android Studio では、project.properties が生成されないので、自分でファイルを作る。

注意点 2: Assets/Plugins/Android 内に置かないとだめ

jar ファイル単体の場合、他の場所(例えば、Assets/DDDTwitter/Plugins/Android とか)に置いても、plugins として認識されてビルドに含まれる。 しかし res や AndroidManifest をビルドに含めるためには、Assets/Plugins/Android 直下に置くか、ディレクトリを切って project.properties と一緒に配置する必要がある。

注意点 3: android-support-v4.jar が必要

FileProvider を使うために必要。Android SDK から持ってくればいい。

android-support-v4.jar とか google-play-services.jar とかが複数含まれていてビルドに失敗することはままあるので、 こういった、プラグインの枠を超えて使われるライブラリは、Assets/Plugins/Android 直下など分かりやすいところに置いた方が良いと思う。

まとめ・感想

Unity Android でも、FileProvider を使うことができた。 FileProvider の使い方自体はまったく難しくなかったが、Unity が res/xml をビルドに含めてくれなくって、かなりハマった。