I\'m just wondering if it is possible / where would I get started in measuring the upwards and downwards movement of an Android device. I\'d need it to be as accurate as possibl
Case 1: Using GPS
GPS built in in almost all android devices can provide position with best possible accuracy of about 2-3 meters horizontally and 4-5 meters vertically. If that vertical accuracy is ok for your case, you can get altitude before the movement starts, and when it ends, compare them and get vertical movement.
If you need better accuracy, no android customer level device GPS can provide it. There are special professional GPS devices (used by land surveyors, costing 1000s of dollars, and very rarely running android os) that can provide cm-level accuracy, but this is not what we are talking about here.
Ofcource, you need to be outdoors in order to have good GPS reception.
Case 2: Using motion sensors
If one knows initial speed, acceleration, and time can calculate distance moved. Have a look at this http://www.dummies.com/education/science/physics/finding-distance-using-initial-velocity-time-and-acceleration/
We have
distance=start speed+ (1/2) * acceleration * time^2
In our case we know time (actually time-span) from the devices inbuilt clock, and we can get acceleration using the device's sensors. Note that TYPE_ACCELEROMETER sensor gives us acceleration force along each axis including gravity, so we have to subtract the gravitational force (we get it from TYPE_GRAVITY sensor). Keep in mind that when someone picks up a device, his hand doesn't necessarily moves in steady speed or acceleration. Therefore, changes in speed and acceleration must be taken into consideration.
Concerning start speed there are several cases.
In case it is zero (user is standing in a building, picks phone from a table and moves it to his ear) you just omit it from the formulas.
In case it not zero so that
speed=device speed+user's "vehicle" speed
, we have to use GPS to calculate it. If the user is in a lifting helicopter we can do this. In case he is in a elevator or going up the stairs of a building we cannot because there is no GPS reception there. Things get more complicated in case the helicopter or the elevator accelerates too because there is no way to automatically separate helicopter/elevator acceleration from "user moving the device" acceleration. Same when the user is walking. In that case continuous changes of speed, acceleration and direction take place as he does his steps and the body moves up and down.
I confess that I have no idea on the actual accuracy one can get in calculated distance by using the motion sensors method. And as you see from what I am writing there is no way I think to find a solution that works under every circumstances. But in the special case that the user is still and just moves the device vertically you can do something I believe.
P.S. I am not getting into details on how to get GPS data or Motion sensor data from the device sensors because you can easily google it and find help on that. I believe what is more important is to understand the maths and physics that get involved in the devices vertical movement.
Before going to solution, I would split the solution in small assets and put the puzzle for solution
For step one, I would implement SensorEventListener
to our class, this will allow us to use onSensorChanged
method.
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
listenToSensors(event);
}
}
private void listenToSensors(SensorEvent event) {
if (isPhoneVertical(event)) {
timeCounter();
if (mStatus) {
mStartTime = getStartTime();
mStatus = false;
}
} else {
if (!mStatus) {
mTotalTime = getDiffTime() + mTotalTime;
mStatus = true;
}
}
}
For step two, I have built a method called isPhoneVertical
to check if our phone is in vertical way or not, it is primarily checking the y axis. You can change the steep degree by changing maxVertical
.
Less value her less steep, 0 means the phone should almost be 100% vertical. For my test it is set to 3.
private boolean isPhoneVertical(SensorEvent event) {
float[] values = event.values;
double y = values[1];
// do not change this value
double yAxisInitValue = 10.0;
double verMargin = yAxisInitValue - maxVertical;
return y >= verMargin;
}
For step 3 I have made few method to check start time and stop time and update a global variable that keep track of time in seconds.
private long getStartTime() {
return System.currentTimeMillis() / 1000;
}
private long getDiffTime() {
return getStartTime() - mStartTime;
}
For step 4 I have made a regular runOnUiThread
to update the time on screen.
private void timeCounter() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mView1.setText("Phone has been vertical for: " + getDiffTime() + " seconds");
mView2.setText("The total time: " + (mTotalTime + getDiffTime()) + "");
}
});
}
That said this solution is to illustrate how this goal can be reached, I am sure it can be done different ways. But I wanted to show the logic behind the solution.
And here is a screen shot of the app that counts the time for each time the phone is vertical and total time it has been vertical.
The solution including some explanations:
MainActivity.java
public class MainActivity extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private TextView mView1, mView2;
private long mStartTime;
private long mTotalTime;
private boolean mStatus = true;
// less value her less steep, 0 means the phone should almost be 100% vertical
// try it out
private double maxVertical = 3.0;
@Override
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mView1 = (TextView) findViewById(R.id.textView1);
mView2 = (TextView) findViewById(R.id.textView2);
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
listenToSensors(event);
}
}
private void listenToSensors(SensorEvent event) {
if (isPhoneVertical(event)) {
timeCounter();
if (mStatus) {
mStartTime = getStartTime();
mStatus = false;
}
} else {
if (!mStatus) {
mTotalTime = getDiffTime() + mTotalTime;
mStatus = true;
}
}
}
// This method return true only for specific phone orientation
// y axis for vertical orientation
private boolean isPhoneVertical(SensorEvent event) {
float[] values = event.values;
double y = values[1];
// do not change this value
double yAxisInitValue = 10.0;
double verMargin = yAxisInitValue - maxVertical;
return y >= verMargin;
}
private long getStartTime() {
return System.currentTimeMillis() / 1000;
}
private long getDiffTime() {
return getStartTime() - mStartTime;
}
// update steps in user interface
private void timeCounter() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mView1.setText("Phone has been vertical for: " + getDiffTime() + " seconds");
mView2.setText("The total time: " + (mTotalTime + getDiffTime()) + "");
}
});
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
// if you want to collect data while mobile screen off, just disable the
// following line, the app will still collecting sensor data
mSensorManager.unregisterListener(this);
}
}
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.maytham.verticaltravels.MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:textSize="20sp"
android:text="TextView1" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/textView1"
android:layout_marginTop="57dp"
android:textSize="20sp"
android:text="TextView2" />
</RelativeLayout>
I will leave some link for reading as well.
If you think I might add more information that can help you further with your question please let me know. It is also unclear if you was looking for walking detection/counter, if that is some thing you are interesting in, look at following answers/links.
and GitHub source codes
Before,We have to know about sensor API which is we are using.
Android Sensor API
Android sensor API provides many classes and interface. The important classes and interfaces of sensor API are as follows:
1) SensorManager class
The android.hardware.SensorManager
class provides methods :
to get sensor instance,
to access and list sensors,
to register and unregister sensor listeners etc.
You can get the instance of SensorManager by calling the method getSystemService()
and passing the SENSOR_SERVICE
constant in it.
SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);
2) Sensor class
The android.hardware.Sensor
class provides methods to get information of the sensor such as sensor name, sensor type, sensor resolution, sensor type etc.
3) SensorEvent class
Its instance is created by the system. It provides information about the sensor.
4) SensorEventListener interface
It provides two call back methods to get information when sensor values (x,y and z) change or sensor accuracy changes. Public and abstract methods Description
void onAccuracyChanged(Sensor sensor, int accuracy)
//it is called when sensor accuracy is changed.
*void onSensorChanged(SensorEvent event)*
//it is called when sensor values are changed.
Note: assume *X=horizontal side,Y=Vertical side and Z=elevation * of the device.
create a simple xml like active_main.xml
<RelativeLayout xmlns:androclass="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="92dp"
android:layout_marginTop="114dp"
android:text="TextView" />
</RelativeLayout>
Next create a simple activity like MainActivity.java to measure directions like downward/upwards.
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import android.hardware.SensorManager;
import android.hardware.SensorEventListener;
import android.hardware.SensorEvent;
import android.hardware.Sensor;
import java.util.List;
public class MainActivity extends Activity {
SensorManager sm = null;
TextView textView1 = null;
List list;
SensorEventListener sel = new SensorEventListener(){
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
textView1.setText("x: "+values[0]+"\ny: "+values[1]+"\nz: "+values[2]);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Get a SensorManager instance */
sm = (SensorManager)getSystemService(SENSOR_SERVICE);
textView1 = (TextView)findViewById(R.id.textView1);
list = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);
if(list.size()>0){
sm.registerListener(sel, (Sensor) list.get(0), SensorManager.SENSOR_DELAY_NORMAL);
}else{
Toast.makeText(getBaseContext(), "Error: No Accelerometer.", Toast.LENGTH_LONG).show();
}
}
@Override
protected void onStop() {
if(list.size()>0){
sm.unregisterListener(sel);
}
super.onStop();
}
}
Output seems like:
X=0.0[No Move On Horizontal]
Y=9.77622[Moved on Vertically]
Z=0.813417[Move on Elevation]