Oculus Quest と Oculus Go と GearVR に対応した Oculus Store 申請用 APK の作成方法

#はじめに

Oculus Quest アプリの APK を Oculus Store 申請のため Oculus Go や GearVR と同じ方法で生成したのですが、下記のエラーが発生してしまいアップロードすることが出来ませんでした :x: 

> Signature Scheme V1で署名されたAPK
> 利用できなくなったSignature Scheme V1でAPKが署名されています。この種のアプリの署名にはV2またはV3を使用してください。

[公式ドキュメント](https://developer.oculus.com/distribute/latest/concepts/publish-mobile-app-signing/) を見ると、Gear VR / Go と Quest では申請用 APK の作成方法が異なるようでした :upside_down:

今回は `Gear VR and/or Go and/or Quest` の項目にある方法でエラー無く、
Oculus Store にアップロード可能な APK を作成することが出来ました :clap: 

しかし、今後もこういう事象がアプリ申請作業の直前とかで発生すると困るため、
一通りの APK 作成方法を試したので記事として残しておくことにしました :pencil: 

# AndroidManifest.xml の作成方法

まずは Oculus Store 申請用の AndroidManifest.xml を作成します :clipboard: 

[公式ドキュメント](https://developer.oculus.com/distribute/latest/concepts/publish-mobile-manifest/) の手順に沿って Unity メニューの `Tools > Oculus > Create store-compatible AndroidManifest.xml` から作成します。

AndroidManifest.xml ファイルは `Assets/Plugins/Android` フォルダ内に作成されてれば大丈夫です :thumbsup: 

# APK の作成方法

普段行っている GearVR / Go の申請用 APK の作成方法を説明した後、
Oculus Quest にも対応した申請用 APK の作成方法について説明しつつ、
最後に Oculus Quest のみに対応した APK の作成方法について説明します :mortar_board:

## Gear VR / Go の申請用 APK 作成方法

Gear VR / Go の申請用 APK は v1 署名のみを行う必要があるのですが、
Unity では APK ビルド時に自動で v1 及び v2 の署名が APK に行われています :arrow_down: 

```shell
$ apksigner verify -v Sample.apk 
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Number of signers: 1
```

そのため Oculus Store 申請用 APK を作成する際は、
APK の署名を v1 のみ有効にする必要があります。。:weary: 

### mainTemplate.gradle で v1署名のみを有効にする方法

`mainTemplate.gradle` を上書きする対策を行うと楽です :v:

元となる `mainTemplate.gradle` ファイルは Mac で UnityHub を使ってる環境だと :arrow_down: に入ってました。
`/Applications/Unity/Hub/Editor/<プロジェクトで使用している Unity バージョン>/PlaybackEngines/AndroidPlayer/Tools/GradleTemplates/mainTemplate.gradle`

ファイルを見つけたら Unity プロジェクトの `/Assets/Plugins/Android/` に
`mainTemplate.gradle` を追加して v1署名のみを有効化する記述を追記します :writing_hand: 

```gradle:/Assets/Plugins/Android/mainTemplate.gradle
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN

buildscript {
    repositories {
        mavenCentral()
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
**BUILD_SCRIPT_DEPS**}
}

allprojects {
    repositories {
        mavenCentral()
        google()
        jcenter()
        flatDir {
            dirs 'libs'
        }
    }
}

apply plugin: 'com.android.application'
**APPLY_PLUGINS**

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
**DEPS**}

android {
    compileSdkVersion **APIVERSION**
    buildToolsVersion '**BUILDTOOLS**'

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        minSdkVersion **MINSDKVERSION**
        targetSdkVersion **TARGETSDKVERSION**
        applicationId '**APPLICATIONID**'
        ndk {
            abiFilters **ABIFILTERS**
        }
        versionCode **VERSIONCODE**
        versionName '**VERSIONNAME**'
    }

    lintOptions {
        abortOnError false
    }

    aaptOptions {
        noCompress = ['.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS**]
    }**SIGN**

    //===================================
    // signingConfigs の release を追記することで、
    // 明示的に v2SigningEnabled を false とすることで v1署名のみが有効になる
    signingConfigs {
        release {
            v1SigningEnabled true 
            v2SigningEnabled false
        }
    }
    //===================================

    buildTypes {
        debug {
            minifyEnabled **MINIFY_DEBUG**
            useProguard **PROGUARD_DEBUG**
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD**
            jniDebuggable true
        }
        release {
            minifyEnabled **MINIFY_RELEASE**
            useProguard **PROGUARD_RELEASE**
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG**
        }
    }**PACKAGING_OPTIONS****SPLITS**
**BUILT_APK_LOCATION**
    bundle {
        language {
            enableSplit = false
        }
        density {
            enableSplit = false
        }
        abi {
            enableSplit = true
        }
    }
}**SPLITS_VERSION_CODE****REPOSITORIES****SOURCE_BUILD_SETUP**
```

これで次回以降 `Development Build` からチェックを外した状態で生成された APK は、
v1署名のみが有効化された状態となっているはずです :thumbsup: [^1]

### PostProcessBuild で v1署名のみを有効にする方法

次は Editor スクリプトで対応してみます :gear:

下記のスクリプトを Unity プロジェクト内の `Assets/Editor` 以下に配置しておくことで、
`Build Settings` の `Development Build` にチェックが無い時にだけ、
APK 作成時に v1 署名だけを `apksigner` コマンドを利用して有効にします :pen_fountain: 

```csharp:Assets/Editor/AndroidApkSigner.cs
using UnityEditor;
using UnityEditor.Callbacks;
using System.Diagnostics;

public class AndroidApkSigner
{
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string path)
    {
        // apksigner の絶対パスを指定
        var apkSignerPath = "/Users/nika/Library/Android/sdk/build-tools/28.0.3/apksigner";

        //'Development Build' にチェックが入っていないときのみ実行
        if (!EditorUserBuildSettings.development)
        {
            // ビルド時に設定した keystore のファイルパス取得
            var keyStoreFilePath = PlayerSettings.Android.keystoreName;

            // ビルド時に設定した keystore のエイリアスとパスワード取得
            var keyStoreAliasName = PlayerSettings.Android.keyaliasName;
            var keyStorePassword = PlayerSettings.Android.keystorePass;

            // keystore に署名をし直すために必要な認証のためのコマンドオプション
            var keyStoreOption = " --ks-key-alias " + keyStoreAliasName + " --ks-pass pass:" + keyStorePassword;

            // v１ の署名のみを有効にするためのコマンドオプション
            var signningOption = "--v1-signing-enabled true --v2-signing-enabled false";

            // v1 の署名のみを有効にするための apksigner のコマンド実行
            DoBashCommand(apkSignerPath + " sign --ks " + keyStoreFilePath + " " + keyStoreOption + " " + signningOption + " " + path);
        }

        // 正常に v1 署名のみが有効化されたかを確認するためのコマンド実行
        // Unity コンソールにその結果を出力する
        var confirm = DoBashCommand(apkSignerPath + " verify -v " + path);
        UnityEngine.Debug.Log(confirm);
    }

    // http://smartgames.hatenablog.com/entry/2016/06/21/230714
    static string DoBashCommand(string cmd)
    {
        var p = new Process();
        p.StartInfo.FileName = "/bin/bash";
        p.StartInfo.Arguments = "-c \" " + cmd + " \"";
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.Start();

        var output = p.StandardOutput.ReadToEnd();
        p.WaitForExit();
        p.Close();

        return output;
    }
}
```

:arrow_up: の作業が完了したら、実際に keystore の各種情報を `Build Settings > Player Settings... > Publish Settings` で入力し、`Development Build` からチェックを外した状態でビルドしてみます:hammer_pick:

<img width="912" alt="スクリーンショット 2019-09-21 14.19.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/60452886-3980-ab03-507e-3a0c18d4ccc7.png">

Unity コンソールから上記の出力が確認出来れば v1 署名のみが有効になっています :tada: 

## Quest もしくは Gear VR / Go の申請用 APK 作成方法

こちらは AndroidManifest.xml の `android.hardware.vr.headtracking` の `required` 属性が `false` で、APK の署名が v1 だけ有効になっていれば大丈夫です。

まず AndroidManifest.xml の `android.hardware.vr.headtracking` の部分を変更します :arrow_down: 

```xml:Assets/Plugins/Android/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
    <!-- android.hardware.vr.headtracking の required を false にする -->
    <uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="false" />
    <application android:allowBackup="false">
        <activity android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" android:configChanges="locale|fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" android:launchMode="singleTask" android:name="com.unity3d.player.UnityPlayerActivity" android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.INFO"/>
            </intent-filter>
        </activity>
        <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="false" />
    </application>
</manifest>
```

あとは APK への v1 署名は [Gear VR / Go の申請用 APK 作成方法](#gear-vr--go-%E3%81%AE%E7%94%B3%E8%AB%8B%E7%94%A8-apk-%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95) の方法で行えば大丈夫です :thumbsup: 

## Quest の申請用 APK 作成方法

こちらは今までと比べると少し面倒な作業が必要になります。。:sweat_smile:

まずは Unity の `Build Settings > Export Project` にチェックを入れて、
`Export` ボタンをクリックし Android Studio で開くことが可能なプロジェクトとして出力します。

プロジェクトを出力する際の注意点として、Mac では明示的に出力先フォルダを指定しないと、
プロジェクトの出力に失敗するようなのでご注意ください (Desktop を出力先に指定するとエラーが発生する)

<img width="355" alt="スクリーンショット 2019-09-21 15.18.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/d36da95f-b11a-f9b1-6721-080dcdce7d90.png">

次に Android Studio を開いて出力されたプロジェクトを開きます。
すると :arrow_down: のダイアログが出てくるので、`OK` をクリックします。

<img width="619" alt="スクリーンショット 2019-09-21 15.33.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/45d96ff9-673f-4b28-14b8-fe2b834f502c.png">

無事にビルドが完了したら Android Studio のメニューから `Build > Generate Signed Bundle / APK` を選択します。

<img width="280" alt="スクリーンショット 2019-09-21 15.39.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/bad41f18-dbe4-884e-df6a-f7a6d6e7a650.png">

`Android App Bundle` か `APK` を選択するダイアログが出現するので `APK` の方を選択します :white_check_mark: 
keystore の認証情報を入力するダイアログが出てくるので、必要な情報を入力して `Next` ボタンをクリックします :pencil: 
最後に `Build Variants` は `release` を選択し、`Signature Versions` では `V2 (Full APK Signature)` にチェックを入れて `Finish` ボタンをクリックします :arrow_down: 

<img width="680" alt="スクリーンショット 2019-09-21 15.48.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/eac33a61-35f2-e369-818e-208246597f51.png">

APK の作成が完了すると Android Studio プロジェクトフォルダ直下の `release` フォルダに APK が出力されていることが確認出来ます :arrow_down: 

```shell
$ pwd
/Users/nika/Desktop/build/OculusMobileVoiceChat/release
$ ls *.apk
OculusMobileVoiceChat-release.apk # ｖ２ で署名された APK が生成されている
```

#おわりに

Oculus Store の申請周りは新たな HMD が出るたびに頻繁に変わるので、
都度チェックしておく必要があるなと再認識させられました :clipboard:

また今回は APK の作成方法についてしか載せませんでしたが、
他にもアプリ申請のために対応すべき事柄は沢山あります。。 :mountain: 

Oculus Store への申請を検討している方 (特に Quest) は、
:arrow_down: を事前に確認しておくとスムーズに申請作業を進められると思います。
[Uploading Android Apps](https://developer.oculus.com/distribute/latest/concepts/publish-uploading-mobile/)
[Store Review Policies](https://developer.oculus.com/distribute/latest/concepts/publish-prep-app/) (特に Virtual Reality Checks (VRCs) の項目)
[Submitting Your App to the Oculus Quest Store](https://developer.oculus.com/blog/submitting-your-app-to-the-oculus-quest-store/)
(Quest アプリの申請にはコンセプトレビューも通過する必要がある)

# 参考リンク

- [Application Manifests for Release Builds](https://developer.oculus.com/distribute/latest/concepts/publish-mobile-manifest/)
- [Android Application Signing](https://developer.oculus.com/distribute/latest/concepts/publish-mobile-app-signing/)
- [Unityでbashを利用する](http://smartgames.hatenablog.com/entry/2016/06/21/230714)
- [Uploading Android Apps](https://developer.oculus.com/distribute/latest/concepts/publish-uploading-mobile/)
- [Store Review Policies](https://developer.oculus.com/distribute/latest/concepts/publish-prep-app/)
- [Submitting Your App to the Oculus Quest Store](https://developer.oculus.com/blog/submitting-your-app-to-the-oculus-quest-store/)
- [【Unity2018.4.0f1】APK Signed With Signature Schema V3エラー　回避方法](https://qiita.com/MeBu/items/206701ab906a3ac3a656)

[^1]: 逆に `Development Build` にチェックが入った状態だと、`v2SigningEnabled false` の設定が効かなくなるためご注意ください。。 :x: