Google drive upload on Android (VR apk)

Hi,

I’m having issues with a script supposed to upload a file on a Google Drive repository, using a service account. (This is an internal project so it won’t be released on a store, I’m aware of the security vulnerability.)
The whole process works well in editor, but is destined to be used on a VR headset. Once I build the APK, I have an error when doing the drive upload…

See the code below, I’ll comment briefly.

The code was initially shorter, but trying to use other ways to fix my bugs, I made it longer.
I first had an issue related to the de-serialization of my JSON key, which I fixed with a manual de-serialization (function InitializeDriveService).

Now the issues I have are related to fields not having getters, last one being : ArgumentException : Get method not found for GrantType.
(Occurring at line 144, during request.UploadAsync().)

GrantType is never mentionned in my code, it’s a property of the request I’m sending. Again, this works fine in editor. I guess there is some incompatibility with Android, but I didn’t find what I should be using instead.

Does anyone have a solution for this ?

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using Google.Apis.Upload;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;

public static class GoogleDriveCircuitDataUploader {


    const string driveID = "XXXXXXXXXXXX"; // Replaced by my drive folder ID

    private static DriveService service;
    private static string fileName; // The file name, which will be based on this session start date and time
    private static string fileID; // The file where the logs of this session are uploaded (null while not created)
    private static string fileContent = ""; // The file content, to increment over this session


    public static void SubmitCircuitData(string dataToString) {
        fileContent += dataToString + "\n\n";
        UploadFileFromText(fileContent);
    }

    private static bool AuthenticateWithServiceAccount() {
        TMP_Logs.PrintLog("Attempting Authentication...");
        try {
            string serviceAccountKeyFilePath = Path.Combine("Assets/service-account-key.json"); // Clé JSON téléchargée
            ServiceAccountCredential credential;

            service = GoogleDriveServiceManualAuth.InitializeDriveService(GetServiceAccountKey());

            fileName = $"Circuit Data - {DateTime.Now.ToString()}.txt";
            Debug.Log("Authenticated using Service Account.");
            return true;
        }
        catch (Exception e) {
            Debug.LogError("Error while trying to authenticate using Service Account : " + e.Message);
            //TMP_Logs.PrintLog("Error while trying to authenticate using Service Account : " + e.Message);
            return false;
        }
    }

    public class GoogleDriveServiceManualAuth {
        public class ServiceAccountKey {
            public string type;
            public string project_id;
            public string private_key_id;
            public string private_key;
            public string client_email;
            public string client_id;
            public string auth_uri;
            public string token_uri;
            public string auth_provider_x509_cert_url;
            public string client_x509_cert_url;
        }

        public static DriveService InitializeDriveService(string serviceAccountJson) {
            try {
                // Deserialize avec JsonUtility
                var serviceAccountKey = JsonUtility.FromJson<ServiceAccountKey>(serviceAccountJson);
                if (serviceAccountKey == null) {
                    UnityEngine.Debug.LogError("Failed to deserialize service account JSON.");
                    return null;
                }

                // Créer une source d’identités basée sur la clé privée
                var credential = new ServiceAccountCredential(
                    new ServiceAccountCredential.Initializer(serviceAccountKey.client_email) {
                        Scopes = new[] { DriveService.Scope.DriveFile }
                    }.FromPrivateKey(serviceAccountKey.private_key)
                );

                // Initialize the Google Drive service
                var service = new DriveService(new BaseClientService.Initializer() {
                    HttpClientInitializer = credential,
                    ApplicationName = "Your Application Name",
                });

                UnityEngine.Debug.Log("Google Drive service initialized successfully.");
                return service;
            }
            catch (Exception ex) {
                UnityEngine.Debug.LogError($"Failed to initialize Google Drive service: {ex.Message}");
                return null;
            }
        }
    }

    private static string GetServiceAccountKey() {
        string path = Path.Combine(Application.streamingAssetsPath, "service-account-key.json");

        if (path.Contains("://")) { // Android platform
            UnityWebRequest www = UnityWebRequest.Get(path);
            www.SendWebRequest();
            while (!www.isDone) { } // Attendez que le fichier soit chargé
            if (www.result == UnityWebRequest.Result.Success) {
                return www.downloadHandler.text;
            }
            else {
                Debug.LogError("Failed to load JSON from StreamingAssets: " + www.error);
                return null;
            }
        }
        else { // Other platforms
            return File.ReadAllText(path);
        }
    }

    /*
     * Updload the circuit logs, procided as a string to encode as text file
     */
    private static async void UploadFileFromText(string fileContent) {
        if (service == null) {
            if (!AuthenticateWithServiceAccount()) {
                Debug.LogError("Google Drive service not initialized, can't upload.");
                return;
            }
        }

        byte[] textBytes = System.Text.Encoding.UTF8.GetBytes(fileContent);
        using (var stream = new MemoryStream(textBytes)) {
            if (fileID == null) {
                await CreateFileFromText(stream);
            }
            else {
                await UpdateFileFromText(stream);
            }
        }
    }

    private static async Task CreateFileFromText(MemoryStream stream) {
        var fileMetadata = new Google.Apis.Drive.v3.Data.File() {
            Name = fileName,
            Parents = new List<string> { driveID }
        };

        var request = service.Files.Create(fileMetadata, stream, "text/plain");
        //request.Fields = "id";
        var progress = await request.UploadAsync();

        if (progress.Status == UploadStatus.Completed) {
            fileID = request.ResponseBody.Id;
            Debug.Log("File uploaded successfully.");
            TMP_Logs.PrintLog("Create - File uploaded successfully.");
        }
        else {
            Debug.LogError("File upload failed: " + progress.Exception);
            TMP_Logs.PrintLog("Create - File upload failed: " + progress.Exception);
        }
    }

    private static async Task UpdateFileFromText(MemoryStream stream) {
        var fileMetadata = new Google.Apis.Drive.v3.Data.File() {
            Name = fileName
        };

        var request = service.Files.Update(fileMetadata, fileID, stream, "text/plain");
        request.Fields = "id";
        var progress = await request.UploadAsync();

        if (progress.Status == UploadStatus.Completed) {
            fileID = request.ResponseBody.Id;
            Debug.Log("File uploaded successfully.");
            TMP_Logs.PrintLog("Update - File uploaded successfully.");
        }
        else {
            Debug.LogError("File upload failed: " + progress.Exception);
            TMP_Logs.PrintLog("Update - File upload failed: " + progress.Exception);
        }
    }

}