Faster WiFi scanning alternative than WifiManager startScan()? - java

TL;DR version
I need to scan and get the BSSID / M.A.C address and Signal Level of the nearby Access Points several times a second. WifiManager.startScan() scans about 3.5 seconds which is a bit slow. (better than 6 seconds for others but still too slow for me).
I'm looking for a way to get results every 200ms or maybe even less.
The more detailed version.
I'm trying to build a location finding app that uses WiFi signals instead of the GPS. So far so good with the exception that i need to get a lot of data in order to have accurate and consistent results.
I need to get the BSSID and the Signal Level from every Access Point I can find and than store this data for later use.
I've tried using the WifiManager and the BroadcastReceiver to scan and get the scan results. The problem with them is that they are really slow. It takes at least 3 seconds for a single scan. In 3 seconds signal level value will change , leading to inaccurate results. I also need to have several thousand scans which will take an awful loooot of time. So far I haven't found an alternative.
My phone is currently running Android 8.0.0
Perhaps if I optimise my code the scan will perform faster?
onCreate method:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor);
btnMainActivity = (Button) findViewById(R.id.button_ToMainActivity);
btnGetWifiInfo = (Button) findViewById(R.id.button_GetWifiInfo);
textWifiInfo = findViewById(R.id.textView_wifiInfo);
textWifiNr = findViewById(R.id.textView_wifiNr);
mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
mWifiReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context c, Intent intent) {
getScanResultInfo();
timeDifference = SystemClock.elapsedRealtime() - startTime;
textWifiInfo.setText("Seconds elapsed: "+Double.toString(timeDifference /1000.0));
nrOfScans++;
if (nrOfScans < 10){
mWifiManager.startScan();
}
}
};
getWifiInfo();
toMainActivity();
}
public void getScanResultInfo(){
int level;
List<ScanResult> wifiScanList = mWifiManager.getScanResults();
textWifiNr.setText("Nr of detected APs: "+ wifiScanList.size());
for (ScanResult scanResult : wifiScanList) {
level = WifiManager.calculateSignalLevel(scanResult.level, 5);
Log.d("WIFI","Level is " + level + " out of 5 " + scanResult.level + " on " + scanResult.BSSID + " ");
}
}
And when I press the button the scan starts.
public void getWifiInfo(){
btnGetWifiInfo.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
finePermission = false;
startTime = SystemClock.elapsedRealtime();
nrOfScans = 0;
checkPermissions();
if ( finePermission == true){
((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE)).startScan();
mWifiManager.startScan();
}
else {
Log.d("WIFI"," Missing Permissions: "+finePermission);
}
}
}
);
}
Thank you in advance for your time!

You can skip passive channel scanning by using the hidden API, startscanActive.
startScanActive is not an exposed API in android reference; so use it at your own risk. If you must use it, refer this - https://github.com/mozilla/MozStumbler/issues/40
Also, note that if the API indeed scans only active channels as the name suggests, you will not be able to get APs present on passive channels in your scan results

Bad news from official Android side
https://android.googlesource.com/platform/frameworks/base/+/478ee74428e04522d6ac0bf10c30ddaaf62028a4
and
https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/4f11976612567e57eefb0b58c7aef1059f52b45c
announce (or propose?) to significantly limit scanResult requests per time from v9 Pie on.
In concrete values, your app should be limited to max 4 scanResult requests in 2 minutes.
This will be a disaster/nightmare for all existing WiFi-Tool apps.
Official reason: safe battery.
Let's unite our voices and let them know we want Android as a Swiss Army knife and not let it become another white brick.

Related

Solved - Java ShakeDetector triggers multiple times - How to get last output?

So I wrote an android app for dice rolling, with adjustable dice (D4-100), amounts and bonuses.
It works all fine when I press the roll button, but I also wanted it to react to shaking my phone.
The problem is, when i shake it once, it displays the result, but if i shake for too long, the shown results get visibly overwritten - I don't want the user to just keep on shaking until the result is accepted!.
Is there some way to gather all ShakeEvents and only trigger the last one that occured?
Here's what's inside onCreate related to those ShakeEvents:
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
ShakeDetector shakeDetector = new ShakeDetector(this);
shakeDetector.start(sensorManager);
and here's the "hearShake()" method (from Square, Inc.'s seismic):
#Override
public void hearShake() {
Toast.makeText(this, "Rolling...", Toast.LENGTH_SHORT).show();
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(250,VibrationEffect.DEFAULT_AMPLITUDE));
} else {
v.vibrate(250);
}
rollButton.performClick();
}
Solution:
in rollButton.performClick(); I added long lastShake = System.currentTimeMillis();
The content of hearShake() is wrapped inside if (separateShake()):
public boolean separateShake(){
return ((System.currentTimeMillis() - lastShake) > 3000) ? true : false;
}
Now rollButton.performClick() only gets triggered if there's at least a 3 second delay between the shakes, which is fine for me!
One solution would be to record the time at which the shake is recorded and ignore any additional shakes that occur within, say, the next 3 seconds. That way they'd have to do quite a long shake for it to count as multiple.

Get duration audio take 8 - 10 seconds, What's the best way to get it?

I have a loop where there's a filter to mp3 files, but when I want get duration of files took 8 - 10 seconds, I tried with Mediaplayer.create,get.Duration,MediaMetadaretrieve and Mediaplayer.setDatasource.get.Duration, but they took 8 - 10 seconds except Mediaplayer.setDatasource that will be crash it,
File scan;
File[]list;
String song_path;
int posicion;
int minutes;
int seconds;
ArrayList<String>Song_list=new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text=(TextView)findViewById(R.id.text);
metada=new MediaMetadataRetriever();
scan= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
list=scan.listFiles();
for(File files:list){
if(files.getName().endsWith("mp3")){
song_path=String.valueOf(files.getAbsolutePath());
Song_list.add(song_path);
metada.setDataSource(MainActivity.this,Uri.parse(song_path));
String dutation=metada.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
posicion=Integer.parseInt(dutation)/1000;
minutes=posicion/60;
seconds=posicion%60;
text.setText(minutes+":"+seconds+text.getText());
/*player=MediaPlayer.create(MainActivity.this,Uri.parse(song_path));
posicion=player.getDuration()/1000;
minutes=posicion/60;
seconds=posicion/60;
text.setText(minutes+":"+seconds+text.getText());*/
}
}
}
}
I know that the codes Media.create and metada.extracMetada are they who took a lot of time, Thank you for the attention
You can use AudioFileIO Java class to read and retreive files data
Example in your For loop you can use this Instead :
AudioFile myMp3 = AudioFileIO.read(files);
duration = myMp3.getAudioHeader().getTrackLength();
I would suggest you to use ExoPlayer which is also by Android and takes much lesser time in getting the duration of an audio mp3 file. It is better in implementation and plays files from url as well as local files. Plus you can customise it also for any view and has all kinds of listeners.
https://developer.android.com/guide/topics/media/exoplayer.html

Android GPS update frequency

I'm writing a lap timing app but have run into a GPS update frequency problem. At speeds greater than 75km/h (21m/s) my code stops working. My question is how can I request updates at a faster rate? I need it work at speeds up to 300km/h (83m/s) and would like the app to get updates every couple of meters traveled which would mean it would need an update every 0.025 seconds # 300km/h. Below is my code, I tried an alternate code to get time stamp but got the same result, I believe it's a GPS update frequency problem not a code problem. I wanted updates every couple of meters # 300km/h in case the phone passes through the proximity radius on a tangent.
int prox = 30; // Proximity Switch To Finish Line = 30 meters
int speedGov = 0; // Speed In Kmh
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, Availability status, Bundle extras)
{
}
protected override void OnResume()
{
this.InitializeLocationManager();
base.OnResume();
_locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);
}
void InitializeLocationManager()
{
_locationManager = (LocationManager)GetSystemService(LocationService);
Criteria criteriaForLocationService = new Criteria
{
Accuracy = Accuracy.Fine
};
IList<string> acceptableLocationProviders = _locationManager.GetProviders(criteriaForLocationService, true);
if (acceptableLocationProviders.Any())
{
_locationProvider = acceptableLocationProviders.First();
}
else
{
_locationProvider = String.Empty;
}
}
public void OnLocationChanged(Location location)
{
_currentLocation = location;
if (_currentLocation == null)
{
}
else
{
d2fl = Convert.ToInt32(_currentLocation.DistanceTo(fl));
speedGov = Convert.ToInt32(_currentLocation.Speed * 3.6);
}
}
int A = 0; // 1st Distance to Finish Line
int B = 1000000; // 2nd Distance to Finish Line
// Get Time Stamp
while (true)
{
A = d2fl;
if (A > B && d2fl < prox && speedGov > 2) // Travelling away from Finish Line & Within 30m proximity to Finish Line & Going faster than 2km/h
{
// Time stamp for when phone first starts travelling away from Finish Line
string hours = DateTime.Now.ToString("HH");
string minutes = DateTime.Now.ToString("mm");
string seconds = DateTime.Now.ToString("ss");
string milliseconds = DateTime.Now.ToString("fff");
lapFinishTimeStamp = (Convert.ToDecimal(hours) * 3600) + (Convert.ToDecimal(minutes) * 60) + Convert.ToDecimal(seconds) + (Convert.ToDecimal(milliseconds) / 1000);
A = 0;
B = 1000000;
break;
}
B = A;
}
// Alternate Get Time Stamp - worked the same as above "Get Time Stamp"
while (true)
{
int A = d2fl;
Thread.Sleep(5);
int B = d2fl;
if (A < B && d2fl < prox && speedGov > 2)
{
string hours = DateTime.Now.ToString("HH");
string minutes = DateTime.Now.ToString("mm");
string seconds = DateTime.Now.ToString("ss");
string milliseconds = DateTime.Now.ToString("fff");
lapFinishTimeStamp = (Convert.ToDecimal(hours) * 3600) + (Convert.ToDecimal(minutes) * 60) + Convert.ToDecimal(seconds) + (Convert.ToDecimal(milliseconds) / 1000);
A = 0;
B = 0;
break;
}
A = 0;
B = 0;
}
Have read some other anwsers on this forum but are a few years old. This app will need to work on Galaxy S4 onwards.
Plus I'm a little confused about the GPS frequency's, from what I've read the GPS frequency operates at quite a high rate (hardware is around 1.6 GHz) but the phones operating systems seems to cull the data to a lower frequency, is this intentional?
Don't confuse the the radio frequency value (1.1-1.6GHz) from how frequently you will get location updates (1Hz).
Have you seen the device list in: Get GPS position on a precise time interval ? Even though its a few years old, I doubt any on device GPS will report any faster (probably due to battery/noise/use case design). Even if the on board device was reporting at 10Hz or 20Hz that is only 100ms or 50ms which is still slower than your requirement of 25ms. Remember if the CPU is talking to the GPS and calculating location - it is eating battery which is the limiting factor on mobile devices.
If you want consistent sub-second GPS value updates you'll need to use an external device.
Consumer devices update gps positions with a rate of 1hz, so one location per second.
A higher rate of e.g 10 / s would not make much sense.
Positions would not get better, more likely they get worse, however this a theoretical discussion, since consumer GPS chips usually will not provide a higher rate than 1 or 2 hz.
So just change your application design.
Especially at high speeds it is save to interpolate between two locations.
Keep in mind that position have an circular error of at least 3-5m.
So your lap timing app, migght addionally output an timingAccuracy value.
5m error at 100km/h result in an timing accuracy of 0.18s.
You can get the estimated positional error with location.getHoricontalAccuracy() (or similar names)

frequency of onSensorChanged() is wierd(too fast)?

When I use onSensorChanged() to test my cellphone's accelerometer, finding that it responses to fast. Almost every 1~3ms onSensorChanged() will be activated. I search for some other cellphone's information feeling that it is kind of weird, so I doubt that my code might be wrong. Here is part of my code:
public void onSensorChanged(SensorEvent se_a) { /* 取得x,y,z加速度值 */
xa = se_a.values[0];
ya = se_a.values[1];
za = se_a.values[2];
final String timeStamp_a = new SimpleDateFormat("HHmmssSSS",
Locale.UK).format(new Date());
String tmp_a = "0 " + timeStamp_a + " " + String.valueOf(xa)
+ " " + String.valueOf(ya) + " " + String.valueOf(za) + "\n";
And part of result is like:
0 160106203 9.5385 -0.6895301 1.1109096
0 160106204 9.500193 -0.5746084 1.1109096
0 160106206 9.576807 -0.5746084 1.1875241
0 160106207 9.461885 -0.6895301 1.3024458
My cellphone is LG G2. I set the accelerometer to SENSOR_DELAY_FASTEST. But using some app on google play to test my accelerometer, it shows that in SENSOR_DELAY_FASTEST the frequency is 120Hz, so it is very weird to find that onSensorChanged() response so fast(1~3ms). Where is my code can be wrong? Please help me!
If the update rate is to fast using SENSOR_DELAY_FASTEST you can set another flag which fit your needs.
See the docs for the different rates you can use:
The default data delay (SENSOR_DELAY_NORMAL) is suitable for
monitoring typical screen orientation changes and uses a delay of
200,000 microseconds. You can specify other data delays, such as SENSOR_DELAY_GAME (20,000 microsecond delay), SENSOR_DELAY_UI
(60,000 microsecond delay), or SENSOR_DELAY_FASTEST (0
microsecond!!!! delay). As of Android 3.0 (API Level 11) you can
also specify the delay as an absolute value (in microseconds).
Edit: Have a look at this method SensorManager.registerListener(SensorEventListener, Sensor, int) where you can specify your delay in ms if no flag supports your needs. This is available since API 9. If you want a specific frequenzy provide it as a parameter.

what's wrong with my sensor monitoring technique?

(please read UPDATE 3 at the end)I'm developing an app that continually works with the sensors of device, works with Accelerometer and Magnetic sensors to retrieve the orientation of device(the purpose is mentioned here). in other words, my app needs to know the orientation of device in Real-time(however this is never possible, so as fast as possible instead, but really as fast as possible !). as mentioned in professional Android 4 Application Development by Reto Meier:
The accelerometers can update hundreds of times a second...
I must not lose any data that sensors report and I also want to do time-consuming operations on these data(retrieve the orientation and then do calculations... ). I decided to solve my problem by using LinkedBlockingQueue:
public void startSensors() {
LinkedBlockingQueue<float[][]> array=new LinkedBlockingQueue();
sensorListenerForOrientation = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
if (aValues != null && mValues != null) {
try {
array.put(new float[][] { aValues, mValues });
} catch (InterruptedException e) {
}
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
Sensor aSensor = sm.getSensorList(Sensor.TYPE_ACCELEROMETER).get(
sm.getSensorList(Sensor.TYPE_ACCELEROMETER).size() - 1);
Sensor mSensor = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(
sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).size() - 1);
sm.registerListener(sensorListenerForOrientation, aSensor,
SensorManager.SENSOR_DELAY_FASTEST);
sm.registerListener(sensorListenerForOrientation, mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
executor.execute(new Runnable() {
#Override
public void run() {
doCalculations();
}
});
}
and
public void doCalculations() {
for (;;) {
float[][] result = null;
try {
result = array.take();
} catch (InterruptedException e) {
}
float[] aValues, mValues;
aValues = result[0];
mValues = result[1];
int[] degrees=getOrientation(aValues,mValues);
Log.e("",String.valueOf(degrees[0]));
//other calculations...
}
}
now I pick up my device and rotate it about 90 degrees to right and then return it to the first position fast(for example in 1.5 seconds) but as I look at the orientations that are registered in device I see for example: 0,1,2,3,4,5.......,40,39,38,37,....,0
I just want to say that I can't see a large domain of degrees in my result .
based on what I have done and what I have researched I just can be sure that I am NOT losing any data, any new data reported by sensors are recorded.
any Idea, solution?!
Regards!
UPDATE 1: I did another experiment with my device and got shocking results! if I rotate my device over an axis 90 degrees fast (less than a second), I can see all degrees in my result: 0,1,2,3,....,89,90 (for example) but if I rotate it 90 degrees and then rotate it back to its first position, the result would be 0,1,2,...,36,37,36,...2,1,0(for example)...really confusing !
UPDATE 2: I updated doCalculations() method to be more clear what I have done
UPDATE 3: I think maybe we can solve the problem in another way! I have clear purposes for this code. please have a look at this. I
have mentioned what is going to happen, I need to detect an specific
movement gesture. so maybe the whole way that I have chosen(the
technique above) is not a good way for solving this problem. maybe
it's better to detect that gesture by using other sensors or using the
same sensors in other way. what do you think?
So it looks like you are trying to find high throughput low latency solution for a standard "Producer-Consumer" problem. Basically the idea is quite straightforward: decrease data handling overhead, process data in parallel. Suggestions are the following:
1. Use "low latency" libraries
javolution.org - is a real-time library aiming to make Java or Java-Like/C++ applications faster and more time predictable. It includes Android support.
mentaqueue - is a super-fast, garbage-less, lock-free, two-thread (producer-consumer) queue based on the Disruptor ideas. Android support is undefined (it looks like it should work).
disruptor - yet another lightning fast library
trove - provides high speed regular and primitive collections for Java.
Any of these solution will let you save a lot of CPU cycles.
2. Process data wisely
There is an overhead every time you submit a job. Batch processing can be really helpful.
Process data continuously. Note, executor.execute will consume quite a lot. Several long-living consumers might help.
3. Finally, use micro optimization techniques
For example, get rid of if-else-if in favor of switch.
Track performance all the time in order to identify good and bad solutions. Experiment.
Happy coding.
Just thinking: please try the following:
public void startSensors() {
final Stack<Runnable> mStack = new Stack<Runnable>();
sensorListenerForOrientation = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
if (aValues != null && mValues != null) {
mStack.push(new Calculater(new float[][] { aValues, mValues });
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
Sensor aSensor = sm.getSensorList(Sensor.TYPE_ACCELEROMETER).get(
sm.getSensorList(Sensor.TYPE_ACCELEROMETER).size() - 1);
Sensor mSensor = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(
sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).size() - 1);
sm.registerListener(sensorListenerForOrientation, aSensor,
SensorManager.SENSOR_DELAY_FASTEST);
sm.registerListener(sensorListenerForOrientation, mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
new Thread() {
public void run() {
while(true)
{
try {
Runnable r = mStack.pop();
r.run();
} catch(Exception ex){}
}
}
}.start();
}
private class Calculater implements Runnable {
float[][] theValues;
public Calculater(float[][] values) {
theValues = values;
}
public void run() {
int[] degrees= getOrientation(theValues[0], theValues[1]);
Log.e("",String.valueOf(degrees[0]));
}
}
Your code looks reasonable. A big unknown is how good the sensors and sensor fusion are in your device. Quick angle change readings rely on integration of angular acceleration or else a physical gyroscope with magnetic data mixed in to make the result absolutely align with the earth. Magnetic data are subject to surroundings. If your device has low quality sensors or there are magnetic disturbances in your environment, it's entirely possible to see the kinds of error you are seeing. Big metal structures and magnetic equipment (like motors or even fluorescent light ballasts) can blank the field or introduce arbitrary errors. For normal uses, a device only needs an accelerometer to accurately determine which way is down so screen flips are accurate. This only needs to work when the device is not moving, where a gyro has no role. If you have a phone or tablet with sensors meant only to serve this purpose - therefore with no gyro or an inaccurate one - you are seeing a device limitation. The erratic values are other evidence that your device is low quality and/or that you are in a location where the earth's magnetic field is being distorted. Try the program on another (preferably expensive) device outside and in the open, and see what you get.
The usual thing to do within an event block is to do almost nothing, since this is really fast.
"Almost" being the important word. In your case, the event could just add the data of the event (from the event parameter) to some data structure (list, stack, circular buffer... your pick). That way you should lose less events (if any).
Which means that you can then (for instance periodically) read the stored events and decide if a gesture was made. That means that your intensive calculations are made less often. But you don't lose any events. I think this is acceptable because of your purpose, which is gesture recognition. I assume it doesn't have to be that fast (ie. you don't have to calculate it every time the sensor updates).
Note : this is one common way to handle IT in the Linux world.
just a thought. I have a similar problem when I needed to collect several large sample sizes an perform calculations. My situation was probably quite different from yours as I just needed acceleration. What I did was create an array list. calculated acceleration per every record reported :
#Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float acceleration = FloatMath.sqrt((x * x) + (y * y) + (z * z));
Then in the same onSensorChanged method, I wait until the size hits a certain limit, like 300, clone that sample to a new list,clear out original, perform calculations on new list and continue in that manner. I get results in secs. I am not sure how much down time is allowed for your application but when I run this I get what I am looking for in less that 5 secs. If you need more sample code let me know, but that is the gist. Sorry if I didn't understand your question properly but I think you were asking for a way to calculate data without losing much? Also I have this running on a separate handler when I register the listener, not to interfere with the main thread, not to effect user experience.
Change variable declaration:
List<float[][]> array = Collections.synchronizedList(new ArrayList<float[][]>());
Inside the runnable:
Iterator<float[][]> values = array.iterator();
while (values.hasNext()) {
float[][] result = values.next();
//calculating.
//after calculating remove the items.
values.remove();
}
This is what's wrong with your code. Fast as possible requires fast coding techniques. Save the sensor type instead of evaluating it twice.
#Override
public void onSensorChanged(SensorEvent event) {
int i = event.sensor.getType();
if (i == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (i == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
}

Categories