I have an EditText and a TextView, in which when I use speech recognition to talk, it is writing on the both.
The problem is when I press a button again the contents editText and textView get deleted.
Can somebody help me to continue from where I stopped?
For example, if I say "hey how are you" it writes it. On clicking start button lets
say I said "i'm ok". I want the second response to be "hey
how are you i'm ok " on the textview or edit text (Appended), and so on. However, it is overwriting the code.
Below is my code. Please help.
int m_waitSeconds = 0;
DataRecognitionClient dataClient = null;
MicrophoneRecognitionClient micClient = null;
FinalResponseStatus isReceivedResponse = FinalResponseStatus.NotReceived;
EditText _logText;
RadioGroup _radioGroup;
Button _buttonSelectMode;
Button _startButton;
EditText edit;
TextView txt;
String a;
public enum FinalResponseStatus { NotReceived, OK, Timeout }
/**
* Gets the primary subscription key
*/
public String getPrimaryKey() {
return this.getString(R.string.primaryKey);
}
/**
* Gets the LUIS application identifier.
* #return The LUIS application identifier.
*/
private String getLuisAppId() {
return this.getString(R.string.luisAppID);
}
/**
* Gets the LUIS subscription identifier.
* #return The LUIS subscription identifier.
*/
private String getLuisSubscriptionID() {
return this.getString(R.string.luisSubscriptionID);
}
/**
* Gets a value indicating whether or not to use the microphone.
* #return true if [use microphone]; otherwise, false.
*/
private Boolean getUseMicrophone() {
int id = this._radioGroup.getCheckedRadioButtonId();
return id == R.id.micIntentRadioButton ||
id == R.id.micDictationRadioButton ||
id == (R.id.micRadioButton - 1);
}
/**
* Gets a value indicating whether LUIS results are desired.
* #return true if LUIS results are to be returned otherwise, false.
*/
private Boolean getWantIntent() {
int id = this._radioGroup.getCheckedRadioButtonId();
return id == R.id.dataShortIntentRadioButton ||
id == R.id.micIntentRadioButton;
}
/**
* Gets the current speech recognition mode.
* #return The speech recognition mode.
*/
private SpeechRecognitionMode getMode() {
int id = this._radioGroup.getCheckedRadioButtonId();
if (id == R.id.micDictationRadioButton ||
id == R.id.dataLongRadioButton) {
return SpeechRecognitionMode.LongDictation;
}
return SpeechRecognitionMode.ShortPhrase;
}
/**
* Gets the default locale.
* #return The default locale.
*/
private String getDefaultLocale() {
return "en-us";
// ru-ru
}
/**
* Gets the short wave file path.
* #return The short wave file.
*/
private String getShortWaveFile() {
return "whatstheweatherlike.wav";
}
/**
* Gets the long wave file path.
* #return The long wave file.
*/
private String getLongWaveFile() {
return "batman.wav";
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.txt1);
edit = (EditText) findViewById(R.id.edit1);
this._logText = (EditText) findViewById(R.id.editText1);
this._radioGroup = (RadioGroup)findViewById(R.id.groupMode);
this._buttonSelectMode = (Button)findViewById(R.id.buttonSelectMode);
this._startButton = (Button) findViewById(R.id.button1);
if (getString(R.string.primaryKey).startsWith("Please")) {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.add_subscription_key_tip_title))
.setMessage(getString(R.string.add_subscription_key_tip))
.setCancelable(false)
.show();
}
// setup the buttons
final MainActivity This = this;
this._startButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
This.StartButton_Click(arg0);
}
});
this._buttonSelectMode.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
This.ShowMenu(This._radioGroup.getVisibility() == View.INVISIBLE);
}
});
this._radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup rGroup, int checkedId) {
This.RadioButton_Click(rGroup, checkedId);
}
});
this.ShowMenu(true);
}
private void ShowMenu(boolean show) {
if (show) {
this._radioGroup.setVisibility(View.VISIBLE);
this._logText.setVisibility(View.INVISIBLE);
} else {
this._radioGroup.setVisibility(View.INVISIBLE);
this._logText.setText("");
this._logText.setVisibility(View.VISIBLE);
}
}
/**
* Handles the Click event of the _startButton control.
*/
private void StartButton_Click(View arg0) {
this._startButton.setEnabled(false);
this._radioGroup.setEnabled(false);
this.m_waitSeconds = this.getMode() == SpeechRecognitionMode.ShortPhrase ? 20 : 200;
this.ShowMenu(false);
this.LogRecognitionStart();
if (this.getUseMicrophone()) {
if (this.micClient == null) {
if (this.getWantIntent()) {
this.WriteLine("--- Start microphone dictation with Intent detection ----");
this.micClient =
SpeechRecognitionServiceFactory.createMicrophoneClientWithIntent(
this,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else
{
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(
this,
this.getMode(),
this.getDefaultLocale(),
this,
this.getPrimaryKey());
}
}
this.micClient.startMicAndRecognition();
}
else
{
if (null == this.dataClient) {
if (this.getWantIntent()) {
this.dataClient =
SpeechRecognitionServiceFactory.createDataClientWithIntent(
this,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else {
this.dataClient = SpeechRecognitionServiceFactory.createDataClient(
this,
this.getMode(),
this.getDefaultLocale(),
this,
this.getPrimaryKey());
}
}
this.SendAudioHelper((this.getMode() == SpeechRecognitionMode.ShortPhrase) ? this.getShortWaveFile() : this.getLongWaveFile());
}
}
/**
* Logs the recognition start.
*/
private void LogRecognitionStart() {
String recoSource;
if (this.getUseMicrophone()) {
recoSource = "microphone";
} else if (this.getMode() == SpeechRecognitionMode.ShortPhrase) {
recoSource = "short wav file";
} else {
recoSource = "long wav file";
}
this.WriteLine("\n--- Start speech recognition using " + recoSource + " with " + this.getMode() + " mode in " + this.getDefaultLocale() + " language ----\n\n");
}
private void SendAudioHelper(String filename) {
RecognitionTask doDataReco = new RecognitionTask(this.dataClient, this.getMode(), filename);
try
{
doDataReco.execute().get(m_waitSeconds, TimeUnit.SECONDS);
}
catch (Exception e)
{
doDataReco.cancel(true);
isReceivedResponse = FinalResponseStatus.Timeout;
}
}
public void onFinalResponseReceived(final RecognitionResult response) {
boolean isFinalDicationMessage = this.getMode() == SpeechRecognitionMode.LongDictation &&
(response.RecognitionStatus == RecognitionStatus.EndOfDictation ||
response.RecognitionStatus == RecognitionStatus.DictationEndSilenceTimeout);
if (null != this.micClient && this.getUseMicrophone() && ((this.getMode() == SpeechRecognitionMode.ShortPhrase) || isFinalDicationMessage)) {
// we got the final result, so it we can end the mic reco. No need to do this
// for dataReco, since we already called endAudio() on it as soon as we were done
// sending all the data.
this.micClient.endMicAndRecognition();
}
if (isFinalDicationMessage) {
this._startButton.setEnabled(true);
this.isReceivedResponse = FinalResponseStatus.OK;
}
if (!isFinalDicationMessage) {
this.WriteLine("********* Final n-BEST Results *********");
for (int i = 0; i < response.Results.length; i++) {
this.WriteLine("[" + i + "]" + " Confidence=" + response.Results[i].Confidence +
" Text=\"" + response.Results[i].DisplayText + "\"");
// edit.setText(response.Results[i].DisplayText);
}
this.WriteLine();
}
}
/**
* Called when a final response is received and its intent is parsed
*/
public void onIntentReceived(final String payload) {
this.WriteLine("--- Intent received by onIntentReceived() ---");
this.WriteLine(payload);
this.WriteLine();
}
public void onPartialResponseReceived(final String response) {
int a = edit.length();
edit.setText(response);
txt.setText(edit.getText().toString());
// this.WriteLine("--- Partial result received by onPartialResponseReceived() ---");
this.WriteLine(response);
this.WriteLine();
}
public void onError(final int errorCode, final String response) {
this._startButton.setEnabled(true);
this.WriteLine("--- Error received by onError() ---");
this.WriteLine("Error code: " + SpeechClientStatus.fromInt(errorCode) + " " + errorCode);
this.WriteLine("Error text: " + response);
this.WriteLine();
}
/**
* Called when the microphone status has changed.
* #param recording The current recording state
*/
public void onAudioEvent(boolean recording) {
this.WriteLine("--- Microphone status change received by onAudioEvent() ---");
this.WriteLine("********* Microphone status: " + recording + " *********");
if (recording) {
this.WriteLine("Please start speaking.");
}
WriteLine();
if (!recording) {
this.micClient.endMicAndRecognition();
this._startButton.setEnabled(true);
}
}
/**
* Writes the line.
*/
private void WriteLine() {
this.WriteLine("");
}
/**
* Writes the line.
* #param text The line to write.
*/
private void WriteLine(String text) {
this._logText.append(text + " ");
}
/**
* Handles the Click event of the RadioButton control.
* #param rGroup The radio grouping.
* #param checkedId The checkedId.
*/
private void RadioButton_Click(RadioGroup rGroup, int checkedId) {
// Reset everything
if (this.micClient != null) {
this.micClient.endMicAndRecognition();
try {
this.micClient.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
this.micClient = null;
}
if (this.dataClient != null) {
try {
this.dataClient.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
this.dataClient = null;
}
this.ShowMenu(false);
this._startButton.setEnabled(true);
}
/*
* Speech recognition with data (for example from a file or audio source).
* The data is broken up into buffers and each buffer is sent to the Speech Recognition Service.
* No modification is done to the buffers, so the user can apply their
* own VAD (Voice Activation Detection) or Silence Detection
*
* #param dataClient
* #param recoMode
* #param filename
*/
private class RecognitionTask extends AsyncTask<Void, Void, Void> {
DataRecognitionClient dataClient;
SpeechRecognitionMode recoMode;
String filename;
RecognitionTask(DataRecognitionClient dataClient, SpeechRecognitionMode recoMode, String filename) {
this.dataClient = dataClient;
this.recoMode = recoMode;
this.filename = filename;
}
#Override
protected Void doInBackground(Void... params) {
try {
// Note for wave files, we can just send data from the file right to the server.
// In the case you are not an audio file in wave format, and instead you have just
// raw data (for example audio coming over bluetooth), then before sending up any
// audio data, you must first send up an SpeechAudioFormat descriptor to describe
// the layout and format of your raw audio data via DataRecognitionClient's sendAudioFormat() method.
// String filename = recoMode == SpeechRecognitionMode.ShortPhrase ? "whatstheweatherlike.wav" : "batman.wav";
InputStream fileStream = getAssets().open(filename);
int bytesRead = 0;
byte[] buffer = new byte[1024];
do {
// Get Audio data to send into byte buffer.
bytesRead = fileStream.read(buffer);
if (bytesRead > -1) {
// Send of audio data to service.
dataClient.sendAudio(buffer, bytesRead);
}
} while (bytesRead > 0);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
finally {
dataClient.endAudio();
}
return null;
}
}
i edited in onPartialResponseReceived()
Related
Currently, I am implementing google Speech to Text in my project. The sample code referred is this: Click Here.
I have used the SpeechService and Voice Recorder class from this project.
public class SpeechService extends Service {
public static final List<String> SCOPE =
Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
private static final String TAG = "SpeechService";
private static final String PREFS = "SpeechService";
private static final String PREF_ACCESS_TOKEN_VALUE = "access_token_value";
private static final String PREF_ACCESS_TOKEN_EXPIRATION_TIME = "access_token_expiration_time";
/**
* We reuse an access token if its expiration time is longer than this.
*/
private static final int ACCESS_TOKEN_EXPIRATION_TOLERANCE = 30 * 60 * 1000; // thirty minutes
/**
* We refresh the current access token before it expires.
*/
private static final int ACCESS_TOKEN_FETCH_MARGIN = 60 * 1000; // one minute
private static final String HOSTNAME = "speech.googleapis.com";
private static final int PORT = 443;
private static Handler mHandler;
private final SpeechBinder mBinder = new SpeechBinder();
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final StreamObserver<StreamingRecognizeResponse> mResponseObserver
= new StreamObserver<StreamingRecognizeResponse>() {
#Override
public void onNext(StreamingRecognizeResponse response) {
Log.e("Speech", "Recognized");
String text = null;
boolean isFinal = false;
if (response.getResultsCount() > 0) {
System.out.println("result count....."+String.valueOf(response.getResultsCount()));
final StreamingRecognitionResult result = response.getResults(0);
isFinal = result.getIsFinal();
if (result.getAlternativesCount() > 0) {
final SpeechRecognitionAlternative alternative = result.getAlternatives(0);
text = alternative.getTranscript();
}
}
if (text != null && isFinal) {
for (Listener listener : mListeners) {
listener.onSpeechRecognized(text, isFinal);
}
} else {
for (Listener listener : mListeners) {
listener.onRandomStupidity();
}
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "Error calling the API.", t);
for(Listener listener : mListeners){
listener.onErrorRecognizing();
}
}
#Override
public void onCompleted() {
Log.i(TAG, "API completed.");
}
};
private volatile AccessTokenTask mAccessTokenTask;
private final Runnable mFetchAccessTokenRunnable = new Runnable() {
#Override
public void run() {
fetchAccessToken();
}
};
private SpeechGrpc.SpeechStub mApi;
private StreamObserver<StreamingRecognizeRequest> mRequestObserver;
public static SpeechService from(IBinder binder) {
return ((SpeechBinder) binder).getService();
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
fetchAccessToken();
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mFetchAccessTokenRunnable);
mHandler = null;
// Release the gRPC channel.
if (mApi != null) {
final ManagedChannel channel = (ManagedChannel) mApi.getChannel();
if (channel != null && !channel.isShutdown()) {
try {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Error shutting down the gRPC channel.", e);
}
}
mApi = null;
}
}
private void fetchAccessToken() {
if (mAccessTokenTask != null) {
return;
}
mAccessTokenTask = new AccessTokenTask();
mAccessTokenTask.execute();
}
private String getDefaultLanguageCode() {
final LangInnerResponse languageToLearn = MemoryCache.getLanguageToLearn();
if(languageToLearn != null) {
Log.e("Test Lang", languageToLearn.getCode());
return languageToLearn.getCode();
} else {
final Locale locale = Locale.getDefault();
final StringBuilder language = new StringBuilder(locale.getLanguage());
final String country = locale.getCountry();
if (!TextUtils.isEmpty(country)) {
language.append("-");
language.append(country);
}
return language.toString();
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void addListener(#NonNull Listener listener) {
mListeners.add(listener);
}
public void removeListener(#NonNull Listener listener) {
mListeners.remove(listener);
}
/**
** Starts recognizing speech audio.
*
* #param sampleRate The sample rate of the audio.
*/
public void startRecognizing(int sampleRate) {
if (mApi == null) {
Log.w(TAG, "API not ready. Ignoring the request.");
return;
}
System.out.println("calling api....");
// Configure the API
mRequestObserver = mApi.streamingRecognize(mResponseObserver);
mRequestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setStreamingConfig(StreamingRecognitionConfig.newBuilder()
.setConfig(RecognitionConfig.newBuilder()
.setLanguageCode(getDefaultLanguageCode())
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setSampleRateHertz(sampleRate)
.build())
.setInterimResults(true)
.setSingleUtterance(true)
.build())
.build());
}
/**
* Recognizes the speech audio. This method should be called every time a chunk of byte buffer
* is ready.
*
* #param data The audio data.
* #param size The number of elements that are actually relevant in the {#code data}.
*/
public void recognize(byte[] data, int size) {
if (mRequestObserver == null) {
return;
}
// Call the streaming recognition API
mRequestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setAudioContent(ByteString.copyFrom(data, 0, size))
.build());
}
/**
* Finishes recognizing speech audio.
*/
public void finishRecognizing() {
if (mRequestObserver == null) {
return;
}
mRequestObserver.onCompleted();
mRequestObserver = null;
}
public interface Listener {
/**
* Called when a new piece of text was recognized by the Speech API.
*
* #param text The text.
* #param isFinal {#code true} when the API finished processing audio.
*/
void onSpeechRecognized(String text, boolean isFinal);
void onErrorRecognizing();
void onRandomStupidity();
}
/**
* Authenticates the gRPC channel using the specified {#link GoogleCredentials}.
*/
private static class GoogleCredentialsInterceptor implements ClientInterceptor {
private final Credentials mCredentials;
private Metadata mCached;
private Map<String, List<String>> mLastMetadata;
GoogleCredentialsInterceptor(Credentials credentials) {
mCredentials = credentials;
}
private static Metadata toHeaders(Map<String, List<String>> metadata) {
Metadata headers = new Metadata();
if (metadata != null) {
for (String key : metadata.keySet()) {
Metadata.Key<String> headerKey = Metadata.Key.of(
key, Metadata.ASCII_STRING_MARSHALLER);
for (String value : metadata.get(key)) {
headers.put(headerKey, value);
}
}
}
return headers;
}
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
final Channel next) {
return new ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)) {
#Override
protected void checkedStart(Listener<RespT> responseListener, Metadata headers)
throws StatusException {
Metadata cachedSaved;
URI uri = serviceUri(next, method);
synchronized (this) {
Map<String, List<String>> latestMetadata = getRequestMetadata(uri);
if (mLastMetadata == null || mLastMetadata != latestMetadata) {
mLastMetadata = latestMetadata;
mCached = toHeaders(mLastMetadata);
}
cachedSaved = mCached;
}
headers.merge(cachedSaved);
delegate().start(responseListener, headers);
}
};
}
/**
* Generate a JWT-specific service URI. The URI is simply an identifier with enough
* information for a service to know that the JWT was intended for it. The URI will
* commonly be verified with a simple string equality check.
*/
private URI serviceUri(Channel channel, MethodDescriptor<?, ?> method)
throws StatusException {
String authority = channel.authority();
if (authority == null) {
throw Status.UNAUTHENTICATED
.withDescription("Channel has no authority")
.asException();
}
// Always use HTTPS, by definition.
final String scheme = "https";
final int defaultPort = 443;
String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());
URI uri;
try {
uri = new URI(scheme, authority, path, null, null);
} catch (URISyntaxException e) {
throw Status.UNAUTHENTICATED
.withDescription("Unable to construct service URI for auth")
.withCause(e).asException();
}
// The default port must not be present. Alternative ports should be present.
if (uri.getPort() == defaultPort) {
uri = removePort(uri);
}
return uri;
}
private URI removePort(URI uri) throws StatusException {
try {
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,
uri.getPath(), uri.getQuery(), uri.getFragment());
} catch (URISyntaxException e) {
throw Status.UNAUTHENTICATED
.withDescription("Unable to construct service URI after removing port")
.withCause(e).asException();
}
}
private Map<String, List<String>> getRequestMetadata(URI uri) throws StatusException {
try {
return mCredentials.getRequestMetadata(uri);
} catch (IOException e) {
throw Status.UNAUTHENTICATED.withCause(e).asException();
}
}
}
private class SpeechBinder extends Binder {
SpeechService getService() {
return SpeechService.this;
}
}
private class CreateApiSingle implements SingleOnSubscribe<SpeechGrpc.SpeechStub> {
#Override
public void subscribe(SingleEmitter<SpeechGrpc.SpeechStub> emitter) throws Exception {
final AccessToken accessToken = generateCredentials();
final SpeechGrpc.SpeechStub api = generateApi(accessToken);
emitter.onSuccess(api);
}
private AccessToken generateCredentials() throws IOException {
final SharedPreferences prefs =
getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
// Check if the current token is still valid for a while
if (tokenValue != null && expirationTime > 0) {
if (expirationTime
> System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TOLERANCE) {
return new AccessToken(tokenValue, new Date(expirationTime));
}
}
// ***** WARNING *****
// In this sample, we load the credential from a JSON file stored in a raw resource
// folder of this client app. You should never do this in your app. Instead, store
// the file in your server and obtain an access token from there.
// *******************
final InputStream stream = getResources().openRawResource(R.raw.credential);
final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
.createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
prefs.edit()
.putString(PREF_ACCESS_TOKEN_VALUE, token.getTokenValue())
.putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME,
token.getExpirationTime().getTime())
.apply();
stream.close();
return token;
}
private SpeechGrpc.SpeechStub generateApi(AccessToken accessToken) {
final ManagedChannel channel = new OkHttpChannelProvider()
.builderForAddress(HOSTNAME, PORT)
.nameResolverFactory(new DnsNameResolverProvider())
.intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
.createScoped(SCOPE)))
.build();
return SpeechGrpc.newStub(channel);
}
}
private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> {
#Override
protected AccessToken doInBackground(Void... voids) {
final SharedPreferences prefs =
getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
// Check if the current token is still valid for a while
if (tokenValue != null && expirationTime > 0) {
if (expirationTime
> System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TOLERANCE) {
return new AccessToken(tokenValue, new Date(expirationTime));
}
}
// ***** WARNING *****
// In this sample, we load the credential from a JSON file stored in a raw resource
// folder of this client app. You should never do this in your app. Instead, store
// the file in your server and obtain an access token from there.
// *******************
final InputStream stream = getResources().openRawResource(R.raw.credential);
try {
final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
.createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
prefs.edit()
.putString(PREF_ACCESS_TOKEN_VALUE, token.getTokenValue())
.putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME,
token.getExpirationTime().getTime())
.apply();
return token;
} catch (IOException e) {
Log.e(TAG, "Failed to obtain access token.", e);
}
return null;
}
#Override
protected void onPostExecute(AccessToken accessToken) {
mAccessTokenTask = null;
final ManagedChannel channel = new OkHttpChannelProvider()
.builderForAddress(HOSTNAME, PORT)
.nameResolverFactory(new DnsNameResolverProvider())
.intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
.createScoped(SCOPE)))
.build();
mApi = SpeechGrpc.newStub(channel);
// Schedule access token refresh before it expires
if (mHandler != null) {
mHandler.postDelayed(mFetchAccessTokenRunnable,
Math.max(accessToken.getExpirationTime().getTime()
- System.currentTimeMillis()
- ACCESS_TOKEN_FETCH_MARGIN, ACCESS_TOKEN_EXPIRATION_TOLERANCE));
}
}
}}
public class VoiceRecorder {
private static final int[] SAMPLE_RATE_CANDIDATES = new int[]{48000, 44100};
private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private static final int AMPLITUDE_THRESHOLD = 1500;
private static final int SPEECH_TIMEOUT_MILLIS = 2000;
private static final int MAX_SPEECH_LENGTH_MILLIS = 30 * 1000;
public static abstract class Callback {
/**
* Called when the recorder starts hearing voice.
*/
public void onVoiceStart() {
}
/**
* Called when the recorder is hearing voice.
*
* #param data The audio data in {#link AudioFormat#ENCODING_PCM_16BIT}.
* #param size The size of the actual data in {#code data}.
*/
public void onVoice(byte[] data, int size) {
}
/**
* Called when the recorder stops hearing voice.
*/
public void onVoiceEnd() {
}
}
private final Callback mCallback;
private AudioRecord mAudioRecord;
private Thread mThread;
private byte[] mBuffer;
private final Object mLock = new Object();
/** The timestamp of the last time that voice is heard. */
private long mLastVoiceHeardMillis = Long.MAX_VALUE;
/** The timestamp when the current voice is started. */
private long mVoiceStartedMillis;
public VoiceRecorder(#NonNull Callback callback) {
mCallback = callback;
}
/**
* Starts recording audio.
*
* <p>The caller is responsible for calling {#link #stop()} later.</p>
*/
public void start() {
// Stop recording if it is currently ongoing.
stop();
// Try to create a new recording session.
mAudioRecord = createAudioRecord();
if (mAudioRecord == null) {
throw new RuntimeException("Cannot instantiate VoiceRecorder");
}
// Start recording.
mAudioRecord.startRecording();
// Start processing the captured audio.
mThread = new Thread(new ProcessVoice());
mThread.start();
}
/**
* Stops recording audio.
*/
public void stop() {
synchronized (mLock) {
System.out.println("stop audio record....");
dismiss();
if (mThread != null) {
mThread.interrupt();
mThread = null;
}
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
mBuffer = null;
System.out.println("stop audio record....2");
}
}
/**
* Dismisses the currently ongoing utterance.
*/
public void dismiss() {
if (mLastVoiceHeardMillis != Long.MAX_VALUE) {
mLastVoiceHeardMillis = Long.MAX_VALUE;
mCallback.onVoiceEnd();
}
}
/**
* Retrieves the sample rate currently used to record audio.
*
* #return The sample rate of recorded audio.
*/
public int getSampleRate() {
if (mAudioRecord != null) {
return mAudioRecord.getSampleRate();
}
return 0;
}
/**
* Creates a new {#link AudioRecord}.
*
* #return A newly created {#link AudioRecord}, or null if it cannot be created (missing
* permissions?).
*/
private AudioRecord createAudioRecord() {
for (int sampleRate : SAMPLE_RATE_CANDIDATES) {
final int sizeInBytes = AudioRecord.getMinBufferSize(sampleRate, CHANNEL, ENCODING);
if (sizeInBytes == AudioRecord.ERROR_BAD_VALUE) {
continue;
}
final AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRate, CHANNEL, ENCODING, sizeInBytes);
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
mBuffer = new byte[sizeInBytes];
return audioRecord;
} else {
audioRecord.release();
}
}
return null;
}
/**
* Continuously processes the captured audio and notifies {#link #mCallback} of corresponding
* events.
*/
private class ProcessVoice implements Runnable {
#Override
public void run() {
while (true) {
synchronized (mLock) {
if (Thread.currentThread().isInterrupted()) {
break;
}
final int size = mAudioRecord.read(mBuffer, 0, mBuffer.length);
final long now = System.currentTimeMillis();
if (isHearingVoice(mBuffer, size)) {
if (mLastVoiceHeardMillis == Long.MAX_VALUE) {
mVoiceStartedMillis = now;
mCallback.onVoiceStart();
}
mCallback.onVoice(mBuffer, size);
mLastVoiceHeardMillis = now;
if (now - mVoiceStartedMillis > MAX_SPEECH_LENGTH_MILLIS) {
end();
}
} else if (mLastVoiceHeardMillis != Long.MAX_VALUE) {
mCallback.onVoice(mBuffer, size);
if (now - mLastVoiceHeardMillis > SPEECH_TIMEOUT_MILLIS) {
end();
}
}
}
}
}
private void end() {
mLastVoiceHeardMillis = Long.MAX_VALUE;
mCallback.onVoiceEnd();
System.out.println("end...");
}
private boolean isHearingVoice(byte[] buffer, int size) {
for (int i = 0; i < size - 1; i += 2) {
// The buffer has LINEAR16 in little endian.
int s = buffer[i + 1];
if (s < 0) s *= -1;
s <<= 8;
s += Math.abs(buffer[i]);
if (s > AMPLITUDE_THRESHOLD) {
return true;
}
}
return false;
}
}}
Then I implemented the Speech Service & Voice Recorder callback as follows:
private VoiceRecorder voiceRecorder;
private final SpeechService.Listener speechServiceListener = new SpeechService.Listener() {
#Override
public void onSpeechRecognized(final String text, final boolean isFinal) {
if (isFinal) {
System.out.println("ui thread...");
if (!TextUtils.isEmpty(text)) {
runOnUiThread(() -> {
showMessage(text);
flingAnswer(text);
});
}
}
}
#Override
public void onErrorRecognizing() {
showMessage("Please try again. Could not detect.");
}
#Override
public void onRandomStupidity() {
}
};
private SpeechService speechService;
private final VoiceRecorder.Callback voiceCallback = new VoiceRecorder.Callback() {
#Override
public void onVoiceStart() {
if (speechService != null) {
System.out.println("voice start....");
speechService.startRecognizing(voiceRecorder.getSampleRate());
}
}
#Override
public void onVoice(byte[] data, int size) {
if (speechService != null) {
speechService.recognize(data, size);
}
}
#Override
public void onVoiceEnd() {
if (speechService != null) {
speechService.finishRecognizing();
}
}
};
private final ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
speechService = SpeechService.from(binder);
speechService.addListener(speechServiceListener);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
speechService = null;
}
};
For voice input this is the code:
#Override
public void stopRecognizing() {
stopVoiceRecorder();
Log.e("Recording", "Stopped");
}
#Override
public void startRecognizing() {
if (permissionManager != null && permissionManager.askForPermissions()) {
startVoiceRecorder();
vibrate.vibrate(50);//Providing haptic feedback to user on press.
}
Log.e("Recording", "Started");
}
binding.imgVoice.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
System.out.println("up...");
mCallback.stopRecognizing();
binding.imgVoice
.animate()
.scaleX(1.0f)
.scaleY(1.0f);
binding.imgVoice.setVisibility(View.GONE);
binding.progressBar.setVisibility(View.VISIBLE);
break;
case MotionEvent.ACTION_DOWN:
System.out.println("down...");
binding.imgVoice
.animate()
.scaleX(1.8f)
.scaleY(1.8f);
mCallback.startRecognizing();
break;
}
return true;
});
}
When I press the mic, event registered as Action_Down, I start the voice recorder and on releasing the mic , voice recorder is stopped. Also, with the Action_Down I am scaling up the mic icon which needs to be scaled down on Action_Up . But the ui freezes as a whole most of the times. I find that the onNext() callback for StreamObserver is continuously being invoked before the isFinal becomes true.
private void startVoiceRecorder() {
if (voiceRecorder != null) {
voiceRecorder.stop();
}
voiceRecorder = new VoiceRecorder(voiceCallback);
voiceRecorder.start();
}
private void stopVoiceRecorder() {
if (voiceRecorder != null) {
voiceRecorder.stop();
voiceRecorder = null;
}
}
But I want the mic to scale down as soon as I release the mic(on Action up event) which is not happening.
So if anyone can help me over this?
Thanks in Advance.
Good day everyone, I would like to ask, hat is the cause of that ANR?. In my project I have service which is binded in a activity. Now when I exit in that activity the app is hang for a moment. My thought is that the service is still running though I unbind it in onStop() of the activity.
Here is my service class
public class SpeechService extends Service {
public interface Listener {
/**
* Called when a new piece of text was recognized by the Speech API.
*
* #param text The text.
* #param isFinal {#code true} when the API finished processing audio.
*/
void onSpeechRecognized(String text, boolean isFinal);
}
private static final String TAG = "SpeechService";
private static final String PREFS = "SpeechService";
private static final String PREF_ACCESS_TOKEN_VALUE = "access_token_value";
private static final String PREF_ACCESS_TOKEN_EXPIRATION_TIME = "access_token_expiration_time";
/** We reuse an access token if its expiration time is longer than this. */
private static final int ACCESS_TOKEN_EXPIRATION_TOLERANCE = 30 * 60 * 1000; // thirty minutes
/** We refresh the current access token before it expires. */
private static final int ACCESS_TOKEN_FETCH_MARGIN = 60 * 1000; // one minute
public static final List<String> SCOPE =
Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
private static final String HOSTNAME = "speech.googleapis.com";
private static final int PORT = 443;
private final SpeechBinder mBinder = new SpeechBinder();
private final ArrayList<Listener> mListeners = new ArrayList<>();
private volatile AccessTokenTask mAccessTokenTask;
private SpeechGrpc.SpeechStub mApi;
private static Handler mHandler;
private final StreamObserver<StreamingRecognizeResponse> mResponseObserver
= new StreamObserver<StreamingRecognizeResponse>() {
#Override
public void onNext(StreamingRecognizeResponse response) {
String text = null;
boolean isFinal = false;
if (response.getResultsCount() > 0) {
final StreamingRecognitionResult result = response.getResults(0);
isFinal = result.getIsFinal();
if (result.getAlternativesCount() > 0) {
final SpeechRecognitionAlternative alternative = result.getAlternatives(0);
text = alternative.getTranscript();
}
}
if (text != null) {
for (Listener listener : mListeners) {
listener.onSpeechRecognized(text, isFinal);
}
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "Error calling the API.", t);
}
#Override
public void onCompleted() {
Log.i(TAG, "API completed.");
}
};
private final StreamObserver<RecognizeResponse> mFileResponseObserver
= new StreamObserver<RecognizeResponse>() {
#Override
public void onNext(RecognizeResponse response) {
String text = null;
if (response.getResultsCount() > 0) {
final SpeechRecognitionResult result = response.getResults(0);
if (result.getAlternativesCount() > 0) {
final SpeechRecognitionAlternative alternative = result.getAlternatives(0);
text = alternative.getTranscript();
}
}
if (text != null) {
for (Listener listener : mListeners) {
listener.onSpeechRecognized(text, true);
}
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "Error calling the API.", t);
}
#Override
public void onCompleted() {
Log.i(TAG, "API completed.");
}
};
private StreamObserver<StreamingRecognizeRequest> mRequestObserver;
public static SpeechService from(IBinder binder) {
return ((SpeechBinder) binder).getService();
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
fetchAccessToken();
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mFetchAccessTokenRunnable);
mHandler = null;
// Release the gRPC channel.
if (mApi != null) {
final ManagedChannel channel = (ManagedChannel) mApi.getChannel();
if (channel != null && !channel.isShutdown()) {
try {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Error shutting down the gRPC channel.", e);
}
}
mApi = null;
}
}
private void fetchAccessToken() {
if (mAccessTokenTask != null) {
return;
}
mAccessTokenTask = new AccessTokenTask();
mAccessTokenTask.execute();
}
private String getDefaultLanguageCode() {
final Locale locale = Locale.getDefault();
final StringBuilder language = new StringBuilder(locale.getLanguage());
final String country = locale.getCountry();
if (!TextUtils.isEmpty(country)) {
language.append("-");
language.append(country);
}
return language.toString();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void addListener(#NonNull Listener listener) {
mListeners.add(listener);
}
public void removeListener(#NonNull Listener listener) {
mListeners.remove(listener);
}
/**
* Starts recognizing speech audio.
*
* #param sampleRate The sample rate of the audio.
*/
public void startRecognizing(int sampleRate) {
if (mApi == null) {
Log.w(TAG, "API not ready. Ignoring the request.");
return;
}
// Configure the API
mRequestObserver = mApi.streamingRecognize(mResponseObserver);
mRequestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setStreamingConfig(StreamingRecognitionConfig.newBuilder()
.setConfig(RecognitionConfig.newBuilder()
.setLanguageCode(getDefaultLanguageCode())
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setSampleRateHertz(sampleRate)
.build())
.setInterimResults(true)
.setSingleUtterance(true)
.build())
.build());
}
/**
* Recognizes the speech audio. This method should be called every time a chunk of byte buffer
* is ready.
*
* #param data The audio data.
* #param size The number of elements that are actually relevant in the {#code data}.
*/
public void recognize(byte[] data, int size) {
if (mRequestObserver == null) {
return;
}
// Call the streaming recognition API
mRequestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setAudioContent(ByteString.copyFrom(data, 0, size))
.build());
}
/**
* Finishes recognizing speech audio.
*/
public void finishRecognizing() {
if (mRequestObserver == null) {
return;
}
mRequestObserver.onCompleted();
mRequestObserver = null;
}
/**
* Recognize all data from the specified {#link InputStream}.
*
* #param stream The audio data.
*/
public void recognizeInputStream(InputStream stream) {
try {
mApi.recognize(
RecognizeRequest.newBuilder()
.setConfig(RecognitionConfig.newBuilder()
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setLanguageCode("en-US")
.setSampleRateHertz(16000)
.build())
.setAudio(RecognitionAudio.newBuilder()
.setContent(ByteString.readFrom(stream))
.build())
.build(),
mFileResponseObserver);
} catch (IOException e) {
Log.e(TAG, "Error loading the input", e);
}
}
private class SpeechBinder extends Binder {
SpeechService getService() {
return SpeechService.this;
}
}
private final Runnable mFetchAccessTokenRunnable = new Runnable() {
#Override
public void run() {
fetchAccessToken();
}
};
private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> {
#Override
protected AccessToken doInBackground(Void... voids) {
final SharedPreferences prefs =
getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
// Check if the current token is still valid for a while
if (tokenValue != null && expirationTime > 0) {
if (expirationTime
> System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TOLERANCE) {
return new AccessToken(tokenValue, new Date(expirationTime));
}
}
// ***** WARNING *****
// In this sample, we load the credential from a JSON file stored in a raw resource
// folder of this client app. You should never do this in your app. Instead, store
// the file in your server and obtain an access token from there.
// *******************
final InputStream stream = getResources().openRawResource(R.raw.credential);
try {
final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
.createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
prefs.edit()
.putString(PREF_ACCESS_TOKEN_VALUE, token.getTokenValue())
.putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME,
token.getExpirationTime().getTime())
.apply();
return token;
} catch (IOException e) {
Log.e(TAG, "Failed to obtain access token.", e);
}
return null;
}
#Override
protected void onPostExecute(AccessToken accessToken) {
mAccessTokenTask = null;
final ManagedChannel channel = new OkHttpChannelProvider()
.builderForAddress(HOSTNAME, PORT)
.nameResolverFactory(new DnsNameResolverProvider())
.intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
.createScoped(SCOPE)))
.build();
mApi = SpeechGrpc.newStub(channel);
// Schedule access token refresh before it expires
if (mHandler != null) {
mHandler.postDelayed(mFetchAccessTokenRunnable,
Math.max(accessToken.getExpirationTime().getTime()
- System.currentTimeMillis()
- ACCESS_TOKEN_FETCH_MARGIN, ACCESS_TOKEN_EXPIRATION_TOLERANCE));
}
}
}
/**
* Authenticates the gRPC channel using the specified {#link GoogleCredentials}.
*/
private static class GoogleCredentialsInterceptor implements ClientInterceptor {
private final Credentials mCredentials;
private Metadata mCached;
private Map<String, List<String>> mLastMetadata;
GoogleCredentialsInterceptor(Credentials credentials) {
mCredentials = credentials;
}
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
final Channel next) {
return new ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)) {
#Override
protected void checkedStart(Listener<RespT> responseListener, Metadata headers)
throws StatusException {
Metadata cachedSaved;
URI uri = serviceUri(next, method);
synchronized (this) {
Map<String, List<String>> latestMetadata = getRequestMetadata(uri);
if (mLastMetadata == null || mLastMetadata != latestMetadata) {
mLastMetadata = latestMetadata;
mCached = toHeaders(mLastMetadata);
}
cachedSaved = mCached;
}
headers.merge(cachedSaved);
delegate().start(responseListener, headers);
}
};
}
/**
* Generate a JWT-specific service URI. The URI is simply an identifier with enough
* information for a service to know that the JWT was intended for it. The URI will
* commonly be verified with a simple string equality check.
*/
private URI serviceUri(Channel channel, MethodDescriptor<?, ?> method)
throws StatusException {
String authority = channel.authority();
if (authority == null) {
throw Status.UNAUTHENTICATED
.withDescription("Channel has no authority")
.asException();
}
// Always use HTTPS, by definition.
final String scheme = "https";
final int defaultPort = 443;
String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());
URI uri;
try {
uri = new URI(scheme, authority, path, null, null);
} catch (URISyntaxException e) {
throw Status.UNAUTHENTICATED
.withDescription("Unable to construct service URI for auth")
.withCause(e).asException();
}
// The default port must not be present. Alternative ports should be present.
if (uri.getPort() == defaultPort) {
uri = removePort(uri);
}
return uri;
}
private URI removePort(URI uri) throws StatusException {
try {
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,
uri.getPath(), uri.getQuery(), uri.getFragment());
} catch (URISyntaxException e) {
throw Status.UNAUTHENTICATED
.withDescription("Unable to construct service URI after removing port")
.withCause(e).asException();
}
}
private Map<String, List<String>> getRequestMetadata(URI uri) throws StatusException {
try {
return mCredentials.getRequestMetadata(uri);
} catch (IOException e) {
throw Status.UNAUTHENTICATED.withCause(e).asException();
}
}
private static Metadata toHeaders(Map<String, List<String>> metadata) {
Metadata headers = new Metadata();
if (metadata != null) {
for (String key : metadata.keySet()) {
Metadata.Key<String> headerKey = Metadata.Key.of(
key, Metadata.ASCII_STRING_MARSHALLER);
for (String value : metadata.get(key)) {
headers.put(headerKey, value);
}
}
}
return headers;
}
}
}
and here is my activity class
public class MainActivity extends AppCompatActivity implements MessageDialogFragment.Listener {
private static final String FRAGMENT_MESSAGE_DIALOG = "message_dialog";
private static final String STATE_RESULTS = "results";
private static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
private SpeechService mSpeechService;
private VoiceRecorder mVoiceRecorder;
private final VoiceRecorder.Callback mVoiceCallback = new VoiceRecorder.Callback() {
#Override
public void onVoiceStart() {
showStatus(true);
if (mSpeechService != null) {
mSpeechService.startRecognizing(mVoiceRecorder.getSampleRate());
}
}
#Override
public void onVoice(byte[] data, int size) {
if (mSpeechService != null) {
mSpeechService.recognize(data, size);
}
}
#Override
public void onVoiceEnd() {
showStatus(false);
if (mSpeechService != null) {
mSpeechService.finishRecognizing();
}
}
};
// Resource caches
private int mColorHearing;
private int mColorNotHearing;
// View references
private TextView mStatus;
private TextView mText, mResult;
private Button editButton, clearButton;
private SharedPreferences settings;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
mSpeechService = SpeechService.from(binder);
mSpeechService.addListener(mSpeechServiceListener);
mStatus.setVisibility(View.VISIBLE);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mSpeechService = null;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
final Resources resources = getResources();
final Resources.Theme theme = getTheme();
mColorHearing = ResourcesCompat.getColor(resources, R.color.status_hearing, theme);
mColorNotHearing = ResourcesCompat.getColor(resources, R.color.status_not_hearing, theme);
mStatus = (TextView) findViewById(R.id.status);
mText = (TextView) findViewById(R.id.text);
mResult = (TextView) findViewById(R.id.resultText);
editButton = (Button)findViewById(R.id.button1);
clearButton = (Button)findViewById(R.id.button2);
settings = getSharedPreferences("MyPreference", Context.MODE_PRIVATE);
clearButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Sounds sounds = new Sounds(getApplicationContext());
if(settings.getBoolean("muteAble", false ) == true){
sounds.playSound();
}
mResult.setText("");
}
});
editButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Sounds sounds = new Sounds(getApplicationContext());
if(settings.getBoolean("muteAble", false ) == true){
sounds.playSound();
}
Intent editIntent = new Intent(MainActivity.this, EditorActivity.class);
String forEditText = mResult.getText().toString();
editIntent.putExtra("forEdit", forEditText);
startActivity(editIntent);
}
});
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home){
this.finish();
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onStart() {
super.onStart();
// Prepare Cloud Speech API
bindService(new Intent(this, SpeechService.class), mServiceConnection, BIND_AUTO_CREATE);
// Start listening to voices
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED) {
startVoiceRecorder();
} else if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.RECORD_AUDIO)) {
showPermissionMessageDialog();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_AUDIO_PERMISSION);
}
}
#Override
protected void onStop() {
// Stop listening to voice
stopVoiceRecorder();
// Stop Cloud Speech API
mSpeechService.removeListener(mSpeechServiceListener);
unbindService(mServiceConnection);
mSpeechService = null;
super.onStop();
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
if (permissions.length == 1 && grantResults.length == 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startVoiceRecorder();
} else {
showPermissionMessageDialog();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void startVoiceRecorder() {
if (mVoiceRecorder != null) {
mVoiceRecorder.stop();
}
mVoiceRecorder = new VoiceRecorder(mVoiceCallback);
mVoiceRecorder.start();
}
private void stopVoiceRecorder() {
if (mVoiceRecorder != null) {
mVoiceRecorder.stop();
mVoiceRecorder = null;
}
}
private void showPermissionMessageDialog() {
MessageDialogFragment
.newInstance(getString(R.string.permission_message))
.show(getSupportFragmentManager(), FRAGMENT_MESSAGE_DIALOG);
}
private void showStatus(final boolean hearingVoice) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mStatus.setTextColor(hearingVoice ? mColorHearing : mColorNotHearing);
}
});
}
#Override
public void onMessageDialogDismissed() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_AUDIO_PERMISSION);
}
private final SpeechService.Listener mSpeechServiceListener =
new SpeechService.Listener() {
#Override
public void onSpeechRecognized(final String text, final boolean isFinal) {
if (isFinal) {
mVoiceRecorder.dismiss();
}
if (mText != null && !TextUtils.isEmpty(text)) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (isFinal) {
mText.setText(null);
mResult.append(" "+text.toString());
} else {
mText.setText(text);
}
}
});
}
}
};
}
Thank in advance for your help
I am using RxJava2.
i have some observable, and few subscribers that can be subscribed for it.
each time when new subscribers arrive, some job should be done and each of subscribers should be notified.
for this i decide to use PublishSubject. but when doOnSubscribe received from firs subscriber, myPublishSubject.hasObservers() return false...
any idea why it happens and how can i fix this?
private val myPublishSubject = PublishSubject.create<Boolean>()
fun getPublishObservable():Observable<Boolean> {
return myPublishSubject.doOnSubscribe {
//do some job when new subscriber arrived and notify all subscribers
//via
myPublishSubject.onNext(true)
}
}
Do I understand it correct, that when doOnSubscribe called it mean that there is at least one subscribers already present?
i did not find ready answer, so i create my own version of subject and call it RefreshSubject.
it based on PublishSubject but with one difference: if you would like to return observable and be notified when new subscriber arrives and ready to receive some data you should use method getSubscriberReady.
here a small example:
private RefreshSubject<Boolean> refreshSubject = RefreshSubject.create();
//ordinary publish behavior
public Observable<Boolean> getObservable(){
return refreshSubject;
}
//refreshSubject behaviour
public Observable<Boolean> getRefreshObserver(){
return refreshSubject.getSubscriberReady(new Action() {
#Override
public void run() throws Exception {
//new subscriber arrives and ready to receive some data
//so you can make some data request and all your subscribers (with new one just arrived)
//will receive new content
}
});
}
and here is full class:
public class RefreshSubject<T> extends Subject<T> {
/** The terminated indicator for the subscribers array. */
#SuppressWarnings("rawtypes")
private static final RefreshSubject.RefreshDisposable[] TERMINATED = new RefreshSubject.RefreshDisposable[0];
/** An empty subscribers array to avoid allocating it all the time. */
#SuppressWarnings("rawtypes")
private static final RefreshSubject.RefreshDisposable[] EMPTY = new RefreshSubject.RefreshDisposable[0];
/** The array of currently subscribed subscribers. */
private final AtomicReference<RefreshDisposable<T>[]> subscribers;
/** The error, write before terminating and read after checking subscribers. */
Throwable error;
/**
* Constructs a RefreshSubject.
* #param <T> the value type
* #return the new RefreshSubject
*/
#CheckReturnValue
public static <T> RefreshSubject<T> create() {
return new RefreshSubject<T>();
}
/**
* Constructs a RefreshSubject.
* #since 2.0
*/
#SuppressWarnings("unchecked")
private RefreshSubject() {
subscribers = new AtomicReference<RefreshSubject.RefreshDisposable<T>[]>(EMPTY);
}
#Override
public void subscribeActual(Observer<? super T> t) {
RefreshSubject.RefreshDisposable<T> ps = new RefreshSubject.RefreshDisposable<T>(t, RefreshSubject.this);
t.onSubscribe(ps);
if (add(ps)) {
// if cancellation happened while a successful add, the remove() didn't work
// so we need to do it again
if (ps.isDisposed()) {
remove(ps);
}
} else {
Throwable ex = error;
if (ex != null) {
t.onError(ex);
} else {
t.onComplete();
}
}
}
public Observable<T> getSubscriberReady(final Action onReady){
return Observable.create(new ObservableOnSubscribe<T>() {
#Override
public void subscribe(ObservableEmitter<T> e) throws Exception {
add(new RefreshDisposable(e, RefreshSubject.this));
onReady.run();
}
});
}
/**
* Tries to add the given subscriber to the subscribers array atomically
* or returns false if the subject has terminated.
* #param ps the subscriber to add
* #return true if successful, false if the subject has terminated
*/
private boolean add(RefreshSubject.RefreshDisposable<T> ps) {
for (;;) {
RefreshSubject.RefreshDisposable<T>[] a = subscribers.get();
if (a == TERMINATED) {
return false;
}
int n = a.length;
#SuppressWarnings("unchecked")
RefreshSubject.RefreshDisposable<T>[] b = new RefreshSubject.RefreshDisposable[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = ps;
if (subscribers.compareAndSet(a, b)) {
return true;
}
}
}
/**
* Atomically removes the given subscriber if it is subscribed to the subject.
* #param ps the subject to remove
*/
#SuppressWarnings("unchecked")
private void remove(RefreshSubject.RefreshDisposable<T> ps) {
for (;;) {
RefreshSubject.RefreshDisposable<T>[] a = subscribers.get();
if (a == TERMINATED || a == EMPTY) {
return;
}
int n = a.length;
int j = -1;
for (int i = 0; i < n; i++) {
if (a[i] == ps) {
j = i;
break;
}
}
if (j < 0) {
return;
}
RefreshSubject.RefreshDisposable<T>[] b;
if (n == 1) {
b = EMPTY;
} else {
b = new RefreshSubject.RefreshDisposable[n - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, n - j - 1);
}
if (subscribers.compareAndSet(a, b)) {
return;
}
}
}
#Override
public void onSubscribe(Disposable s) {
if (subscribers.get() == TERMINATED) {
s.dispose();
}
}
#Override
public void onNext(T t) {
if (subscribers.get() == TERMINATED) {
return;
}
if (t == null) {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
for (RefreshSubject.RefreshDisposable<T> s : subscribers.get()) {
s.onNext(t);
}
}
#SuppressWarnings("unchecked")
#Override
public void onError(Throwable t) {
if (subscribers.get() == TERMINATED) {
RxJavaPlugins.onError(t);
return;
}
if (t == null) {
t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.");
}
error = t;
for (RefreshSubject.RefreshDisposable<T> s : subscribers.getAndSet(TERMINATED)) {
s.onError(t);
}
}
#SuppressWarnings("unchecked")
#Override
public void onComplete() {
if (subscribers.get() == TERMINATED) {
return;
}
for (RefreshSubject.RefreshDisposable<T> s : subscribers.getAndSet(TERMINATED)) {
s.onComplete();
}
}
#Override
public boolean hasObservers() {
return subscribers.get().length != 0;
}
#Override
public Throwable getThrowable() {
if (subscribers.get() == TERMINATED) {
return error;
}
return null;
}
#Override
public boolean hasThrowable() {
return subscribers.get() == TERMINATED && error != null;
}
#Override
public boolean hasComplete() {
return subscribers.get() == TERMINATED && error == null;
}
/**
* Wraps the actualEmitter subscriber, tracks its requests and makes cancellation
* to remove itself from the current subscribers array.
*
* #param <T> the value type
*/
private static final class RefreshDisposable<T> extends AtomicBoolean implements Disposable {
private static final long serialVersionUID = 3562861878281475070L;
/** The actualEmitter subscriber. */
final Emitter<? super T> actualEmitter;
/** The actualEmitter subscriber. */
final Observer<? super T> actualObserver;
/** The subject state. */
final RefreshSubject<T> parent;
/**
* Constructs a PublishSubscriber, wraps the actualEmitter subscriber and the state.
* #param actualEmitter the actualEmitter subscriber
* #param parent the parent RefreshProcessor
*/
RefreshDisposable(Emitter<? super T> actualEmitter, RefreshSubject<T> parent) {
this.actualEmitter = actualEmitter;
this.parent = parent;
actualObserver = null;
}
/**
* Constructs a PublishSubscriber, wraps the actualEmitter subscriber and the state.
* #param actualObserver the actualObserver subscriber
* #param parent the parent RefreshProcessor
*/
RefreshDisposable(Observer<? super T> actualObserver, RefreshSubject<T> parent) {
this.actualObserver = actualObserver;
this.parent = parent;
actualEmitter = null;
}
public void onNext(T t) {
if (!get()) {
if (actualEmitter != null)
actualEmitter.onNext(t);
if (actualObserver != null)
actualObserver.onNext(t);
}
}
public void onError(Throwable t) {
if (get()) {
RxJavaPlugins.onError(t);
} else {
if (actualEmitter != null)
actualEmitter.onError(t);
if (actualObserver != null)
actualObserver.onError(t);
}
}
public void onComplete() {
if (!get()) {
if (actualEmitter != null)
actualEmitter.onComplete();
if (actualObserver != null)
actualObserver.onComplete();
}
}
#Override
public void dispose() {
if (compareAndSet(false, true)) {
parent.remove(this);
}
}
#Override
public boolean isDisposed() {
return get();
}
}
}
I'm trying to have user registration for my android application. I'm able to successfully make user register and store their details in Cloudant. They can also login using the phone they had used to register.
However, when I try using another phone to login the account, it doesn't work. Is possible to replicate all data from Cloudant so that users can also login to other phones too? Here is my code:
public class CloudantConnect {
private static final String TAG = CloudantConnect.class.getSimpleName();
private static final String DATASTORE_DIRECTORY = "data";
private Datastore datastore;
private IndexManager indexManager;
private Replicator push_replicator;
private Replicator pull_replicator;
private Context context;
private final Handler handler;
private RegisterActivity register_listener;
public CloudantConnect(Context context, String datastore_name) {
this.context = context;
// Set up information within its own folder in the application
File path = this.context.getApplicationContext().getDir(DATASTORE_DIRECTORY, Context.MODE_PRIVATE);
DatastoreManager manager = new DatastoreManager(path.getAbsolutePath());
try {
this.datastore = manager.openDatastore(datastore_name);
} catch (DatastoreNotCreatedException e) {
Log.e(TAG, "Unable to open Datastore", e);
}
// Reach here if datastore successfully created
Log.d(TAG, "Successfully set up database at" + path.getAbsolutePath());
// Set up replicator objects
try {
this.reloadReplicationSettings();
} catch (URISyntaxException e) {
Log.e(TAG, "Unable to construct remote URI from configuration", e);
}
this.handler = new Handler(Looper.getMainLooper());
Log.d(TAG, "CloudantConnect set up " + path.getAbsolutePath());
}
/**
* Creates new document for user details database storage
* #param user to store user details into database
* #return document of user details stored
*/
public User createNewUserDocument(User user) {
MutableDocumentRevision revision = new MutableDocumentRevision();
revision.body = DocumentBodyFactory.create(user.asMap());
try {
BasicDocumentRevision created = this.datastore.createDocumentFromRevision(revision);
return User.fromRevision(created);
} catch (DocumentException e) {
return null;
}
}
/**
* Sets replication listener
*/
public void setReplicationListener(RegisterActivity listener) {
this.register_listener = listener;
}
/**
* Start push replication
*/
public void startPushReplication() {
if(this.push_replicator != null) {
this.push_replicator.start();
} else {
throw new RuntimeException("Push replication not set up correctly");
}
}
/**
* Start pull replication
*/
public void startPullReplication() {
if(this.pull_replicator != null) {
this.pull_replicator.start();
} else {
throw new RuntimeException("Pull replication not set up correctly");
}
}
/**
* Stop running replication
*/
public void stopAllReplication() {
if(this.push_replicator != null) {
this.push_replicator.stop();
}
if(this.pull_replicator != null) {
this.pull_replicator.stop();
}
}
/**
* Stop running replication and reloads replication settings
* from the app's preferences.
*/
public void reloadReplicationSettings() throws URISyntaxException {
this.stopAllReplication();
// Set up new replicator objects
URI uri = this.createServerURI();
// Push replication
PushReplication push = new PushReplication();
push.source = datastore;
push.target = uri;
push_replicator = ReplicatorFactory.oneway(push);
push_replicator.getEventBus().register(this);
// Pull replication
PullReplication pull = new PullReplication();
pull.source = uri;
pull.target = datastore;
pull_replicator = ReplicatorFactory.oneway(pull);
pull_replicator.getEventBus().register(this);
Log.d(TAG, "Set up replicators for URI:" + uri.toString());
}
/**
* Calls when replication is completed
*/
public void complete(ReplicationCompleted rc) {
handler.post(new Runnable() {
#Override
public void run() {
if(register_listener != null) {
register_listener.replicationComplete();
}
}
});
}
/**
* Calls when replication has error
*/
public void error(ReplicationErrored re) {
Log.e(TAG, "Replication error:", re.errorInfo.getException());
handler.post(new Runnable() {
#Override
public void run() {
if(register_listener != null) {
register_listener.replicationError();
}
}
});
}
}
It looks like you've got all the code there to do replication. Do you actually call startPullReplication() from somewhere?
If you want your complete and error callbacks to run when replication completes/fails, you will need to add the #Subscribe annotation on them both so they're triggered when the events are put on the EventBus.
When the user presses the buy button, they get taken to the android play screen to buy the in-app item. But when they press back, they get this exception about not being able to start the billing service:
java.lang.RuntimeException: Unable to start service com.problemio.BillingService#405704f0 with Intent { act=com.android.vending.billing.RESPONSE_CODE cmp=com.problemio/.BillingService (has extras) }: java.lang.NullPointerException
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2387)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
... 10 more
java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
It specifically mentions these lines in my class:
Caused by:
java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
on line 561 I have this code:
private void prependLogEntry(CharSequence cs) {
SpannableStringBuilder contents = new SpannableStringBuilder(cs);
contents.append('\n');
contents.append(mLogTextView.getText()); // Line 561
mLogTextView.setText(contents);
}
on line 569 I have this code:
private void logProductActivity(String product, String activity) {
SpannableStringBuilder contents = new SpannableStringBuilder();
contents.append(Html.fromHtml("<b>" + product + "</b>: "));
contents.append(activity);
prependLogEntry(contents); // Line 569
}
By the way, which log is this? Is it really needed? Maybe its worthwhile to comment out these methods?
In any case, the other line the error points to is 187 and here is the code for that:
#Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Consts.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
logProductActivity(request.mProductId, "sending purchase request");
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Consts.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
logProductActivity(request.mProductId, "dismissed purchase dialog");
// This is line 187 right above this comment.
} else {
if (Consts.DEBUG) {
Log.i(TAG, "purchase failed");
}
logProductActivity(request.mProductId, "request purchase returned " + responseCode);
}
}
So you see, it keeps having a problem on this logging thing. What is this log? Where can I see it? How can I fix it so it doesn't make errors?
I am not certain, but maybe the bigger issue is that request is null. Why would the request possibly be null here?
Here is the entire ExtraHelpActivity class:
public class ExtraHelpActivity extends BaseActivity implements ServiceConnection
{
// , OnClickListener
private static final String TAG = "Pay";
String issueProductIdWebMarketing = "1";
String issueProductIdDonate = "2";
String issueProductIdPsych = "3";
/**
* The developer payload that is sent with subsequent
* purchase requests.
*/
private String payloadContents = null;
/**
* Used for storing the log text.
*/
private static final String LOG_TEXT_KEY = "DUNGEONS_LOG_TEXT";
/**
* The SharedPreferences key for recording whether we initialized the
* database. If false, then we perform a RestoreTransactions request
* to get all the purchases for this user.
*/
private static final String DB_INITIALIZED = "db_initialized";
private ExtraHelpPurchaseObserver mExtraHelpPurchaseObserver;
private Handler mHandler;
private Handler handler;
private BillingService mBillingService;
private Button mBuyButton;
private Button mEditPayloadButton;
private Button mEditSubscriptionsButton;
private TextView mLogTextView;
private Spinner mSelectItemSpinner;
private ListView mOwnedItemsTable;
private SimpleCursorAdapter mOwnedItemsAdapter;
private PurchaseDatabase mPurchaseDatabase;
private Cursor mOwnedItemsCursor;
private Set<String> mOwnedItems = new HashSet<String>();
/**
* The developer payload that is sent with subsequent
* purchase requests.
*/
private String mPayloadContents = null;
private static final int DIALOG_CANNOT_CONNECT_ID = 1;
private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3;
/**
* Each product in the catalog can be MANAGED, UNMANAGED, or SUBSCRIPTION. MANAGED
* means that the product can be purchased only once per user (such as a new
* level in a game). The purchase is remembered by Android Market and
* can be restored if this application is uninstalled and then
* re-installed. UNMANAGED is used for products that can be used up and
* purchased multiple times (such as poker chips). It is up to the
* application to keep track of UNMANAGED products for the user.
* SUBSCRIPTION is just like MANAGED except that the user gets charged monthly
* or yearly.
*/
private enum Managed { MANAGED, UNMANAGED, SUBSCRIPTION }
/**
* A {#link PurchaseObserver} is used to get callbacks when Android Market sends
* messages to this application so that we can update the UI.
*/
private class ExtraHelpPurchaseObserver extends PurchaseObserver {
public ExtraHelpPurchaseObserver(Handler handler) {
super(ExtraHelpActivity.this, handler);
}
#Override
public void onBillingSupported(boolean supported, String type) {
if (Consts.DEBUG) {
Log.i(TAG, "supported: " + supported);
}
if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {
if (supported) {
restoreDatabase();
mBuyButton.setEnabled(true);
mEditPayloadButton.setEnabled(true);
} else {
showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
}
} else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
mCatalogAdapter.setSubscriptionsSupported(supported);
} else {
showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
}
}
#Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
if (Consts.DEBUG) {
Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
}
if (developerPayload == null) {
logProductActivity(itemId, purchaseState.toString());
} else {
logProductActivity(itemId, purchaseState + "\n\t" + developerPayload);
}
if (purchaseState == PurchaseState.PURCHASED) {
mOwnedItems.add(itemId);
// If this is a subscription, then enable the "Edit
// Subscriptions" button.
for (CatalogEntry e : CATALOG) {
if (e.sku.equals(itemId) &&
e.managed.equals(Managed.SUBSCRIPTION)) {
mEditSubscriptionsButton.setVisibility(View.VISIBLE);
}
}
}
mCatalogAdapter.setOwnedItems(mOwnedItems);
mOwnedItemsCursor.requery();
}
#Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Consts.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
logProductActivity(request.mProductId, "sending purchase request");
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Consts.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
logProductActivity(request.mProductId, "dismissed purchase dialog");
} else {
if (Consts.DEBUG) {
Log.i(TAG, "purchase failed");
}
logProductActivity(request.mProductId, "request purchase returned " + responseCode);
}
}
#Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.d(TAG, "completed RestoreTransactions request");
}
// Update the shared preferences so that we don't perform
// a RestoreTransactions again.
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(DB_INITIALIZED, true);
edit.commit();
} else {
if (Consts.DEBUG) {
Log.d(TAG, "RestoreTransactions error: " + responseCode);
}
}
}
}
private static class CatalogEntry {
public String sku;
public int nameId;
public Managed managed;
public CatalogEntry(String sku, int nameId, Managed managed) {
this.sku = sku;
this.nameId = nameId;
this.managed = managed;
}
}
/** An array of product list entries for the products that can be purchased. */
private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
new CatalogEntry("marketing_001", 1 , Managed.MANAGED),
new CatalogEntry("potion_001", 2 , Managed.UNMANAGED),
new CatalogEntry("subscription_monthly", 3,
Managed.SUBSCRIPTION),
new CatalogEntry("subscription_yearly", 4 ,
Managed.SUBSCRIPTION)
};
private String mItemName;
private String mSku;
private Managed mManagedType;
private CatalogAdapter mCatalogAdapter;
//outside onCreate() Within class
// public Handler mTransactionHandler = new Handler(){
// public void handleMessage(android.os.Message msg) {
// Log.i(TAG, "Transaction complete");
// Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
// Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
//
// if(BillingHelper.latestPurchase.isPurchased()){
// //code here which is to be performed after successful purchase
// }
// };
//
// };
// TODO:
// TODO:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.extra_help);
//Now setup the in-app billing</pre>
handler = new Handler();
mExtraHelpPurchaseObserver = new ExtraHelpPurchaseObserver(handler);
mBillingService = new BillingService();
mBillingService.setContext(this);
mPurchaseDatabase = new PurchaseDatabase(this);
ResponseHandler.register(mExtraHelpPurchaseObserver);
//Check if billing is supported. (Optional)
//boolean check = mBillingService.checkBillingSupported();
// startService(new Intent(mContext, BillingService.class));
// BillingHelper.setCompletedHandler(mTransactionHandler);
// Make sure the user is logged in
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( ExtraHelpActivity.this);
final String user_id = prefs.getString( "user_id" , null );
Button donate = (Button)findViewById(R.id.donate);
donate.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
// Send me an email that a comment was submitted on a question.
// boolean val = mBillingService.requestPurchase(
// "android.test.purchased", payloadContents);
//Replace "android.test.purchased" with your product ID that you added to Google Play or make it a variable that is populated from a list.
//Place this code in a button event or where ever it fits in your process flow.
if (mBillingService.requestPurchase(issueProductIdDonate, Consts.ITEM_TYPE_INAPP , null))
{
}
else
{
Log.i("tag", "Can't purchase on this device");
}
}
});
try
{
boolean bindResult = bindService(
new Intent("com.android.vending.billing.MarketBillingService.BIND"),
this,
Context.BIND_AUTO_CREATE);
if (bindResult)
{
Log.i( "Err" , "Service bind successful.");
}
else
{
Log.e( "Err", "Could not bind to the MarketBillingService.");
}
}
catch (SecurityException e)
{
Log.e( "Err" , "Security exception: " + e);
}
}
/**
* Save the context of the log so simple things like rotation will not
* result in the log being cleared.
*/
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
//outState.putString(LOG_TEXT_KEY, Html.toHtml((Spanned) mLogTextView.getText()));
}
/**
* Restore the contents of the log if it has previously been saved.
*/
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null)
{
//mLogTextView.setText(Html.fromHtml(savedInstanceState.getString(LOG_TEXT_KEY)));
}
}
#Override
protected Dialog onCreateDialog(int id)
{
switch (id)
{
case DIALOG_CANNOT_CONNECT_ID:
return createDialog(1,1);
case DIALOG_BILLING_NOT_SUPPORTED_ID:
return createDialog(2,2);
case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
return createDialog(3,3);
// case DIALOG_CANNOT_CONNECT_ID:
// return createDialog(R.string.cannot_connect_title,
// R.string.cannot_connect_message);
// case DIALOG_BILLING_NOT_SUPPORTED_ID:
// return createDialog(R.string.billing_not_supported_title,
// R.string.billing_not_supported_message);
// case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
// return createDialog(R.string.subscriptions_not_supported_title,
// R.string.subscriptions_not_supported_message);
default:
return null;
}
}
private Dialog createDialog(int titleId, int messageId) {
String helpUrl = replaceLanguageAndRegion("help_url");
if (Consts.DEBUG) {
Log.i(TAG, helpUrl);
}
final Uri helpUri = Uri.parse(helpUrl);
// TODO: replace 1 with the thing its supposed to be - I think learn more url :)
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(titleId)
.setIcon(android.R.drawable.stat_sys_warning)
.setMessage(messageId)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(1, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW, helpUri);
startActivity(intent);
}
});
return builder.create();
}
/**
* Replaces the language and/or country of the device into the given string.
* The pattern "%lang%" will be replaced by the device's language code and
* the pattern "%region%" will be replaced with the device's country code.
*
* #param str the string to replace the language/country within
* #return a string containing the local language and region codes
*/
private String replaceLanguageAndRegion(String str) {
// Substitute language and or region if present in string
if (str.contains("%lang%") || str.contains("%region%")) {
Locale locale = Locale.getDefault();
str = str.replace("%lang%", locale.getLanguage().toLowerCase());
str = str.replace("%region%", locale.getCountry().toLowerCase());
}
return str;
}
/**
* Sets up the UI.
*/
private void setupWidgets()
{
mOwnedItemsCursor = mPurchaseDatabase.queryAllPurchasedItems();
startManagingCursor(mOwnedItemsCursor);
String[] from = new String[] { PurchaseDatabase.PURCHASED_PRODUCT_ID_COL,
PurchaseDatabase.PURCHASED_QUANTITY_COL
};
// int[] to = new int[] { R.id.item_name, R.id.item_quantity };
// mOwnedItemsAdapter = new SimpleCursorAdapter(this, R.layout.item_row,
// mOwnedItemsCursor, from, to);
// mOwnedItemsTable = (ListView) findViewById(R.id.owned_items);
// mOwnedItemsTable.setAdapter(mOwnedItemsAdapter);
}
private void prependLogEntry(CharSequence cs) {
SpannableStringBuilder contents = new SpannableStringBuilder(cs);
contents.append('\n');
contents.append(mLogTextView.getText());
mLogTextView.setText(contents);
}
private void logProductActivity(String product, String activity) {
SpannableStringBuilder contents = new SpannableStringBuilder();
contents.append(Html.fromHtml("<b>" + product + "</b>: "));
contents.append(activity);
prependLogEntry(contents);
}
/**
* If the database has not been initialized, we send a
* RESTORE_TRANSACTIONS request to Android Market to get the list of purchased items
* for this user. This happens if the application has just been installed
* or the user wiped data. We do not want to do this on every startup, rather, we want to do
* only when the database needs to be initialized.
*/
private void restoreDatabase() {
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
if (!initialized) {
mBillingService.restoreTransactions();
Toast.makeText(this, 3, Toast.LENGTH_LONG).show();
// Used to be R.string.restoring_transactions instead of 3
}
}
/**
* Creates a background thread that reads the database and initializes the
* set of owned items.
*/
private void initializeOwnedItems() {
new Thread(new Runnable() {
public void run() {
doInitializeOwnedItems();
}
}).start();
}
/**
* Reads the set of purchased items from the database in a background thread
* and then adds those items to the set of owned items in the main UI
* thread.
*/
private void doInitializeOwnedItems() {
Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
if (cursor == null) {
return;
}
final Set<String> ownedItems = new HashSet<String>();
try {
int productIdCol = cursor.getColumnIndexOrThrow(
PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
while (cursor.moveToNext()) {
String productId = cursor.getString(productIdCol);
ownedItems.add(productId);
}
} finally {
cursor.close();
}
// We will add the set of owned items in a new Runnable that runs on
// the UI thread so that we don't need to synchronize access to
// mOwnedItems.
mHandler.post(new Runnable() {
public void run() {
mOwnedItems.addAll(ownedItems);
mCatalogAdapter.setOwnedItems(mOwnedItems);
}
});
}
/**
* Called when a button is pressed.
*/
public void onClick(View v) {
if (v == mBuyButton) {
if (Consts.DEBUG) {
Log.d(TAG, "buying: " + mItemName + " sku: " + mSku);
}
if (mManagedType != Managed.SUBSCRIPTION &&
!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
} else if (!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents)) {
// Note: mManagedType == Managed.SUBSCRIPTION
showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
}
} else if (v == mEditPayloadButton) {
showPayloadEditDialog();
} else if (v == mEditSubscriptionsButton) {
editSubscriptions();
}
}
/** List subscriptions for this package in Google Play
*
* This allows users to unsubscribe from this apps subscriptions.
*
* Subscriptions are listed on the Google Play app detail page, so this
* should only be called if subscriptions are known to be present.
*/
private void editSubscriptions() {
// Get current package name
String packageName = getPackageName();
// Open app detail in Google Play
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageName));
startActivity(i);
}
/**
* Displays the dialog used to edit the payload dialog.
*/
private void showPayloadEditDialog()
{
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
final View view = View.inflate(this, R.layout.edit_payload, null);
final TextView payloadText = (TextView) view.findViewById(R.id.payload_text);
if (mPayloadContents != null) {
payloadText.setText(mPayloadContents);
}
dialog.setView(view);
dialog.setPositiveButton(
R.string.edit_payload_accept,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mPayloadContents = payloadText.getText().toString();
}
});
dialog.setNegativeButton(
R.string.edit_payload_clear,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (dialog != null) {
mPayloadContents = null;
dialog.cancel();
}
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener(
oops.
mLogTextView is never initialised.
add mLogTextView = findViewById(R.id.blaaa);
I am sure you already know that
please tell me if i am wrong so that I delete this answer (:
Are you give permission in menifest file?
<uses-permission android:name="com.android.vending.BILLING" />
I am adding an answer because I reached the character limit in my original question since my class is so long.
This is the extra_help.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<include android:id="#+id/header"
layout="#layout/header"
android:layout_height="wrap_content"
android:layout_width="fill_parent"/>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5px">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5px"
>
<TextView
android:id="#+id/heading_1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#color/light_best_blue"
android:text="THE APP IS FREE TO HELP AS MANY PEOPLE AS POSSIBLE, PLEASE GIVE BACK"
/>
<Button
android:id="#+id/donate"
android:layout_marginTop ="10dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Donate $1.99 Since the App is Free"
/>
</LinearLayout>
</ScrollView>
</LinearLayout>