アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

Unityでギャラリーに保存してある画像をアップロードするPluginを作成してみる(#1)

こんにちは。ちゃらいプログラマです。
最近Pluginを作成する機会が増えました。今回はギャラリーに保存されている画像をUnity側で表示/アップロードする処理をご紹介させて頂きます。
まずは、Android側からですが、今まで紹介したPlugin作成のようにUnity側のみで完結できればよかったのですが、残念ながらできませんでした。
大きく以下の流れとなります。

1.Activityからギャラリーを開く処理を投げる
2.ギャラリー側で選択した画像結果を受け取り、Unity側で参照可能なフォルダに保存する
3.保存したファイルをアップロードする

※作業環境は、Unity 2019.2.17f1になります。

Assets/Plugins/Androidフォルダ以下にファイルを作成する

AndroidManifest.xml
UnityPluginActivity.java
 →メイン処理を記述します
android-support-v4.jar
 →Android6以降をサポートする場合に必要

AndroidManifest.xmlの記述

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="※1" >
    
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  ←必ず必要
    
    <application
        android:banner="@drawable/app_banner"
        android:debuggable="true"
        android:icon="@mipmap/app_icon"
        android:isGame="true"
        android:label="@string/app_name"
        android:theme="@style/UnityThemeSelector" >
        <activity
            android:name="※1.UnityPluginActivity"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
            android:hardwareAccelerated="true"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:screenOrientation="sensorPortrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="unityplayer.UnityActivity"
                android:value="true" />
        </activity>
    </application>

</manifest>

UnityPluginActivity.javaの記述

package ※1;

import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContentUris;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Build;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.Manifest;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;

public class UnityPluginActivity extends UnityPlayerActivity
{
    private final int INTENT_IMAGE                                = 1000;
    private final int INTENT_PERMISSION_READ_EXTERNAL_STORAGE    = 1001;

    private static String _gameObjectName    = "";    // Unity側へパラメータを渡す際に必要となるゲームオブジェクト名
    private static String _saveFilePath        = "";          // 保存するファイルパス

    // UnityPlayer.currentActivity.startActivityForResult( intent, INTENT_IMAGE ); 実行結果
    // resultData.getData()で選択した画像のURIを取得できる
    // resultCode != RESULT_OKの場合は、物理キーの戻るボタンが押された
    @Override
    protected void onActivityResult( int requestCode, int resultCode, Intent resultData )
    {
        super.onActivityResult( requestCode, resultCode, resultData );

        String savePath = "";

        switch( requestCode )
        {
        case INTENT_IMAGE:
            if( resultCode == RESULT_OK )
            {
                Uri        uri     = resultData.getData();
                String    path = getPath( uri );

                if( path != "" )
                {
                   // 保存ファイル名の生成
                    savePath = _saveFilePath + "/tmpImage" + path.substring( path.lastIndexOf(".") );

                   // ファイルコピー
                    try( InputStream    inputStream        = getContentResolver().openInputStream( uri );
                         OutputStream    outputStream    = new FileOutputStream( new File( savePath ) )
                    )
                    {
                        byte[]    buffer    = new byte[1024];
                        int        len        = 0;
                        while( (len = inputStream.read( buffer )) != -1 )
                        {
                            outputStream.write( buffer, 0, len );
                        }
                    }
                    catch (Exception e)
                    {
                        savePath = e.toString();
                    }
                }
            }

            // C#側へメッセージ送信
            UnityPlayer.UnitySendMessage( _gameObjectName, "CallbackOpen", savePath );
            break;
        default:
            break;
        }    
    }

    // ActivityCompat.requestPermissions 実行結果
    @Override
    public void onRequestPermissionsResult( int requestCode, String permissions[], int[] grantResults )
    {
        switch( requestCode )
        {
        case INTENT_PERMISSION_READ_EXTERNAL_STORAGE:
            if( grantResults[0] == PackageManager.PERMISSION_GRANTED )
            {
                startActivity();
            }
            break;
        default:
            break;
        }
    }

   // C#側から実行される処理
    public void open( String gameObjectName, String savePath )
    {
        _gameObjectName = gameObjectName;
        _saveFilePath    = savePath;

        // ユーザー認証許可がない場合は許可を得るダイアログを表示する
        if( ContextCompat.checkSelfPermission( UnityPlayer.currentActivity, Manifest.permission.READ_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED )
        {
            ActivityCompat.requestPermissions( UnityPlayer.currentActivity, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE }, INTENT_PERMISSION_READ_EXTERNAL_STORAGE );
        }
        else
        {
            startActivity();
        }
    }

    // ギャラリーを開く処理
    private void startActivity()
    {
        UnityPlayer.currentActivity.runOnUiThread( new Runnable()
        {
            public void run()
            {
                Intent intent = new Intent();
                intent.setType( "image/*" );
                intent.setAction( Intent.ACTION_OPEN_DOCUMENT );
                intent.addCategory( Intent.CATEGORY_OPENABLE );
                UnityPlayer.currentActivity.startActivityForResult( intent, INTENT_IMAGE );
            }
        });
    }

    // URIから実際に格納されているファイルパスを得る
    private String getPath( Uri uri )
    {
        String path = "";
        String scheme = uri.getScheme();
        String authority = uri.getAuthority();

        // SDカード
        if( "com.android.externalstorage.documents".equals( authority ) )
        {
            String docId = DocumentsContract.getDocumentId( uri );
            String[] split = docId.split( ":" );
            path = "/Storage/" + split[0] + "/" + split[1];
        }
        // ダウンロード
        else if( "com.android.providers.downloads.documents".equals( authority ) )
        {
            String docId = DocumentsContract.getDocumentId( uri );
            Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId) );
            path = getDataColumn( contentUri, null, null );
        }
        // 一般
        else if( "com.android.providers.media.documents".equals( authority ) )
        {
            String docId = DocumentsContract.getDocumentId( uri );
            String[] split = docId.split( ":" );
            Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String selection = "_id=?";
            String[] selectionArgs = { split[1] };
            path = getDataColumn( contentUri, selection, selectionArgs );
        }

        return path;
    }

    // ファイルパス取得
    private String getDataColumn( Uri uri, String selection, String[] selectionArgs )
    {
        Cursor cursor = null;
        String[] projection = { MediaStore.Files.FileColumns.DATA };

        try
        {
            cursor = getContentResolver().query( uri, projection, selection, selectionArgs, null );
            if( cursor != null && cursor.moveToFirst() )
            {
                return cursor.getString( cursor.getColumnIndexOrThrow(projection[0]) );
            }
        }
        catch (Exception e)
        {
            return "";
        }
        finally
        {
            if( cursor != null )
            {
                cursor.close();
            }
        }

        return "";
    }
}

※1…Unity側の「PackageName」と同じにしてください

重量な部分は以下になります
1.C#側で「open」メソッドを呼び出す
2.UnityPlayer.UnitySendMessageメソッドでC#側に処理結果を渡す

長くなるので続きます。。。