1

I would like to use Google Text-to-Speech in my Android app.

According to documentation, I have been through all these steps:

  1. In the Cloud Console, go to the Create service account key page.
  2. From the Service account list, select New service account.
  3. In the Service account name field, enter a name.
  4. From the Role list, select Project > Owner.
  5. Click Create. A JSON file that contains your key downloads to your computer.

I am using Google's code to pass the credential, which is the JSON file that I have downloaded:

public void main(String... args) throws Exception {
    // You can specify a credential file by providing a path to GoogleCredentials.
    // Otherwise credentials are read from the GOOGLE_APPLICATION_CREDENTIALS environment variable.
    InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
    GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(String.valueOf(stream)))
            .createScoped(Lists.newArrayList(Collections.singleton("https://www.googleapis.com/auth/cloud-platform")));
    Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();

    System.out.println("Buckets:");
    Page<Bucket> buckets = storage.list();
    for (Bucket bucket : buckets.iterateAll()) {
        System.out.println(bucket.toString());
    }
}

Anyway, I have cleaned Gradle, restarted Android Studio but I am still getting this log:

java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

What am I doing wrong?

Update

I have realized that I get this log message only when I click on a button. If I click on a button, the method hello() will run. See the full code:

package ch.yourclick.kitt;

import android.os.Build;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.google.api.client.util.Lists;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.paging.Page;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.texttospeech.v1.AudioConfig;
import com.google.cloud.texttospeech.v1.AudioEncoding;
import com.google.cloud.texttospeech.v1.SsmlVoiceGender;
import com.google.cloud.texttospeech.v1.SynthesisInput;
import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse;
import com.google.cloud.texttospeech.v1.TextToSpeechClient;
import com.google.cloud.texttospeech.v1.TextToSpeechSettings;
import com.google.cloud.texttospeech.v1.VoiceSelectionParams;
import com.google.protobuf.ByteString;
import androidx.annotation.RequiresApi;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import android.os.StrictMode;
import android.view.View;

import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.BucketInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;

import ch.yourclick.kitt.ui.main.SectionsPagerAdapter;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
        ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsPagerAdapter);
        TabLayout tabs = findViewById(R.id.tabs);
        tabs.setupWithViewPager(viewPager);
        FloatingActionButton fab = findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    public void main(String... args) throws Exception {
        // You can specify a credential file by providing a path to GoogleCredentials.
        // Otherwise credentials are read from the GOOGLE_APPLICATION_CREDENTIALS environment variable.
        InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
        GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
                .createScoped(Lists.newArrayList(Collections.singleton("https://www.googleapis.com/auth/cloud-platform")));
        Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();

        System.out.println("Buckets:");
        Page<Bucket> buckets = storage.list();
        for (Bucket bucket : buckets.iterateAll()) {
            System.out.println(bucket.toString());
        }
    }



    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onClick(View view) {
        int SDK_INT = android.os.Build.VERSION.SDK_INT;
        if (SDK_INT > 8)
        {
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
                    .permitAll().build();
            StrictMode.setThreadPolicy(policy);

            try {
                hello();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    /** Demonstrates using the Text-to-Speech API. */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void hello() throws Exception {
        InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
        GoogleCredentials credentials = GoogleCredentials.fromStream(stream);
        TextToSpeechSettings textToSpeechSettings =
                TextToSpeechSettings.newBuilder()
                        .setCredentialsProvider(
                                FixedCredentialsProvider.create(credentials)
                        ).build()
                ;


        // Instantiates a client
        try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(textToSpeechSettings)) {
            // Set the text input to be synthesized
            SynthesisInput input = SynthesisInput.newBuilder().setText("Hello, World!").build();

            // Build the voice request, select the language code ("en-US") and the ssml voice gender
            // ("neutral")
            VoiceSelectionParams voice =
                    VoiceSelectionParams.newBuilder()
                            .setLanguageCode("en-US")
                            .setSsmlGender(SsmlVoiceGender.NEUTRAL)
                            .build();

            // Select the type of audio file you want returned
            AudioConfig audioConfig =
                    AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build();

            // Perform the text-to-speech request on the text input with the selected voice parameters and
            // audio file type
            SynthesizeSpeechResponse response =
                    textToSpeechClient.synthesizeSpeech(input, voice, audioConfig);

            // Get the audio contents from the response
            ByteString audioContents = response.getAudioContent();

            // Write the response to the output file.
            try (OutputStream out = new FileOutputStream("output.mp3")) {
                out.write(audioContents.toByteArray());
                System.out.println("Audio content written to file \"output.mp3\"");
            }
        }
    }

}

Dependencies

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation group: 'io.grpc', name: 'grpc-okhttp', version: '1.0.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'com.google.cloud:google-cloud-storage:1.113.3'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'com.google.cloud:libraries-bom:4.3.0'
    implementation 'com.google.cloud:google-cloud-texttospeech:1.2.1'
    implementation 'com.google.android.gms:play-services:12.0.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
xRay
  • 543
  • 1
  • 5
  • 29

1 Answers1

5

Probably the GoogleCredentials class cannot find a valid credential JSON file due to the way it is being supplied to it.

Please, instead of:

InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(String.valueOf(stream)))
            .createScoped(Lists.newArrayList(Collections.singleton("https://www.googleapis.com/auth/cloud-platform")));

Try:

InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
            .createScoped(Lists.newArrayList(Collections.singleton("https://www.googleapis.com/auth/cloud-platform")));

getResources().openRawResource will provide you already the stream required, and you can use that stream to create the GoogleCredentials:

GoogleCredentials.fromStream(stream)

Regarding your update, it seems that you are creating the TextToSpeechClient without providing any explicit credentials and as a consequence the library is looking for them in the environment.

As you can see in the TechToSpeechClient javadocs, you can provide your credentials to the client.

Instead of this:

TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()

Initialize your client with something similar to this (please, modify it as you consider appropriate):

import com.google.api.gax.core.FixedCredentialsProvider;

// In your method...

InputStream stream = getResources().openRawResource(R.raw.credential); // R.raw.credential is credential.json
GoogleCredentials credentials = GoogleCredentials.fromStream(stream);
TextToSpeechSettings textToSpeechSettings =
  TextToSpeechSettings.newBuilder()
    .setCredentialsProvider(
      FixedCredentialsProvider.create(credentials)
    ).build()
;

TextToSpeechClient textToSpeechClient =
  TextToSpeechClient.create(textToSpeechSettings);

// The rest of your code

Please, be sure your service account credentials gives you the right authorization to access the requested APIs.

In fact, as I realized in your question, if you grant the project owner role to the service account you already have these permissions. In any case, please, be careful, the project owner role gives you full control over every resource in the project: for a service account it is always advisable to be more restrictive. Ideally it should be granted only the necessary permissions to perform the operations it is created for.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • I have tried your code but unfortunately, I get the same message. I have just updated my question. Please check. – xRay Dec 02 '20 at 22:05
  • thank you so much, that message is gone now but now I get the message `io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional channel service provider found. Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded artifact`. Does that mean that I have to add a dependency on Gradle? If so, which one of them? – xRay Dec 04 '20 at 21:01
  • Sorry for the late reply. Nice, I am happy to hear that. Yes, you are right, you need to add any of the dependencies indicated. Which one? Both netty and okhttp are great, although netty in my opinion is widely used, more robust. On the other hand, okhttp is lightweight. In the case of netty you can choose the shade version, auto contained, or the library and its dependencies. The use of one of another will depend on your use case (please, see for instance [this issue](https://github.com/googleapis/google-cloud-java/issues/6425)). – jccampanero Dec 04 '20 at 22:26
  • Please, pay attention to the required libraries versions, sometimes a version incompatibly could be the cause of the issue. Please, can you try? – jccampanero Dec 04 '20 at 22:28
  • Thanks, I am now using `implementation group: 'io.grpc', name: 'grpc-okhttp', version: '1.0.1'` but on the statement `try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(textToSpeechSettings)) {`, I get the error: E/AndroidRuntime: *FATAL EXCEPTION: main* ... *java.lang.IllegalStateException: Could not execute method for android:onClick*. Please see my new code. I have updated it. – xRay Dec 04 '20 at 23:13
  • Please, can you provide more information about the stack trace of the error reported? – jccampanero Dec 04 '20 at 23:49
  • Sure. Please find the error here: https://textsaver.flap.tv/lists/3r5d ... it seems to be a loop since some of the logs are repeated over and over again. – xRay Dec 05 '20 at 11:27
  • I think the issue is related with the version of okhttp. Please,try a different one,or netty instead. Be sure your versions are compatible with the one of the text yo speech client and with It dependency with grpc. By the way, which library version are you using? – jccampanero Dec 05 '20 at 12:10
  • The version of google-cloud-texttospeech is 1.2.1 and grpc-okhttp is 1.0.1. Instead of grpc-okhttp, I have also tried it with `implementation group: 'io.grpc', name: 'grpc-netty', version: '1.34.0'` but then I get the error *More than one file was found with OS independent path 'META-INF/native-image/io.netty/transport/native-image.properties'.* – xRay Dec 05 '20 at 12:38
  • I will dig into it. Yes, this is because the gcp libraries usually have a dependency on netty shaded. But I do not understand why your code did not find it the first time,when you told me about your first error. Please, can you review that dependencies? – jccampanero Dec 05 '20 at 12:51
  • Sure. I have updated my question so that you see all the dependencies that I use. – xRay Dec 05 '20 at 13:30
  • Thank you very much. It looks like you are using a quite dated `grpc-okhttp` library version. Please, can you try any version greater than or equal to `1.31.1`? – jccampanero Dec 05 '20 at 13:40
  • Yes! That was the solution! Thank you so much, you helped me a lot!! – xRay Dec 05 '20 at 14:49
  • 1
    You are welcome Reza. Nice! I am very happy to know that the answer was helpful! Please, do not hesitate to contact me if you need further help. – jccampanero Dec 05 '20 at 14:54
  • 1
    Thanks, it works for me. I think the Google official document should provide examples like this. – jxsun Apr 23 '21 at 03:56
  • You are welcome @jxsun. I am happy to hear that the solution works for you as well. Thank you very much – jccampanero Apr 23 '21 at 10:33