THETA Xのファイルアクセス（SDカード/内蔵メモリ参照、撮影ファイル削除、マニフェスト）

## はじめに

リコーの @KA-2 です。

弊社では[RICOH THETA](https://theta360.com/ja/)という全周囲360度撮れるカメラを出しています。
<B>RICOH THETA V</B>, <b>RICOH THETA Z1</b>, <b>RICOH THETA X</b>は、OSに<B>Android</B>を採用しています。Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます（詳細は本記事の末尾を参照）。

今回は、RICOH THETA Xでプラグインを作成する際に注意が必要な、以下3つのポイントについて説明します。
- THETA XのSDカード/内蔵メモリ参照方法
- THETA XはプラグインからのWebAPI : [camera.delete](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/commands/camera.delete.md)に対応してないため、その代替手段の紹介。<br>※外部機器からは利用できます
- THETA Xでファイルアクセスをする時に必要なマニフェスト記述


## SDカード有無に応じた画像の保存先を知る方法
THETA X では SDカードスロットが追加されました。
SDカードがある場合はSDカードに、SDカードが無い場合は内蔵メモリに画像が保存されます。

WebAPI で takePictureしたあと取得できる fileURLは、HTTP通信経由でファイル取得するための情報です。これは、SDカードの有無で変わりません。

しかし、プラグインから、撮影済み画像を解析するようなケースでは、ダイレクトに画像ファイルにアクセスできます。（要パーミッション）

その時、ファイルまでのパスをどのように取得したらよいか？という疑問が生じます。

これまでの機種では、以下のようなfileURLをファイルまでのパスに変換するメソッドを用意することで対応できました。

```MainActivity.java
    public static final String DCIM = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DCIM).getPath();

    public static String getFilePath( String fileURL ) {
        String filePath= "";
        Matcher matcher = Pattern.compile("/\\d{3}RICOH.*").matcher(fileURL);
        if (matcher.find()) {
            filePath = DCIM + matcher.group();
            //Log.d(TAG, "filePath=" + filePath);
        } else {
            //エラーはnullのまま
        }

        return filePath;
    }
```

この処理は、
「 http://127.0.0.1:8080/files/100RICOH/R0010502.JPG 」というfileURLが取得できた場合、
「/storage/emulated/0/DCIM/100RICOH/R0010502.JPG」というパスに変換しています。


THETA Xの場合、上記メソッドの処理は記憶先が内蔵メモリであった場合にのみ対応しています。
記憶先がSDカードの場合には対応できていません。

そこで、以下のようなSDカード装着結果に応じ、ファイルまでのパスを返すようなメソッドを用意します。

```MainActivity.java
    private static final String PATH_STORAGE = "/storage/";
    private static final String SELF = "self";
    private static final String EMULATED = "emulated";

    public static String sdMount() {
        File file = new File(PATH_STORAGE);
        String[] fileList = file.list();
        if(fileList.length >= 3) {
            for (int i = 0; i < fileList.length; i++) {
                if(!fileList[i].equals(EMULATED) && !fileList[i].equals(SELF)) {
                    return fileList[i];
                }
            }
        }
        return EMULATED + "/0";
    }
```

この処理は、"/storage"というディレクトリ配下に、"self"と"emulated"以外のフォルダがあるか否かを判定し、あった場合には、その名称（SDカードによってAndroidが任意に命名する名前）がパスの一部になるという処理です。Androidのシステムの振る舞いを利用しています。

以下に、Android StudioのDevice File Explorerでその様子を確認した結果を添えておきます。
![SDカード有無によるstorage配下の変化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265455/2746f1d7-dcf0-7382-ca2d-be8dbffd1f55.png)

そして、以下のように、getFilePath()メソッドで得られたfilePathをsdMount()メソッドで得られる文字列に置換すると良いです。

```MainActivity.java
        filePath = filePath.replace(EMULATED + "/0", sdMount());
```

「/storage/emulated/0/DCIM/100RICOH/R0010502.JPG」というファイルパスであれば、<br>「/storage/1702-3A5B/DCIM/100RICOH/R0010502.JPG」というファイルパスに変換されます。<br>※「1702-3A5B」は私が利用しているSDカードの場合です。SDカード固有の名称となります。


上記の置換処理は、getFilePath()メソッドの末尾で行っても良いかもしれません。

ファイルパスの置換が不要で、SDカードをマウントしているか否かだけ調べたい場合には、sdMount()をご自身でアレンジしてください。


## ファイルの削除とデータベース更新

本記事の導入部に示したとおり、THETA Xでは、webAPI の [camera.delete](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/commands/camera.delete.md)をプラグインから実行できません。外部機器からの利用はできるのですが、プラグインからは利用できなくなっています。

そこで、以下のようなメソッドを用意することで等価のファイル削除が行えます。
ポイントは、THETA独自のインテントを利用してTHETA内部データベースの更新を行っている所です。

```MainActivity.java
    void deleteImageFile( String inFilePath) {
        if ( !inFilePath.equals("") ) {
            Log.d(TAG, "delete file path=" + inFilePath );

            //ファイル削除
            File file = new File(inFilePath);
            file.delete();
            Log.d(TAG, "file deleted.");

            //インテントを送りデータベース更新
            notificationDatabaseUpdate(inFilePath);
            Log.d(TAG, "updated the database.");
        }
    }
```


## ファイルアクセスに関するTHETA X固有の注意点

RICOH THETA Xでは、ファイルアクセスを行う際、以下のパーミッションを追加するだけでなく
```AndroidManifest.xml
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```

マニフェストのapplicationタグの属性に<b>「android:requestLegacyExternalStorage="true"」</b>を追加する必要があります。

```AndroidManifest.xml
    <application
        ～省略～
        android:requestLegacyExternalStorage="true">
        ～省略～
    </application>
```

THETA XがAndroid 10 をベースとするようになったためです。
Androidの[こちらのドキュメント](https://developer.android.com/training/data-storage/use-cases?hl=ja#opt-out-in-production-app)にも記載がある注意点です。


## まとめ

THETA XはSDカードスロットに対応したことにより、利用者が便利になった反面、プラグイン作成者が注意するところが増えました。また、その他のTHETA X固有の注意点もありました。
これらのポイントに注意して、THETA Xに対応したプラグインの作成にトライしてみてください。


## RICOH THETAプラグインパートナープログラムについて

THETAプラグインをご存じない方は[こちら](https://github.com/ricohapi/theta-api-specs/blob/main/core/products/theta-plugin.md)をご覧ください。
パートナープログラムへの登録方法は[こちら](https://qiita.com/mShiiina/items/55d98f366e650ca42251)にもまとめてあります。
Qiitaの[RICOH THETAプラグイン開発者コミュニティ TOPページ「About」](https://qiita.com/organizations/theta-plugin)に便利な記事リンク集もあります。
興味を持たれた方は[Twitter](https://twitter.com/thetaplugin)のフォローと[THETAプラグイン開発者コミュニティ(Slack)](https://join.slack.com/t/thetadeveloper/shared_invite/enQtNzcxODI2MTk2ODg0LWY5YmU2ZTIxNGYwNzlmNWZjMWMyMWFlMzY3ZmQ3NGU3ZjZiZjI0Njk4MGIxMDU5NDJiMzQ4Y2FkNDEzYmFlNGU)への参加もよろしくおねがいします｡