I am developing an speech recognition system in android . I have succeeded in it . But the Google Voice Dialog box keeps annoying me and i want to get rid off that . i.e I want to Run this voice recognition in the background as an service . I know there are many SO posts on this with answers . But my problem is the every code here is on Native java and i develop on phone gap . SO i am not able to port this thing to phone gap . I have my code below . So , Any help would be appreciated ! And i use this plugin on phone gap . Thanks in advance .
package com.phonegap.plugins.speech;
import java.util.ArrayList;
import java.util.Locale;
import org.json.JSONArray;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import android.util.Log;
import android.app.Activity;
import android.content.Intent;
import android.speech.RecognizerIntent;
/**
* Style and such borrowed from the TTS and PhoneListener plugins
*/
public class SpeechRecognizer extends CordovaPlugin {
private static final String LOG_TAG = SpeechRecognizer.class.getSimpleName();
private static int REQUEST_CODE = 1001;
private CallbackContext callbackContext;
private LanguageDetailsChecker languageDetailsChecker;
//#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
Boolean isValidAction = true;
this.callbackContext= callbackContext;
// Action selector
if ("startRecognize".equals(action)) {
// recognize speech
startSpeechRecognitionActivity(args);
} else if ("getSupportedLanguages".equals(action)) {
getSupportedLanguages();
} else {
// Invalid action
this.callbackContext.error("Unknown action: " + action);
isValidAction = false;
}
return isValidAction;
}
// Get the list of supported languages
private void getSupportedLanguages() {
if (languageDetailsChecker == null){
languageDetailsChecker = new LanguageDetailsChecker(callbackContext);
}
// Create and launch get languages intent
Intent detailsIntent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS);
cordova.getActivity().sendOrderedBroadcast(detailsIntent, null, languageDetailsChecker, null, Activity.RESULT_OK, null, null);
}
/**
* Fire an intent to start the speech recognition activity.
*
* #param args Argument array with the following string args: [req code][number of matches][prompt string]
*/
private void startSpeechRecognitionActivity(JSONArray args) {
int maxMatches = 0;
String prompt = "";
String language = Locale.getDefault().toString();
try {
if (args.length() > 0) {
// Maximum number of matches, 0 means the recognizer decides
String temp = args.getString(0);
maxMatches = Integer.parseInt(temp);
}
if (args.length() > 1) {
// Optional text prompt
prompt = args.getString(1);
}
if (args.length() > 2) {
// Optional language specified
language = args.getString(2);
}
}
catch (Exception e) {
Log.e(LOG_TAG, String.format("startSpeechRecognitionActivity exception: %s", e.toString()));
}
// Create the intent and set parameters
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
if (maxMatches > 0)
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxMatches);
if (!prompt.equals(""))
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
cordova.startActivityForResult(this, intent, REQUEST_CODE);
}
/**
* Handle the results from the recognition activity.
*/
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
// Fill the list view with the strings the recognizer thought it could have heard
ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
returnSpeechResults(matches);
}
else {
// Failure - Let the caller know
this.callbackContext.error(Integer.toString(resultCode));
}
super.onActivityResult(requestCode, resultCode, data);
}
private void returnSpeechResults(ArrayList<String> matches) {
JSONArray jsonMatches = new JSONArray(matches);
this.callbackContext.success(jsonMatches);
}
}
Because your need is a little different than the functionality provided by the plugin, it seems that you would need to write your own plugin. You can of course use the plugin sources (especially plugin.xml, SpeechRecognizer.js and SpeechRecognizer.java) as an example how to implement it in general.
Related
I have a project that invokes android's default speech recognition system, using RecognizerIntent.ACTION_RECOGNIZE_SPEECH
The UI is 1 textview and 1 command button, to show the result of speech recognition and to start speech recognition, respectively
I also record the input to the speech recogniser in an AMR file using the method shown here:
record/save audio from voice recognition intent
This is possible because the input audio tot he speech recogniser is available as an InputStream in the Intent data returned by the speech recogniser activity
However, I have noticed that when the speech recogniser activity fails to recognise speech (different from incorrect recognition), the onActivityResult(...) function is still called, but it does not return the Intent data, causing NullPointerException when trying to read the AMR file. How do I get the input to the speech recogniser when it fails to recognise speech?
Code:
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.speech.RecognizerIntent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
Button btnSpeak;
TextView txtViewResult;
private static final int VOICE_RECOGNITION = 1;
String saveFileLoc = Environment.getExternalStorageDirectory().getPath();
File fileAmrFile = new File(saveFileLoc+"/recordedSpeech.amr");
OutputStream outputStream = null;
InputStream filestream = null;
Uri audioUri = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnSpeak = (Button)findViewById(R.id.button1);
txtViewResult = (TextView)findViewById(R.id.textView1);
if(fileAmrFile.exists() ){
fileAmrFile.delete();
}
runprog();
} // protected void onCreate(Bundle savedInstanceState) { CLOSED
private void runprog(){
btnSpeak.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Toast.makeText(getApplicationContext(), "Button pressed", Toast.LENGTH_LONG).show();
// Fire an intent to start the speech recognition activity.
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Specify free form input
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT,"Please start speaking");
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.ENGLISH);
// secret parameters that when added provide audio url in the result
intent.putExtra("android.speech.extra.GET_AUDIO_FORMAT", "audio/AMR");
intent.putExtra("android.speech.extra.GET_AUDIO", true);
startActivityForResult(intent, VOICE_RECOGNITION);
}
});
} // private void runprog(){ CLOSED
// handle result of speech recognition
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// if (requestCode == VOICE_RECOGNITION && resultCode == RESULT_OK) {
if (requestCode == VOICE_RECOGNITION ) {
ArrayList<String> results;
results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
txtViewResult.setText(results.get(0));
Log.v("MYLOG", "Speech 2 text");
// the required audio will be returned by getExtras:
Bundle bundle = data.getExtras();
ArrayList<String> matches = bundle.getStringArrayList(RecognizerIntent.EXTRA_RESULTS);
// Toast.makeText(getApplicationContext(), "HEREHERE", Toast.LENGTH_LONG).show();
Log.v("MYLOG", "B4 Uri creation");
// /*
// the recording url is in getData:
audioUri = data.getData();
ContentResolver contentResolver = getContentResolver();
try {
filestream = contentResolver.openInputStream(audioUri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// TODO: read audio file from inputstream
try {
outputStream = new FileOutputStream(new File(saveFileLoc + "/recordedSpeech.wav"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
int read = 0;
byte[] bytes = new byte[1024];
while ((read = filestream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
// System.out.println("Done!");
Toast.makeText(getApplicationContext(), "Done", Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (filestream != null) {
try {
filestream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
// outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// */
}
} // public void onActivityResult(int requestCode, int resultCode, Intent data) { CLOSED
} // protected void onCreate(Bundle savedInstanceState) { CLOSED
layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/spoken" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/textView1"
android:layout_below="#+id/textView1"
android:text="#string/button_text"
/>
</LinearLayout>
For reference, this is what I mean when I said the speech recogniser fails to recognise speech (Note that this is different from when the speech recogniser detects wrongly; in case of a wrong detection, it is still recognising speech)
Phone details:
Model : Samsung Galaxy S4
Model No : SHV-E330L
Android version : 5.01
I'm looking for some guidance related to accessing Google Drive with an Android app.
1) I need to be able to read files uploaded by users outside of my app. Does this mean I need full-drive access? (If the app could create a folder and then see all files uploaded by the user that exist in this folder, that would be great, but I don't think it works this way.)
2) If I need full-drive access, it seems that Googles "Drive API for Android" doesn't support this, and I need to use the REST api. I think this is true.
3) I need an Auth 2.0 client ID from Google. If I use the rest API, does this mean I need to use a "Web Application" ID? I think I need this because I want an "auth code". I wasn't able to get it working with an "Android" type ID.
4) I'm currently using "Google Sign-In" for Android to handle the login and provide an auth code. I can then convert this into a Token + Refresh Token, and save these so I can get new tokens after an hour in some fashion. Is this manually handling of refresh tokens required?
It's getting ugly, but I think that since I need (?) full-drive access then this is the procedure.
Thanks for any guidance.
Edit: The question has been identified as a duplicate. The link provided gives an answer for question #2, but doesn't address the other questions.
I agree the question is messy...
I'm answering my own question.
I struggled with this because A) Google's REST example uses an outdated login process, B) The "Sign In" example uses code that doesn't work with "full-access" scope, and C) the there were too many vastly different code examples when trying to put it all together.
To quickly answer my questions as I see it now:
1) Yes, full-drive access is required to read files uploaded outside my app.
2) Yes, I need to use REST api.
3) Yes, I need a "Web Application" client ID.
4) Google Sign-In seems the best way currently to sign in, and using a GoogleCredential object along with the Drive api abject will handle the token refreshes automatically, as long as you keep around a refresh token.
In case anyone else is struggling with accessing Drive with full-access from Android using the latest "Sign-In" procedure and REST v3, below is my sample code.
In addition to the "Web application" OAuth client ID, you also need to create an "Android" type ID with a matching package name and certificate fingerprint in order for the Sign-In to work. Also note that you'll have different certificates for your dev and production versions. The IDs/codes from these Android clients do not need to be entered into the app.
build.gradle : app
// Google Sign In
compile 'com.google.android.gms:play-services-auth:10.0.1'
// Drive REST API
compile('com.google.apis:google-api-services-drive:v3-rev54-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Callback from Signin (Auth.GoogleSignInApi.getSignInIntent)
if (requestCode == 1) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
_googleApi.handleSignInResult(result);
}
}
A "GoogleApi" class to do the work
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class GoogleApi implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private Context _context;
private Handler _handler;
private GoogleCredential _credential;
private Drive _drive;
private GoogleApiClient _googleApiClient; // only set during login process
private Activity _activity; // launch intent for login (UI)
// Saved to data store
private boolean _loggedIn;
private String _refreshToken; // store, even if user is logged out as we may need to reuse
private static final String ClientID = "xxxxxx.apps.googleusercontent.com"; // web client
private static final String ClientSecret = "xxxxx"; // web client
private class FileAndErrorMsg {
public File file;
public String errorMsg;
public FileAndErrorMsg (File file_, String errorMsg_) { file = file_; errorMsg = errorMsg_; }
}
private class FileListAndErrorMsg {
public List<File> fileList;
public String errorMsg;
public FileListAndErrorMsg (List<File> fileList_, String errorMsg_) { fileList = fileList_; errorMsg = errorMsg_; }
}
// -------------------
// Constructor
// -------------------
public GoogleApi (Context context) {
_context = context;
_handler = new Handler();
loadFromPrefs(); // loggedIn, refreshToken
// create credential; will refresh itself automatically (in Drive calls) as long as valid refresh token exists
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
_credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(ClientID, ClientSecret) // .addRefreshListener
.build();
_credential.setRefreshToken(_refreshToken);
// Get app name from Manifest (for Drive builder)
ApplicationInfo appInfo = context.getApplicationInfo();
String appName = appInfo.labelRes == 0 ? appInfo.nonLocalizedLabel.toString() : context.getString(appInfo.labelRes);
_drive = new Drive.Builder(transport, jsonFactory, _credential).setApplicationName(appName).build();
}
// -------------------
// Auth
// -------------------
// https://developers.google.com/identity/sign-in/android/offline-access#before_you_begin
// https://developers.google.com/identity/sign-in/android/offline-access#enable_server-side_api_access_for_your_app
// https://android-developers.googleblog.com/2016/02/using-credentials-between-your-server.html
// https://android-developers.googleblog.com/2016/05/improving-security-and-user-experience.html
public boolean isLoggedIn () {
return _loggedIn;
}
public void startAuth(Activity activity) {
startAuth(activity, false);
}
public void startAuth(Activity activity, boolean forceRefreshToken) {
_activity = activity;
_loggedIn = false;
saveToPrefs();
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(new Scope("https://www.googleapis.com/auth/drive"))
.requestServerAuthCode(ClientID, forceRefreshToken) // if force, guaranteed to get back refresh token, but will show "offline access?" if Google already issued refresh token
.build();
_googleApiClient = new GoogleApiClient.Builder(activity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
_googleApiClient.connect();
}
#Override
public void onConnected(Bundle connectionHint) {
// Called soon after .connect()
// This is only called when starting our Login process. Sign Out first so select-account screen shown. (OK if not already signed in)
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
// Start sign in
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(_googleApiClient);
_activity.startActivityForResult(signInIntent, 1); // Activity's onActivityResult will use the same code: 1
}
});
}
#Override
public void onConnectionSuspended(int cause) {
authDone("Connection suspended.");
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) { authDone("Connection failed."); }
public void handleSignInResult(GoogleSignInResult result) {
// Callback from Activity > onActivityResult
if (result.isSuccess()) {
GoogleSignInAccount acct = result.getSignInAccount();
String authCode = acct.getServerAuthCode();
new Thread(new ContinueAuthWithAuthCode_Background(authCode)).start();
}
else authDone("Login canceled or unable to connect to Google."); // can we get better error message?
}
private class ContinueAuthWithAuthCode_Background implements Runnable {
String _authCode;
public ContinueAuthWithAuthCode_Background (String authCode) {
_authCode = authCode;
}
public void run() {
// Convert authCode to tokens
GoogleTokenResponse tokenResponse = null;
String errorMsg = null;
try {
tokenResponse = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "https://www.googleapis.com/oauth2/v4/token", ClientID, ClientSecret, _authCode, "").execute();
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
final GoogleTokenResponse tokenResponseFinal = tokenResponse;
final String errorMsgFinal = errorMsg;
_handler.post(new Runnable() { public void run() {
// Main thread
GoogleTokenResponse tokenResponse = tokenResponseFinal;
String errorMsg = errorMsgFinal;
if (tokenResponse != null && errorMsg == null) {
_credential.setFromTokenResponse(tokenResponse); // this will keep old refresh token if no new one sent
_refreshToken = _credential.getRefreshToken();
_loggedIn = true;
saveToPrefs();
// FIXME: if our refresh token is bad and we're not getting a new one, how do we deal with this?
Log("New refresh token: " + tokenResponse.getRefreshToken());
}
else if (errorMsg == null) errorMsg = "Get token error."; // shouldn't get here
authDone(errorMsg);
} });
}
}
private void authDone(String errorMsg) {
// Disconnect (we only need googleApiClient for login process)
if (_googleApiClient != null && _googleApiClient.isConnected()) _googleApiClient.disconnect();
_googleApiClient = null;
}
/*
public void signOut() {
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
public void revokeAccess() {
// FIXME: I don't know yet, but this may revoke access for all android devices
Auth.GoogleSignInApi.revokeAccess(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
*/
public void LogOut() {
_loggedIn = false;
saveToPrefs(); // don't clear refresh token as we may need again
}
// -------------------
// API Calls
// -------------------
public void makeApiCall() {
new Thread(new TestApiCall_Background()).start();
}
private class TestApiCall_Background implements Runnable {
public void run() {
FileAndErrorMsg fileAndErr = getFolderFromName_b("Many Files", null);
if (fileAndErr.errorMsg != null) Log("getFolderFromName_b error: " + fileAndErr.errorMsg);
else {
FileListAndErrorMsg fileListAndErr = getFileListInFolder_b(fileAndErr.file);
if (fileListAndErr.errorMsg != null)
Log("getFileListInFolder_b error: " + fileListAndErr.errorMsg);
else {
Log("file count: " + fileListAndErr.fileList.size());
for (File file : fileListAndErr.fileList) {
//Log(file.getName());
}
}
}
_handler.post(new Runnable() { public void run() {
// Main thread
} });
}
}
private FileAndErrorMsg getFolderFromName_b (String folderName, File parent) {
// parent can be null for top level
// Working with folders: https://developers.google.com/drive/v3/web/folder
File folder = null;
folderName = folderName.replace("'", "\\'"); // escape '
String q = String.format(Locale.US, "mimeType='application/vnd.google-apps.folder' and '%s' in parents and name='%s' and trashed=false", parent == null ? "root" : parent.getId(), folderName);
String errorMsg = null;
try {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).execute();
int foundCount = 0;
for (File file : result.getFiles()) {
foundCount++;
folder = file;
}
if (foundCount == 0) errorMsg = "Folder not found: " + folderName;
else if (foundCount > 1) errorMsg = "More than one folder found with name (" + foundCount + "): " + folderName;
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) folder = null;
return new FileAndErrorMsg(folder, errorMsg);
}
private FileListAndErrorMsg getFileListInFolder_b (File folder) {
// folder can be null for top level; does not return subfolder names
List<File> fileList = new ArrayList<File>();
String q = String.format(Locale.US, "mimeType != 'application/vnd.google-apps.folder' and '%s' in parents and trashed=false", folder == null ? "root" : folder.getId());
String errorMsg = null;
try {
String pageToken = null;
do {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).setPageToken(pageToken).execute();
fileList.addAll(result.getFiles());
pageToken = result.getNextPageToken();
} while (pageToken != null);
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) fileList = null;
return new FileListAndErrorMsg(fileList, errorMsg);
}
// -------------------
// Misc
// -------------------
private void Log(String msg) {
Log.v("ept", msg);
}
// -------------------
// Load/Save Tokens
// -------------------
private void loadFromPrefs() {
SharedPreferences pref = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
_loggedIn = pref.getBoolean("GoogleLoggedIn", false);
_refreshToken = pref.getString("GoogleRefreshToken", null);
}
private void saveToPrefs() {
SharedPreferences.Editor editor = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
editor.putBoolean("GoogleLoggedIn", _loggedIn);
editor.putString("GoogleRefreshToken", _refreshToken);
editor.apply(); // async
}
}
The latest example in https://developers.google.com/drive/v3/web/quickstart/android works out of the box.
Just do the following:
1 - Go to Google API console and create an OAuth2 Client ID using your package name and debug/release key as your Signing-certificate fingerprint.
2 - Enable Google Drive API
3 - Apply the following code
build.gradle : app
compile 'com.google.android.gms:play-services-auth:10.0.1'
compile 'pub.devrel:easypermissions:0.2.1'
compile('com.google.api-client:google-api-client-android:1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
compile('com.google.apis:google-api-services-drive:v3-rev57-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
In this code just change the scope to DriveScopes.DRIVE for full drive access
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.*;
import android.Manifest;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends Activity
implements EasyPermissions.PermissionCallbacks {
GoogleAccountCredential mCredential;
private TextView mOutputText;
private Button mCallApiButton;
ProgressDialog mProgress;
static final int REQUEST_ACCOUNT_PICKER = 1000;
static final int REQUEST_AUTHORIZATION = 1001;
static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;
private static final String BUTTON_TEXT = "Call Drive API";
private static final String PREF_ACCOUNT_NAME = "accountName";
private static final String[] SCOPES = { DriveScopes.DRIVE };
/**
* Create the main activity.
* #param savedInstanceState previously saved instance data.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout activityLayout = new LinearLayout(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
activityLayout.setLayoutParams(lp);
activityLayout.setOrientation(LinearLayout.VERTICAL);
activityLayout.setPadding(16, 16, 16, 16);
ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mCallApiButton = new Button(this);
mCallApiButton.setText(BUTTON_TEXT);
mCallApiButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallApiButton.setEnabled(false);
mOutputText.setText("");
getResultsFromApi();
mCallApiButton.setEnabled(true);
}
});
activityLayout.addView(mCallApiButton);
mOutputText = new TextView(this);
mOutputText.setLayoutParams(tlp);
mOutputText.setPadding(16, 16, 16, 16);
mOutputText.setVerticalScrollBarEnabled(true);
mOutputText.setMovementMethod(new ScrollingMovementMethod());
mOutputText.setText(
"Click the \'" + BUTTON_TEXT +"\' button to test the API.");
activityLayout.addView(mOutputText);
mProgress = new ProgressDialog(this);
mProgress.setMessage("Calling Drive API ...");
setContentView(activityLayout);
// Initialize credentials and service object.
mCredential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff());
}
/**
* Attempt to call the API, after verifying that all the preconditions are
* satisfied. The preconditions are: Google Play Services installed, an
* account was selected and the device currently has online access. If any
* of the preconditions are not satisfied, the app will prompt the user as
* appropriate.
*/
private void getResultsFromApi() {
if (! isGooglePlayServicesAvailable()) {
acquireGooglePlayServices();
} else if (mCredential.getSelectedAccountName() == null) {
chooseAccount();
} else if (! isDeviceOnline()) {
mOutputText.setText("No network connection available.");
} else {
new MakeRequestTask(mCredential).execute();
}
}
/**
* Attempts to set the account used with the API credentials. If an account
* name was previously saved it will use that one; otherwise an account
* picker dialog will be shown to the user. Note that the setting the
* account to use with the credentials object requires the app to have the
* GET_ACCOUNTS permission, which is requested here if it is not already
* present. The AfterPermissionGranted annotation indicates that this
* function will be rerun automatically whenever the GET_ACCOUNTS permission
* is granted.
*/
#AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
private void chooseAccount() {
if (EasyPermissions.hasPermissions(
this, Manifest.permission.GET_ACCOUNTS)) {
String accountName = getPreferences(Context.MODE_PRIVATE)
.getString(PREF_ACCOUNT_NAME, null);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
} else {
// Start a dialog from which the user can choose an account
startActivityForResult(
mCredential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
}
} else {
// Request the GET_ACCOUNTS permission via a user dialog
EasyPermissions.requestPermissions(
this,
"This app needs to access your Google account (via Contacts).",
REQUEST_PERMISSION_GET_ACCOUNTS,
Manifest.permission.GET_ACCOUNTS);
}
}
/**
* Called when an activity launched here (specifically, AccountPicker
* and authorization) exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
* #param requestCode code indicating which activity result is incoming.
* #param resultCode code indicating the result of the incoming
* activity result.
* #param data Intent (containing result data) returned by incoming
* activity result.
*/
#Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode != RESULT_OK) {
mOutputText.setText(
"This app requires Google Play Services. Please install " +
"Google Play Services on your device and relaunch this app.");
} else {
getResultsFromApi();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == RESULT_OK && data != null &&
data.getExtras() != null) {
String accountName =
data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
SharedPreferences settings =
getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.apply();
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
}
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode == RESULT_OK) {
getResultsFromApi();
}
break;
}
}
/**
* Respond to requests for permissions at runtime for API 23 and above.
* #param requestCode The request code passed in
* requestPermissions(android.app.Activity, String, int, String[])
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(
requestCode, permissions, grantResults, this);
}
/**
* Callback for when a permission is granted using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Callback for when a permission is denied using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Checks whether the device currently has a network connection.
* #return true if the device has a network connection, false otherwise.
*/
private boolean isDeviceOnline() {
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
/**
* Check that Google Play services APK is installed and up to date.
* #return true if Google Play Services is available and up to
* date on this device; false otherwise.
*/
private boolean isGooglePlayServicesAvailable() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
return connectionStatusCode == ConnectionResult.SUCCESS;
}
/**
* Attempt to resolve a missing, out-of-date, invalid or disabled Google
* Play Services installation via a user dialog, if possible.
*/
private void acquireGooglePlayServices() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
}
}
/**
* Display an error dialog showing that Google Play Services is missing
* or out of date.
* #param connectionStatusCode code describing the presence (or lack of)
* Google Play Services on this device.
*/
void showGooglePlayServicesAvailabilityErrorDialog(
final int connectionStatusCode) {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
Dialog dialog = apiAvailability.getErrorDialog(
MainActivity.this,
connectionStatusCode,
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
/**
* An asynchronous task that handles the Drive API call.
* Placing the API calls in their own task ensures the UI stays responsive.
*/
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private com.google.api.services.drive.Drive mService = null;
private Exception mLastError = null;
MakeRequestTask(GoogleAccountCredential credential) {
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
mService = new com.google.api.services.drive.Drive.Builder(
transport, jsonFactory, credential)
.setApplicationName("Drive API Android Quickstart")
.build();
}
/**
* Background task to call Drive API.
* #param params no parameters needed for this task.
*/
#Override
protected List<String> doInBackground(Void... params) {
try {
return getDataFromApi();
} catch (Exception e) {
mLastError = e;
cancel(true);
return null;
}
}
/**
* Fetch a list of up to 10 file names and IDs.
* #return List of Strings describing files, or an empty list if no files
* found.
* #throws IOException
*/
private List<String> getDataFromApi() throws IOException {
// Get a list of up to 10 files.
List<String> fileInfo = new ArrayList<String>();
FileList result = mService.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files != null) {
for (File file : files) {
fileInfo.add(String.format("%s (%s)\n",
file.getName(), file.getId()));
}
}
return fileInfo;
}
#Override
protected void onPreExecute() {
mOutputText.setText("");
mProgress.show();
}
#Override
protected void onPostExecute(List<String> output) {
mProgress.hide();
if (output == null || output.size() == 0) {
mOutputText.setText("No results returned.");
} else {
output.add(0, "Data retrieved using the Drive API:");
mOutputText.setText(TextUtils.join("\n", output));
}
}
#Override
protected void onCancelled() {
mProgress.hide();
if (mLastError != null) {
if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
showGooglePlayServicesAvailabilityErrorDialog(
((GooglePlayServicesAvailabilityIOException) mLastError)
.getConnectionStatusCode());
} else if (mLastError instanceof UserRecoverableAuthIOException) {
startActivityForResult(
((UserRecoverableAuthIOException) mLastError).getIntent(),
MainActivity.REQUEST_AUTHORIZATION);
} else {
mOutputText.setText("The following error occurred:\n"
+ mLastError.getMessage());
}
} else {
mOutputText.setText("Request cancelled.");
}
}
}
}
I am new here and reason for that I don't know all rules of the site yet, sorry about that!
I am building a Java application to Desktop and Android by using Gluon mobile plugin in Eclipse tool. I have variated code for Desktop and Android as described in an example (http://docs.gluonhq.com/samples/gonative/). So I have Android specific NativeService where I have a method to read files from external storage (that works). I have also studied how to request permissions on run time when using native Android environment (Storage permission error in Marshmallow ). I am able to request the permission, but I can not catch the result.
I am now requesting the permissions same way, but how I can receive the results? I cannot overrrive onRequestPermissionsResult as in the example, becauce I have not directly available android.support.v4.app stuff. Could someone help me and provide an example?
AndroidNativeService.java:
package com.tentaattorix;
import java.io.IOException;
import java.io.File;
import android.os.Environment;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.content.Context;
import javafxports.android.FXActivity;
import android.util.Log;
import android.widget.Toast;
import android.content.pm.PackageManager;
import android.os.Build;
import com.avustajat.LueJaKirjoita;
/**
* Sanaston luku: Android-natiivi toteutus!
* #author PT
* #version 24.10.2016
*
*/
public class AndroidNativeService implements NativeService {
private static final String TAG = "Tentaattorix Native Service";
private Sanastot sanastot = new Sanastot();
private static final int MY_PERMISSIONS_USE_EXTERNAL_STORAGE = 1;
public AndroidNativeService() {
//
}
#Override
public Sanastot haeSanastot(String juuriKansio, String sanastoRegex, char kommentti, char erotin) throws IOException {
String polku = Environment.getExternalStorageDirectory().toString();
String readme = "LueMinut.txt";
String kansioPolku ="";
//Luodaan kansio, jos sitä ei vielä ole.
File kansio = new File(polku, juuriKansio);
kansio.mkdir();
//Asetetaan oikeudet, jos vaikka auttais skannaukseen.
kansio.setWritable(true);
kansio.setReadable(true);
kansio.setExecutable(true);
kansioPolku = kansio.getAbsolutePath();
//Kysy oikeudet, jos niitä ei ole!
if (isStoragePermissionGranted()) {
//Luodaan kansioon tiedosto LueMinut.txt.
try {
LueJaKirjoita.luoLueMinut(kansioPolku, readme);
}
catch (IOException e){
throw e;
}
//Informoidaan uudesta kansiosta ja sinne tulevista tiedostoista järjestelmää!
scanFile(kansioPolku + File.separator + readme);
//Luetaan sanastot kansiosta.
sanastot = LueJaKirjoita.lueTiedostot(kansioPolku, sanastoRegex, kommentti, erotin);
}
// Jos sanastot ei sisällä yhtään sanastoa,
// niin laitetaan edes yksi :)
if (sanastot.annaLkm() < 1) {
String[] rivix = {"Tyhjä sanasto!", "Empty glossary!"};
Sanasto san = new Sanasto("sanasto_");
san.lisaa(rivix);
sanastot.lisaa(san);
}
return sanastot;
}
/**
* //Informoidaan uudesta kansiosta ja sinne tulevista tiedostoista järjestelmää!
* #param path lisätty polku+tiedosto
*/
private void scanFile(String path) {
MediaScannerConnection.scanFile(FXActivity.getInstance().getApplicationContext(),
new String[] { path }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("TAG", "Finished scanning " + path);
}
});
}
private boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (FXActivity.getInstance().checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
FXActivity.getInstance().requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_USE_EXTERNAL_STORAGE);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
}
For starters, you can add the android-support-v4.jar to your project:
Copy it from its location under ANDROID_HOME/extras/android/support/v4/android-support-v4.jar to a libs folder in your project, and then add the dependency to the build.gradle file:
dependencies {
androidCompile files('libs/android-support-v4.jar')
}
Assuming you are targeting Android SDK 23+:
android {
minSdkVersion '23'
compileSdkVersion '23'
targetSdkVersion '23'
manifest = 'src/android/AndroidManifest.xml'
}
then you know that by default all the permissions included in the manifest will be disabled.
If you want to check for permissions on runtime, you can define a new activity that takes care of requesting permissions with a built-in dialog (using ActivityCompat.requestPermissions), register this activity in the manifest, and call it from the FXActivity within a new intent that passes a list with the required permissions.
You just need to call FXActivity.getInstance().setOnActivityResultHandler() to listen to the end of that activity and resume the call if permissions were granted.
The following code is partly based in the PermissionHelper class.
I'll use the simple case of the Dialer service from the new Charm Down 3.0.0 library, that requires Manifest.permission.CALL_PHONE.
AndroidDialerService.java, under Android package
public class AndroidDialerAskService implements DialerAskService {
public static final String KEY_PERMISSIONS = "permissions";
public static final String KEY_GRANT_RESULTS = "grantResults";
public static final String KEY_REQUEST_CODE = "requestCode";
#Override
public void call(String number) {
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(FXActivity.getInstance(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
FXActivity.getInstance().setOnActivityResultHandler((requestCode, resultCode, data) -> {
if (requestCode == 11112) {
// if now we have permission, resume call
if (ContextCompat.checkSelfPermission(FXActivity.getInstance(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
call(number);
}
}
});
Intent permIntent = new Intent(FXActivity.getInstance(), PermissionRequestActivity.class);
permIntent.putExtra(KEY_PERMISSIONS, new String[]{Manifest.permission.CALL_PHONE});
permIntent.putExtra(KEY_REQUEST_CODE, 11111);
FXActivity.getInstance().startActivityForResult(permIntent, 11112);
return;
}
}
if (number != null && !number.isEmpty()) {
Uri uriNumber = Uri.parse("tel:" + number);
Intent dial = new Intent(Intent.ACTION_CALL, uriNumber);
FXActivity.getInstance().startActivity(dial);
}
}
public static class PermissionRequestActivity extends Activity {
private String[] permissions;
private int requestCode;
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
FXActivity.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
finish();
}
#Override
protected void onStart() {
super.onStart();
permissions = this.getIntent().getStringArrayExtra(KEY_PERMISSIONS);
requestCode = this.getIntent().getIntExtra(KEY_REQUEST_CODE, 0);
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
}
}
AndroidManifest.xml
. . .
<uses-permission android:name="android.permission.CALL_PHONE"/>
. . .
<activity android:name="javafxports.android.FXActivity" .../>
<activity android:name="com.gluonhq.charm.down.plugins.android.AndroidDialerService$PermissionRequestActivity" />
. . .
I have a strange behavior of my text to speech engine on android:
When I test text to speech on my Galaxy S5 everything is fine, audio is playing in turkish and in german.
On some other phones (such as one LG for example) the textToSpeech does work too- except in following case:
Export the app (build apk) and installing it manually on a phone
Switch to Turkish language (German language always works!)
The problem is that I get no error message- the TTS seems to get normally initialized.
Any hint would be really appreciated!
here is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setLogo(R.drawable.anne_eli_icons_pfeile_zurueck_weiss_17px);
getActionBar().setHomeButtonEnabled(true);
textToSpeech = new TextToSpeech(this, this);
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
log("onInit()");
int result = textToSpeech.setLanguage(new Locale(getTransLanguage()));
log("result:" + result);
textToSpeech.setSpeechRate(1.2f); // set speech speed rate
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Tools.showToast(this, "Language " + getTransLanguage() + " is not supported! Error code: " + result);
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
} else {
speechEnabled = true;
}
} else {
speechEnabled = false;
Tools.showToast(this, "Text to speech initialization failed!");
}
}
Before, check if TTS Service is avaliable:
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
/**
* This class demonstrates checking for a TTS engine, and if one is
* available it will spit out some speak.
*/
public class Main extends Activity implements TextToSpeech.OnInitListener
{
private TextToSpeech mTts;
// This code can be any value you want, its just a checksum.
private static final int MY_DATA_CHECK_CODE = 1234;
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Fire off an intent to check if a TTS engine is installed
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
}
/**
* Executed when a new TTS is instantiated. Some static text is spoken via TTS here.
* #param i
*/
public void onInit(int i)
{
mTts.speak("Hello folks, welcome to my little demo on Text To Speech.",
TextToSpeech.QUEUE_FLUSH, // Drop all pending entries in the playback queue.
null);
}
/**
* This is the callback from the TTS engine check, if a TTS is installed we
* create a new TTS instance (which in turn calls onInit), if not then we will
* create an intent to go off and install a TTS engine
* #param requestCode int Request code returned from the check for TTS engine.
* #param resultCode int Result code returned from the check for TTS engine.
* #param data Intent Intent returned from the TTS check.
*/
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == MY_DATA_CHECK_CODE)
{
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS)
{
// success, create the TTS instance
mTts = new TextToSpeech(this, this);
}
else
{
// missing data, install it
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}
/**
* Be kind, once you've finished with the TTS engine, shut it down so other
* applications can use it without us interfering with it :)
*/
#Override
public void onDestroy()
{
// Don't forget to shutdown!
if (mTts != null)
{
mTts.stop();
mTts.shutdown();
}
super.onDestroy();
}
}
I'm new to Android development, Eclipse and Java (done mostly .Net and IVR programming up till now), so when I tried to compile and run a sample app I found for TTS on the Droid, I wasn't surprised that I got a runtime error right away. The error is:
dalvikvm Failed resolving com/sample/TTSapp/AndroidTTSapp; interface 4 'android/speech/tts/TextToSpeech$OnInitListner;'
I suppose the OnInitListner method must be in one of the classes that was installed when I installed the Android SDK (release 1.6 R1 I believe), but I'm not sure how to import the associated class module into the current program. I can't find a speech/tts/TextToSpeech directory anywhere on my system. Do I need to download this directory from somewhere? Following is the Java source code for the demo TTS program I'm trying to run:
package com.sample.TTSApp;
import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.sample.TTSApp.R;
import java.util.Locale;
import java.util.Random;
public class AndroidTTSapp extends Activity implements
TextToSpeech.OnInitListener
{
private static final String TAG = "TextToSpeechDemo";
private TextToSpeech mTts;
private Button mAgainButton;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize text-to-speech. This is an asynchronous operation.
// The OnInitListener (second argument) is called after initialization completes.
// Instantiate TextToSpeech.OnInitListener
mTts = new TextToSpeech(this, this);
mAgainButton = (Button) findViewById(R.id.again_button);
mAgainButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
sayHello();
}
});
}
#Override
public void onDestroy()
{ // Don't forget to shutdown!
if (mTts != null)
{
mTts.stop();
mTts.shutdown();
}
super.onDestroy();
}
// Implements TextToSpeech.OnInitListener.
public void onInit(int status)
{
// status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
if (status == TextToSpeech.SUCCESS)
{
int result = mTts.setLanguage(Locale.US);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED)
{
// Language data is missing or the language is not supported.
Log.e(TAG, "Language is not available.");
}
else
{
// Check the documentation for other possible result codes. For example, the language may be available for the locale
// but not for the specified country and variant.
// The TTS engine has been successfully initialized. Allow the user to press the button for the app to speak again.
mAgainButton.setEnabled(true);
// Greet the user
sayHello();
}
}
else
{
// Initialization failed.
Log.e(TAG, "Could not initialize TextToSpeech.");
}
};
private static final Random RANDOM = new Random();
private static final String[] HELLOS =
{
"Hello World", "This is Text to speech demo by Zahid Shaikh"
};
private void sayHello()
{
// Select a random hello.
int i =0;
int helloLength = HELLOS.length;
String hello = HELLOS[i];
i++;
if(i == helloLength) i =0;
mTts.speak(hello,TextToSpeech.QUEUE_FLUSH,null);
}
}
Thanks in advance for any assitance anyone can give a beginner like myself.
Don Tilley
On device or emulator in */system/tts/lang_pico/* must be tts-lang-files (*.bin).
It's init TTS example:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
........
........
initTTS();
}
private void initTTS() {
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == MY_DATA_CHECK_CODE) {
if(resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
mTts = new TextToSpeech(this, this);
} else {
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}
public void onInit(int status) {
if(status == TextToSpeech.SUCCESS) {
int result = mTts.setLanguage(Locale.US);
if(result == TextToSpeech.LANG_AVAILABLE
|| result == TextToSpeech.LANG_COUNTRY_AVAILABLE) {
mTts.speak("Start system", TextToSpeech.QUEUE_FLUSH, null);
}
}
}