I am receiving a range of signals from onReceive using BroadcastReceiver in my iBeaconProject. What I would like to do is to only keep track of one of the beacons (which I specify) and it's distance from my phone to the beacon. Any ideas, guys? Please help me! I'm using http://www.radiusnetworks.com. I am getting a range of signals using the following onReceive function. How do I go about doing it? Thanks all in advance!
BroadcastReceiver bReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int countBea = 0;
if (intent.getAction().equals(intentname) && intent.getExtras() != null && intent.getExtras().containsKey(intentname)) {
Collection<IBeacon> beaconsCol = (Collection<IBeacon>)intent.getExtras().getSerializable(intentname);
for (IBeacon bea : beaconsCol) {
Log.d("beac receive!","receive! "+bea.getProximityUuid()+" "+bea.getMajor()+" "+bea.getMinor()+" "+bea.getAccuracy()+" "+bea.getProximity()+" "+bea.getRssi()+" "+bea.getTxPower());
countBea++;
if(((mainActivity)getActivity()).UUIDValue.equalsIgnoreCase(bea.getProximityUuid())
&& ((mainActivity)getActivity()).MajorValue == bea.getMajor()
&& ((mainActivity)getActivity()).MinorValue == bea.getMinor()) {
update(bea.getProximityUuid(), +bea.getMajor(), bea.getMinor(), bea.getAccuracy());
} else if (((mainActivity)getActivity()).UUIDValue.equalsIgnoreCase(bea.getProximityUuid())
&& (((mainActivity)getActivity()).MajorValue == 0 ||
((mainActivity)getActivity()).MinorValue == 0)) {
updateNILMajorMinor();
} else {
updateMultipleBeaconsDetected();
}
}
System.out.println("COUNTBEAC " + countBea);
}
}
};
Good to see the for-each loop.
Inside it, you can identify the beacon that you want to keep track of,
for (IBeacon bea : beaconsCol) {
//in the following if, identify the specified beacon
// this will remain the same for every refresh
if(bea.getProximityUuid().equals("match it here") && bea.getMajor()==major
&& bea.getMinor()==minor){
//now display that beacon's proximity and accuracy
//the same code will update a textview or notification every time
// here you will have 1 beacon at a time, can add that to a global list
}
}
Can you give a precise idea for the implementation?
does your code enter onReceive periodically?
I have never seen anything mention using the Radius Networks SDK by listening for broadcasts. Instead they ask that you implement certain interfaces and register them with an IBeaconManager.
You may find their code samples useful. That page contains the following snippet, which you may recognize as equivalent to the code in your question.
public class RangingActivity extends Activity implements IBeaconConsumer, RangeNotifier {
private static final String TAG = RangingActivity.class.getName();
private IBeaconManager iBeaconManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iBeaconManager = IBeaconManager.getInstanceForApplication(this);
iBeaconManager.bind(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
iBeaconManager.unBind(this);
}
#Override
public void onIBeaconServiceConnect() {
iBeaconManager.setRangeNotifier(this);
try {
// edit this to match the UUID of your beacon
// or leave null to detect everything
String uuid = null;
Region region = new Region("myRangingUniqueId", uuid, null, null);
iBeaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
Log.e(TAG, "problem while starting ranging", e);
}
}
#Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons, Region region) {
if (!iBeacons.isEmpty()) {
double accuracy = iBeacons.iterator().next().getAccuracy();
Log.i(TAG, "The first iBeacon I see is about " + accuracy + " meters away.");
}
}
}
Related
I am using AltBeacon Android Library (I reproduced issue with v2.9.2; and also with v2.11) for integrating with iBeacon devices provided by Onyx and kontact.io.
The library seems to work very well, but I seem to have an issue with it for which I could not find an acceptable solution.
Here are some more details about how I use AltBeacon Library and about the issue:
Device is stationary near the beacon
Bluetooth on
Application runs in foreground
The BeaconManager is configured to scan in foreground mode with the following settings:
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
Application sets the BeaconManager in foreground mode
beaconManager.setBackgroundMode(false);
Application bounds to the BeaconManager
beaconManager.bind(…)
When onBeaconServiceConnect() is triggered, the application starts monitoring beacons in specific regions (the list of beacons I want to monitor is known, static; I use a list of regions, one different region for each beacon I want to monitor)
beaconManager.startMonitoringBeaconsInRegion(region);
When device enters beacon region (didEnterRegion() is called) application starts ranging for entered region
beaconManager.startRangingBeaconsInRegion(region);
Beacon is detected (didRangeBeaconsInRegion() is called for corresponding beacon)
Application switched beacon scanning to background mode:
beaconManager.setBackgroundMode(true);
After a few minutes, the didExitRegion() is called even if the device and the beacon were not moved and the application remained in the same state.
I have found two Stackoverflow issues which describe the same issue:
AltBeacon unstable for OnyxBeacons, cycling through didEnterRegion and didExitRegion repeatedly
http://stackoverflow.com/questions/40835671/altbeacon-reference-app-and-multiple-exit-entry-calls
The workaround that I currently use is the one suggested in the Stackoverflow issues:
I have updated beacon Advertising Frequency value from 1000 ms to 100 ms.
Once the frequency is increased, everything seems to work fine, but
the solution is not acceptable because the battery life of the beacon is
drastically impaired.
All the beacon scanning is performed in background (i.e. no Activity is used):
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Identifier;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.powersave.BackgroundPowerSaver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BeaconDataProvider implements BeaconConsumer, RangeNotifier, MonitorNotifier {
private final Logger LOGGER = LogFactory.get(this);
private final Context applicationContext;
private final BeaconIdentifierFactory beaconIdentifierFactory;
private final BeaconScanningListener beaconScanningListener;
private BeaconManager beaconManager;
private Collection<Region> targetedRegions;
/**
* This field is used for improving battery consumption. Do not remove it.
*/
#SuppressWarnings({"unused", "FieldCanBeLocal"})
private BackgroundPowerSaver backgroundPowerSaver;
public BeaconDataProvider(Context applicationContext, BeaconIdentifierFactory beaconIdentifierFactory,
BeaconScanningListener beaconScanningListener) {
LOGGER.v("BeaconDataProvider - new instance created.");
this.applicationContext = applicationContext;
this.beaconIdentifierFactory = beaconIdentifierFactory;
this.beaconScanningListener = beaconScanningListener;
beaconManager = BeaconManager.getInstanceForApplication(applicationContext);
LOGGER.v("BeaconManager hashCode=%s", beaconManager.hashCode());
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
backgroundPowerSaver = new BackgroundPowerSaver(applicationContext);
}
public void setBackgroundMode() {
LOGGER.i("setBackgroundMode()");
beaconManager.setBackgroundMode(true);
}
public void setForegroundMode() {
LOGGER.i("setForegroundMode()");
beaconManager.setBackgroundMode(false);
}
public boolean checkAvailability() {
return android.os.Build.VERSION.SDK_INT >= 18 && applicationContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public boolean isBluetoothEnabled() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
boolean result = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
LOGGER.i("isBluetoothEnabled() -> %s", result);
return result;
}
public boolean isLocationPermissionGranted(Context context) {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED);
}
public void startScanning(Collection<BeaconIdentifier> targetedBeacons) {
LOGGER.i("startScanning()");
if (!beaconManager.isBound(this)) {
this.targetedRegions = getRegionsForTargetedBeacons(targetedBeacons);
beaconManager.bind(this);
}
else {
LOGGER.i("Scanning already started.");
}
}
#NonNull
private List<Region> getRegionsForTargetedBeacons(Collection<BeaconIdentifier> beaconIdentifiers) {
List<Region> regions = new ArrayList<>();
for (BeaconIdentifier beaconIdentifier : beaconIdentifiers) {
try {
Region region = new Region(beaconIdentifier.getRegionId(), Identifier.parse(beaconIdentifier.getUuid()),
Identifier.parse(String.valueOf(beaconIdentifier.getMajor())),
Identifier.parse(String.valueOf(beaconIdentifier.getMinor())));
regions.add(region);
}
catch (Exception e) {
LOGGER.e("Caught exception.", e);
LOGGER.w("Failed to create region for beaconIdentifier=%s", beaconIdentifier.getCallParamRepresentation());
}
}
return regions;
}
public void stopScanning() {
LOGGER.i("stopScanning()");
if (beaconManager.isBound(this)) {
for (Region region : targetedRegions) {
try {
beaconManager.stopMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
beaconManager.unbind(this);
}
}
#Override
public void didEnterRegion(Region region) {
LOGGER.v("didEnterRegion(region=%s)", region);
beaconScanningListener.onEnterRegion(region.getUniqueId());
try {
beaconManager.startRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught Exception", e);
}
}
#Override
public void didExitRegion(Region region) {
LOGGER.v("didExitRegion(region=%s)", region);
beaconScanningListener.onExitRegion(region.getUniqueId());
try {
beaconManager.stopRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Error", e);
}
}
#Override
public void didDetermineStateForRegion(int state, Region region) {
LOGGER.v("didDetermineStateForRegion(state=%s, region=%s)", state, region);
}
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
LOGGER.v("didRangeBeaconsInRegion(size=%s, region=%s, regionUniqueId=%s)", beacons.size(), region,
region.getUniqueId());
if (beacons.size() > 0) {
beaconScanningListener.onBeaconsInRange(beaconIdentifierFactory.from(beacons, region.getUniqueId()));
}
}
#Override
public void onBeaconServiceConnect() {
LOGGER.v("onBeaconServiceConnect()");
beaconManager.addRangeNotifier(this);
beaconManager.addMonitorNotifier(this);
for (Region region : targetedRegions) {
try {
beaconManager.startMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
}
#Override
public Context getApplicationContext() {
return applicationContext;
}
#Override
public void unbindService(ServiceConnection serviceConnection) {
LOGGER.v("unbindService()");
applicationContext.unbindService(serviceConnection);
}
#Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
LOGGER.v("bindService()");
return applicationContext.bindService(intent, serviceConnection, i);
}
}
public class BeaconIdentifier {
private final String uuid;
private final int major;
private final int minor;
private String regionId;
public BeaconIdentifier(String uuid, int major, int minor) {
this.uuid = uuid;
this.major = major;
this.minor = minor;
}
public int getMinor() {
return minor;
}
public int getMajor() {
return major;
}
public String getUuid() {
return uuid;
}
public String getCallParamRepresentation() {
return (uuid + "_" + major + "_" + minor).toUpperCase();
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
#Override
public boolean equals(Object o) {
if (o != null) {
if (o instanceof BeaconIdentifier) {
BeaconIdentifier other = (BeaconIdentifier) o;
return this == other || (this.uuid.equalsIgnoreCase(other.uuid)
&& this.major == other.major && this.minor == other.minor);
}
else {
return false;
}
}
else {
return false;
}
}
#Override
public int hashCode() {
int result = 17;
result = 31 * result + (uuid != null ? uuid.toUpperCase().hashCode() : 0);
result = 31 * result + major;
result = 31 * result + minor;
return result;
}
#Override
public String toString() {
return "BeaconIdentifier{" +
"uuid='" + uuid + '\'' +
", major=" + major +
", minor=" + minor +
", regionId='" + regionId + '\'' +
'}';
}
}
The BeaconDataProvider is used as a single instance per application; It is instantiated by Dagger 2 when the Android Application is created. It has #ApplicationScope lifecycle.
The beacon scanning is first started`in foreground mode from an Android IntentService:
beaconDataProvider.setForegroundMode();
beaconDataProvider.startScanning(targetedBeacons);
Once the device enters the region and the beacon is detected, beacon scanning is switched to background mode:
beaconDataProvider.setBackgroundMode();
At first I thought there was something wrong with the Onyx Beacons I was using, but I could reproduce the same issue with the Kontact IO Beacons.
Do you have any suggestions?
Am I miss-using the AltBeacon Android Library?
Thanks,
Alin
The fundamental cause of a call to didExitRegion() is the fact that no BLE beacon advertisement packets matching the region were received by the Android bluetooth stack in the previous 10 seconds. (Note: This value is configurable with BeaconManager.setRegionExitPeriod(...).)
There are several things that could be causing these spurious didExitRegion() calls:
A beacon is not advertising frequently enough.
A beacon is advertising with a very low radio signal.
There is too much radio noise in the vicinity for reliable detections.
The receiving device has a poor bluetooth antenna design causing weaker signals to not get detected.
The receiving device is too far away to reliably detect the beacon.
The foregroundScanPeriod or backgroundScanPeriod is set too short to get a guaranteed detection
Given the setup you've described, I suspect that when you have the beacon transmitting at 1Hz, some combination of 1-4 is causing the problem. You will have to experiment with each of these variables to see if you can isolate the problem to one predominant issue. But again, more than one may be at play at the same time.
Understand that even under good conditions only 80-90 percent of beacons packets transmitted over the air are received by a typical Android device. Because of this, if you have a setup where only 1-5 beacon packets are typically received in a 10 second period, you'll still sometimes get exit events if you get unlucky and a few packets in a row get corrupted by radio noise. There is no way to guarantee this won't happen. You can just make it statistically more unlikely by setting up your system so under nominal conditions it receives as many packets as possible in a 10 second period, so this becomes more unlikely.
Increasing the advertising rate is the easiest way to fix this, because it gives you more statistical chances of getting packets detected in any 10 second period. But as you have seen, there is a tradeoff in terms of battery life.
If you want do preserve battery life but don't care about the time it takes to get a didExitRegion callback, then you may want to modify BeaconManager.setRegionExitPeriod(...) to 30,000 milliseconds or more until the problem goes away.
The above discussion is specific to the configuration of the Android Beacon Library, the same theoretical ideas apply to any beacon detection framework including iOS Core Location. You sometimes see spurious exit events with that framework as well.
I think the problem is here:
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
You should generally set the scanPeriod to 5100 ms or more, because beacons that advertise have a slight chance of being missed if their transmission is always on the boundary of when you start and stop scanning.
So try:
beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
Hope it helps. Let me know if works.
As a workaround to this issue, I have implemented some extra logic to consider a didExitRegion() event only if the corresponding didEnterRegion() is not called in a certain time interval (5 minutes in my case, but this can be adjusted).
Can anybody please tell me? I am making a sample and want to detect miss call on a particular number. Suppose I opened the dialler with the number (0123456789) and when call on this number then detect missed call on this number. how can I do that. Please help ..
Check the flowing code ->
In your broadcast receiver check that if the call is received or not. Then you can find the call status.
public class CallBroadcast extends BroadcastReceiver {
private static boolean isMissedCall;
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
try {
if (bundle != null) {
String state = bundle.getString(TelephonyManager.EXTRA_STATE);
if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
// Ringing
isMissedCall = true;
} else if (state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
// Call Received
isMissedCall = false;
} else if (state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
// Call Drop
// If don't receive call then it will be missed call
if(isMissedCall){
// do your code for missed call
}
}
}
}catch (Exception e){e.printStackTrace();}
}
}
I am working on demo application to get current activity sample using Google Fit. I can get Speed as well as Distance correctly. But it is not returning "in_vehicle" or "biking" state very frequently though I was in the same state. Find attached screenshot for the same. I got speed 59.40KM/H(36.91 M/h) and at that time it not returned "in_vehicle" activity state.
Please provide solution/feedback for the same.
Code :
#Override
public void onDataPoint(DataPoint dataPoint) {
for (Field field : dataPoint.getDataType().getFields()) {
Value val = dataPoint.getValue(field);
if(field.getName().trim().toLowerCase().equals("activity"))
{
if(FitnessActivities.getName(Integer.parseInt(val.toString())).equals("biking"))
{
strState = "Cycling";
}
else if(FitnessActivities.getName(Integer.parseInt(val.toString())).equals("in_vehicle"))
{
strState = "Automotive";
}
else if(FitnessActivities.getName(Integer.parseInt(val.toString())).equals("walking"))
{
strState = "Walking";
}
else
{
strState = "Not Moving";
}
}
}
}
Thanks.
You can find the sample project I created here.
https://github.com/cyfung/ActivityRecognitionSample
Important note: you may NOT get the data as frequent as you requested!
Beginning in API 21, activities may be received less frequently than
the detectionIntervalMillis parameter if the device is in power save
mode and the screen is off.
Key components:
Create the GoogleApiClient in onCreate
mGoogleApiClient =
new GoogleApiClient.Builder(this).addApi(ActivityRecognition.API)
.addConnectionCallbacks(this).addOnConnectionFailedListener(this).build();
Connect and disconnect the api client in onStart and onStop as suggested in Google Api documentation.
#Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
mStatusView.setText("connecting");
}
#Override
protected void onStop() {
super.onStop();
mGoogleApiClient.disconnect();
mStatusView.setText("disconnected");
}
Start activity recognition (should not be called before Google Api connect). Use PendingIntent.getService to create pending intent as callback.
final PendingResult<Status>
statusPendingResult =
ActivityRecognition.ActivityRecognitionApi
.requestActivityUpdates(mGoogleApiClient, DETECT_INTERVAL, PendingIntent
.getService(this, 0, new Intent(this, ActivityDetectionService.class),
PendingIntent.FLAG_UPDATE_CURRENT));
statusPendingResult.setResultCallback(this);
IntentService is the standard method suggested to for callback
public class ActivityDetectionService extends IntentService {
protected static final String TAG = "activityDetectionService";
public ActivityDetectionService() {
super(TAG);
}
#Override
protected void onHandleIntent(Intent intent) {
final ActivityRecognitionResult
activityRecognitionResult =
ActivityRecognitionResult.extractResult(intent);
if (activityRecognitionResult == null) {
return;
}
//process the result here, pass the data needed to the broadcast
// e.g. you may want to use activityRecognitionResult.getMostProbableActivity(); instead
final List<DetectedActivity>
probableActivities =
activityRecognitionResult.getProbableActivities();
sendBroadcast(MainActivity.newBroadcastIntent(probableActivities));
}
}
Register the service in manifest.
<service
android:name=".ActivityDetectionService"
android:exported="false">
</service>
To use the API, you need add the followings in manifest as well.
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
To get back the data to the activity I used a BroadcastReceiver created in onCreate
mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
...
}
}
Register and unregister in onResume and onPause respectively.
#Override
protected void onResume() {
super.onResume();
registerReceiver(mBroadcastReceiver, newBroadcastIntentFilter());
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(mBroadcastReceiver);
}
As you said you are getting speed correctly. You can put customise code written below.
if (strState.equals("Automotive") && speed == 0.00)
{
strState = "Not Moving";
}
else if (strState.equals("Not Moving") && speed > 5)
{
strState = "Automotive";
}
else
{
strState = "strState";
}
This might not be the correct one but It will be give you nearby state result.
I'm not familiar with google fit api, so the only advice i can give you is to check your code carefully. Is
Integer.parseInt(val.toString())
returning the right int and can
FitnessActivities.getName()
equal "biking", "walking", "in_vehicle" etc.
As i can see from here: https://developers.google.com/fit/rest/v1/reference/activity-types
Biking, In vehicle and Walking are 0, 1 and 7.
Check what FitnessActivities.getName(0) is returning for example, also check is val returning different values or it's returning the same every time.
If you have any problem with your codes you should know what are the code is doing at any line, what methods and functions are returning... Also inform people so they found solutions easier.
I use in my Projects Estimote/Android-SDKand I have a problem.
at a time when I come to the Beacon in the area Proximity.IMMEDIATE I send a request to the server, which rotates the result of me.
beaconManager.setRangingListener(new BeaconManager.RangingListener() {
#Override
public void onBeaconsDiscovered(final Region region, final List<Beacon> beacons) {
runOnUiThread(new Runnable() {
#Override
public void run() {
getSupportActionBar().setSubtitle("Found beacons: " + beacons.size());
adapter.replaceWith(beacons);
for (Beacon beacon : beacons){
if (beacon.getMinor()==22222 && Utils.computeProximity(beacon)== Utils.Proximity.IMMEDIATE){
String android_id = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
UserApiConnector.me().getQueue(android_id);
}
}
}
});
}
});
and it works.
but the problem is that this event is triggered continuously while I'm in the area Proximity.IMMEDIATE. I also need to make this event will only work once.
Callback onBeaconsDiscovered is invoked every second, not only when beacon is first seen.
If you do not want to trigger code next time, you need to use a boolean guard like wasSomethingDone. It is initialized as false and set to true after your code is executed.
Then just write if (wasSomethingDone && beacon.getMinor()==22222 && Utils.computeProximity(beacon)== Utils.Proximity.IMMEDIATE) ....
I am struggling with one very strange bug in my app.
I have added TTS to it, and I am using the build one. The user can choose the language from the spinner which is filled in during AsyncTask started in onResume().
The AsyncTask looks like this:
private class AsyncTTSDownload extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... params) {
try {
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
ttsUpdate.dismiss();
TTSSpinnerAdapter adapterTTS = new TTSSpinnerAdapter(
MyTTS.this, android.R.layout.simple_spinner_item,
languagesTTS);
int savedLangTTS = ttsLang.getInt("savedTTS", -1);
langTTS.setAdapter(adapterTTS);
if (savedLangTTS == -1)
{
try {
int langObject = languagesTTS.indexOf(tts.getLanguage());
langTTS.setSelection(langObject);
} catch (IndexOutOfBoundsException ie) {
langTTS.setSelection(0);
}
} else {
langTTS.setSelection(savedLangTTS);
}
Locale langChoosen = (Locale) langTTS.getItemAtPosition(langTTS
.getSelectedItemPosition());
tts.setTTSLanguage(langChoosen);
}
#Override
protected void onPreExecute() {
ttsUpdate = ProgressDialog.show(MyTTS.this, "Wait",
"Loading TTS...");
ttsUpdate.setCancelable(false);
}
}
the thing is, that I am from time to time getting different number of languages supported. This is on this same device, during this same run. Just I open and close Activity with TTS. This bug is causing IndexOutOfBoundsException. This is my way of getting TTS languages:
public List<Locale> testLang() {
Locale[] AvalLoc = Locale.getAvailableLocales();
List<Locale> listaOK = new ArrayList<Locale>();
String tester = "";
for (Locale l : AvalLoc) {
if(tester.contains(l.getLanguage()))
{
continue;
}
int buf = tts.isLanguageAvailable(l);
if (buf == TextToSpeech.LANG_MISSING_DATA
|| buf == TextToSpeech.LANG_NOT_SUPPORTED) {
//TODO maybe
} else {
listaOK.add(l);
tester += l.getLanguage() + ";";
}
}
tts.setLanguage(Locale.ENGLISH);
return listaOK;
}
For now I've only find out a small hack for not showing this error, just save in shared preferences number of languages and compare it with what tts received, but it is not working well at all. Each time I am getting different number.
For me it seems, that something is not finished or started when I am starting again this same activity after return, because this is tts.isAvaliableLanguage(l) who is deciding whether language is supported or not and from time to time, one language is not supported and after reload it is.
EDIT:
As there appeared new comment about my question I need to add one important thing about TTS engine itself.
testLang() is a method inside my class Called TTSClass, that is implementing TextToSpeech.OnInitListener. tts object is created in onCreate of MyTTS activity and this constructor looks like this in TTSClass:
public TTSClass(Context context, Locale language) {
contextTTS = context;
languageTTS = language;
tts = new TextToSpeech(contextTTS, this);
}
and call in activity:
tts = new TTSClass(getApplicationContext(), Locale.ENGLISH);
Because TTSClass implements TextToSpeech.OnInitListener there is also onInit() method which looks like this:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"Initialized");
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
So, this is everything connecting to this class and problem I think. If anything is missing, let me now.
EDIT2:
Suggested by shoe rat comment I've run few more tests, and the outcome is just amazing, or extraordinary, I think it is better word.
So what I've done was adding 3 Log from different places in code informing me about list size on different stages.
First was added in onInit() in if status == TextToSpeech.SUCCESS. This one is just simple call of testLang().size(). The outcome is 5 languages - that is the correct number and it is always like this, no matter if there is or isn't an exception.
Second was added there:
protected String doInBackground(Void... params) {
try {
Log.w(TAG,"before: "+tts.testLang().size());
languagesTTS = tts.testLang();
}
and this one is starting to act quite weird. It is sometimes, or even quite often, showing number lower than 5. But this is not the strangest thing.
The third one is just at the beginning of onPostExecute checking the size of languagesTTS. And believe or not, the number is quite often totally different from the second log. However, it is never smaller. It can be equal or bigger.
Does anyone know, what is going one?
I've found solution. It came out that indeed it was initialization problem.
I'm not sure if documentation is saying anything about it, but it seem like the TTS engine initialization is done asynchronously, so it can finish at any time.
My solution was to change the doInBackground() method like this:
#Override
protected String doInBackground(Void... params) {
try {
while(!TTSClass.isInit){}
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
and in onInit() method I've added isInit public static boolean variable:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"initialized");
isInit = true;
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
Hope, that someone will find it helpful.