簡單重力感應跑步測速應用
阿新 • • 發佈:2019-01-24
一、執行效果圖
二、核心程式碼
public class CaloriesNotifier implements StepListener, SpeakingTimer.Listener { public interface Listener { public void valueChanged(float value); public void passValue(); } private Listener mListener; private static double METRIC_RUNNING_FACTOR = 1.02784823; private static double IMPERIAL_RUNNING_FACTOR = 0.75031498; private static double METRIC_WALKING_FACTOR = 0.708; private static double IMPERIAL_WALKING_FACTOR = 0.517; private double mCalories = 0; PedometerSettings mSettings; Utils mUtils; boolean mIsMetric; boolean mIsRunning; float mStepLength; float mBodyWeight; public CaloriesNotifier(Listener listener, PedometerSettings settings, Utils utils) { mListener = listener; mUtils = utils; mSettings = settings; reloadSettings(); } public void setCalories(float calories) { mCalories = calories; notifyListener(); } public void reloadSettings() { mIsMetric = mSettings.isMetric(); mIsRunning = mSettings.isRunning(); mStepLength = mSettings.getStepLength(); mBodyWeight = mSettings.getBodyWeight(); notifyListener(); } public void resetValues() { mCalories = 0; } public void isMetric(boolean isMetric) { mIsMetric = isMetric; } public void setStepLength(float stepLength) { mStepLength = stepLength; } public void onStep() { if (mIsMetric) { mCalories += (mBodyWeight * (mIsRunning ? METRIC_RUNNING_FACTOR : METRIC_WALKING_FACTOR)) // Distance: * mStepLength // centimeters / 100000.0; // centimeters/kilometer } else { mCalories += (mBodyWeight * (mIsRunning ? IMPERIAL_RUNNING_FACTOR : IMPERIAL_WALKING_FACTOR)) // Distance: * mStepLength // inches / 63360.0; // inches/mile } notifyListener(); } private void notifyListener() { mListener.valueChanged((float)mCalories); } public void passValue() { } public void speak() { if (mSettings.shouldTellCalories()) { if (mCalories > 0) { mUtils.say("" + (int)mCalories + " calories burned"); } } } }
public class DistanceNotifier implements StepListener, SpeakingTimer.Listener { public interface Listener { public void valueChanged(float value); public void passValue(); } private Listener mListener; float mDistance = 0; PedometerSettings mSettings; Utils mUtils; boolean mIsMetric; float mStepLength; public DistanceNotifier(Listener listener, PedometerSettings settings, Utils utils) { mListener = listener; mUtils = utils; mSettings = settings; reloadSettings(); } public void setDistance(float distance) { mDistance = distance; notifyListener(); } public void reloadSettings() { mIsMetric = mSettings.isMetric(); mStepLength = mSettings.getStepLength(); notifyListener(); } public void onStep() { if (mIsMetric) { mDistance += (float)(// kilometers mStepLength // centimeters / 100000.0); // centimeters/kilometer } else { mDistance += (float)(// miles mStepLength // inches / 63360.0); // inches/mile } notifyListener(); } private void notifyListener() { mListener.valueChanged(mDistance); } public void passValue() { // Callback of StepListener - Not implemented } public void speak() { if (mSettings.shouldTellDistance()) { if (mDistance >= .001f) { mUtils.say(("" + (mDistance + 0.000001f)).substring(0, 4) + (mIsMetric ? " kilometers" : " miles")); // TODO: format numbers (no "." at the end) } } } }
public class PaceNotifier implements StepListener, SpeakingTimer.Listener { public interface Listener { public void paceChanged(int value); public void passValue(); } private ArrayList<Listener> mListeners = new ArrayList<Listener>(); int mCounter = 0; private long mLastStepTime = 0; private long[] mLastStepDeltas = {-1, -1, -1, -1}; private int mLastStepDeltasIndex = 0; private long mPace = 0; PedometerSettings mSettings; Utils mUtils; /** Desired pace, adjusted by the user */ int mDesiredPace; /** Should we speak? */ boolean mShouldTellFasterslower; /** When did the TTS speak last time */ private long mSpokenAt = 0; public PaceNotifier(PedometerSettings settings, Utils utils) { mUtils = utils; mSettings = settings; mDesiredPace = mSettings.getDesiredPace(); reloadSettings(); } public void setPace(int pace) { mPace = pace; int avg = (int)(60*1000.0 / mPace); for (int i = 0; i < mLastStepDeltas.length; i++) { mLastStepDeltas[i] = avg; } notifyListener(); } public void reloadSettings() { mShouldTellFasterslower = mSettings.shouldTellFasterslower() && mSettings.getMaintainOption() == PedometerSettings.M_PACE; notifyListener(); } public void addListener(Listener l) { mListeners.add(l); } public void setDesiredPace(int desiredPace) { mDesiredPace = desiredPace; } public void onStep() { long thisStepTime = System.currentTimeMillis(); mCounter ++; // Calculate pace based on last x steps if (mLastStepTime > 0) { long delta = thisStepTime - mLastStepTime; mLastStepDeltas[mLastStepDeltasIndex] = delta; mLastStepDeltasIndex = (mLastStepDeltasIndex + 1) % mLastStepDeltas.length; long sum = 0; boolean isMeaningfull = true; for (int i = 0; i < mLastStepDeltas.length; i++) { if (mLastStepDeltas[i] < 0) { isMeaningfull = false; break; } sum += mLastStepDeltas[i]; } if (isMeaningfull && sum > 0) { long avg = sum / mLastStepDeltas.length; mPace = 60*1000 / avg; // TODO: remove duplication. This also exists in SpeedNotifier if (mShouldTellFasterslower && !mUtils.isSpeakingEnabled()) { if (thisStepTime - mSpokenAt > 3000 && !mUtils.isSpeakingNow()) { float little = 0.10f; float normal = 0.30f; float much = 0.50f; boolean spoken = true; if (mPace < mDesiredPace * (1 - much)) { mUtils.say("much faster!"); } else if (mPace > mDesiredPace * (1 + much)) { mUtils.say("much slower!"); } else if (mPace < mDesiredPace * (1 - normal)) { mUtils.say("faster!"); } else if (mPace > mDesiredPace * (1 + normal)) { mUtils.say("slower!"); } else if (mPace < mDesiredPace * (1 - little)) { mUtils.say("a little faster!"); } else if (mPace > mDesiredPace * (1 + little)) { mUtils.say("a little slower!"); } else { spoken = false; } if (spoken) { mSpokenAt = thisStepTime; } } } } else { mPace = -1; } } mLastStepTime = thisStepTime; notifyListener(); } private void notifyListener() { for (Listener listener : mListeners) { listener.paceChanged((int)mPace); } } public void passValue() { // Not used } //----------------------------------------------------- // Speaking public void speak() { if (mSettings.shouldTellPace()) { if (mPace > 0) { mUtils.say(mPace + " steps per minute"); } } } }
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class Pedometer extends Activity {
private static final String TAG = "Pedometer";
private SharedPreferences mSettings;
private PedometerSettings mPedometerSettings;
private Utils mUtils;
private TextView mStepValueView;
private TextView mPaceValueView;
private TextView mDistanceValueView;
private TextView mSpeedValueView;
private TextView mCaloriesValueView;
TextView mDesiredPaceView;
private int mStepValue;
private int mPaceValue;
private float mDistanceValue;
private float mSpeedValue;
private int mCaloriesValue;
private float mDesiredPaceOrSpeed;
private int mMaintain;
private boolean mIsMetric;
private float mMaintainInc;
private boolean mQuitting = false; // Set when user selected Quit from menu, can be used by onPause, onStop, onDestroy
/**
* True, when service is running.
*/
private boolean mIsRunning;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "[ACTIVITY] onCreate");
super.onCreate(savedInstanceState);
mStepValue = 0;
mPaceValue = 0;
setContentView(R.layout.main);
mUtils = Utils.getInstance();
}
@Override
protected void onStart() {
Log.i(TAG, "[ACTIVITY] onStart");
super.onStart();
}
@Override
protected void onResume() {
Log.i(TAG, "[ACTIVITY] onResume");
super.onResume();
mSettings = PreferenceManager.getDefaultSharedPreferences(this);
mPedometerSettings = new PedometerSettings(mSettings);
mUtils.setSpeak(mSettings.getBoolean("speak", false));
// Read from preferences if the service was running on the last onPause
mIsRunning = mPedometerSettings.isServiceRunning();
// Start the service if this is considered to be an application start (last onPause was long ago)
if (!mIsRunning && mPedometerSettings.isNewStart()) {
startStepService();
bindStepService();
}
else if (mIsRunning) {
bindStepService();
}
mPedometerSettings.clearServiceRunning();
mStepValueView = (TextView) findViewById(R.id.step_value);
mPaceValueView = (TextView) findViewById(R.id.pace_value);
mDistanceValueView = (TextView) findViewById(R.id.distance_value);
mSpeedValueView = (TextView) findViewById(R.id.speed_value);
mCaloriesValueView = (TextView) findViewById(R.id.calories_value);
mDesiredPaceView = (TextView) findViewById(R.id.desired_pace_value);
mIsMetric = mPedometerSettings.isMetric();
((TextView) findViewById(R.id.distance_units)).setText(getString(
mIsMetric
? R.string.kilometers
: R.string.miles
));
((TextView) findViewById(R.id.speed_units)).setText(getString(
mIsMetric
? R.string.kilometers_per_hour
: R.string.miles_per_hour
));
mMaintain = mPedometerSettings.getMaintainOption();
((LinearLayout) this.findViewById(R.id.desired_pace_control)).setVisibility(
mMaintain != PedometerSettings.M_NONE
? View.VISIBLE
: View.GONE
);
if (mMaintain == PedometerSettings.M_PACE) {
mMaintainInc = 5f;
mDesiredPaceOrSpeed = (float)mPedometerSettings.getDesiredPace();
}
else
if (mMaintain == PedometerSettings.M_SPEED) {
mDesiredPaceOrSpeed = mPedometerSettings.getDesiredSpeed();
mMaintainInc = 0.1f;
}
Button button1 = (Button) findViewById(R.id.button_desired_pace_lower);
button1.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mDesiredPaceOrSpeed -= mMaintainInc;
mDesiredPaceOrSpeed = Math.round(mDesiredPaceOrSpeed * 10) / 10f;
displayDesiredPaceOrSpeed();
setDesiredPaceOrSpeed(mDesiredPaceOrSpeed);
}
});
Button button2 = (Button) findViewById(R.id.button_desired_pace_raise);
button2.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mDesiredPaceOrSpeed += mMaintainInc;
mDesiredPaceOrSpeed = Math.round(mDesiredPaceOrSpeed * 10) / 10f;
displayDesiredPaceOrSpeed();
setDesiredPaceOrSpeed(mDesiredPaceOrSpeed);
}
});
if (mMaintain != PedometerSettings.M_NONE) {
((TextView) findViewById(R.id.desired_pace_label)).setText(
mMaintain == PedometerSettings.M_PACE
? R.string.desired_pace
: R.string.desired_speed
);
}
displayDesiredPaceOrSpeed();
}
private void displayDesiredPaceOrSpeed() {
if (mMaintain == PedometerSettings.M_PACE) {
mDesiredPaceView.setText("" + (int)mDesiredPaceOrSpeed);
}
else {
mDesiredPaceView.setText("" + mDesiredPaceOrSpeed);
}
}
@Override
protected void onPause() {
Log.i(TAG, "[ACTIVITY] onPause");
if (mIsRunning) {
unbindStepService();
}
if (mQuitting) {
mPedometerSettings.saveServiceRunningWithNullTimestamp(mIsRunning);
}
else {
mPedometerSettings.saveServiceRunningWithTimestamp(mIsRunning);
}
super.onPause();
savePaceSetting();
}
@Override
protected void onStop() {
Log.i(TAG, "[ACTIVITY] onStop");
super.onStop();
}
protected void onDestroy() {
Log.i(TAG, "[ACTIVITY] onDestroy");
super.onDestroy();
}
protected void onRestart() {
Log.i(TAG, "[ACTIVITY] onRestart");
super.onDestroy();
}
private void setDesiredPaceOrSpeed(float desiredPaceOrSpeed) {
if (mService != null) {
if (mMaintain == PedometerSettings.M_PACE) {
mService.setDesiredPace((int)desiredPaceOrSpeed);
}
else
if (mMaintain == PedometerSettings.M_SPEED) {
mService.setDesiredSpeed(desiredPaceOrSpeed);
}
}
}
private void savePaceSetting() {
mPedometerSettings.savePaceOrSpeedSetting(mMaintain, mDesiredPaceOrSpeed);
}
private StepService mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ((StepService.StepBinder)service).getService();
mService.registerCallback(mCallback);
mService.reloadSettings();
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
private void startStepService() {
if (! mIsRunning) {
Log.i(TAG, "[SERVICE] Start");
mIsRunning = true;
startService(new Intent(Pedometer.this,
StepService.class));
}
}
private void bindStepService() {
Log.i(TAG, "[SERVICE] Bind");
bindService(new Intent(Pedometer.this,
StepService.class), mConnection, Context.BIND_AUTO_CREATE + Context.BIND_DEBUG_UNBIND);
}
private void unbindStepService() {
Log.i(TAG, "[SERVICE] Unbind");
unbindService(mConnection);
}
private void stopStepService() {
Log.i(TAG, "[SERVICE] Stop");
if (mService != null) {
Log.i(TAG, "[SERVICE] stopService");
stopService(new Intent(Pedometer.this,
StepService.class));
}
mIsRunning = false;
}
private void resetValues(boolean updateDisplay) {
if (mService != null && mIsRunning) {
mService.resetValues();
}
else {
mStepValueView.setText("0");
mPaceValueView.setText("0");
mDistanceValueView.setText("0");
mSpeedValueView.setText("0");
mCaloriesValueView.setText("0");
SharedPreferences state = getSharedPreferences("state", 0);
SharedPreferences.Editor stateEditor = state.edit();
if (updateDisplay) {
stateEditor.putInt("steps", 0);
stateEditor.putInt("pace", 0);
stateEditor.putFloat("distance", 0);
stateEditor.putFloat("speed", 0);
stateEditor.putFloat("calories", 0);
stateEditor.commit();
}
}
}
private static final int MENU_SETTINGS = 8;
private static final int MENU_QUIT = 9;
private static final int MENU_PAUSE = 1;
private static final int MENU_RESUME = 2;
private static final int MENU_RESET = 3;
/* Creates the menu items */
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
if (mIsRunning) {
menu.add(0, MENU_PAUSE, 0, R.string.pause)
.setIcon(android.R.drawable.ic_media_pause)
.setShortcut('1', 'p');
}
else {
menu.add(0, MENU_RESUME, 0, R.string.resume)
.setIcon(android.R.drawable.ic_media_play)
.setShortcut('1', 'p');
}
menu.add(0, MENU_RESET, 0, R.string.reset)
.setIcon(android.R.drawable.ic_menu_close_clear_cancel)
.setShortcut('2', 'r');
menu.add(0, MENU_SETTINGS, 0, R.string.settings)
.setIcon(android.R.drawable.ic_menu_preferences)
.setShortcut('8', 's')
.setIntent(new Intent(this, Settings.class));
menu.add(0, MENU_QUIT, 0, R.string.quit)
.setIcon(android.R.drawable.ic_lock_power_off)
.setShortcut('9', 'q');
return true;
}
/* Handles item selections */
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_PAUSE:
unbindStepService();
stopStepService();
return true;
case MENU_RESUME:
startStepService();
bindStepService();
return true;
case MENU_RESET:
resetValues(true);
return true;
case MENU_QUIT:
resetValues(false);
unbindStepService();
stopStepService();
mQuitting = true;
finish();
return true;
}
return false;
}
// TODO: unite all into 1 type of message
private StepService.ICallback mCallback = new StepService.ICallback() {
public void stepsChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(STEPS_MSG, value, 0));
}
public void paceChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(PACE_MSG, value, 0));
}
public void distanceChanged(float value) {
mHandler.sendMessage(mHandler.obtainMessage(DISTANCE_MSG, (int)(value*1000), 0));
}
public void speedChanged(float value) {
mHandler.sendMessage(mHandler.obtainMessage(SPEED_MSG, (int)(value*1000), 0));
}
public void caloriesChanged(float value) {
mHandler.sendMessage(mHandler.obtainMessage(CALORIES_MSG, (int)(value), 0));
}
};
private static final int STEPS_MSG = 1;
private static final int PACE_MSG = 2;
private static final int DISTANCE_MSG = 3;
private static final int SPEED_MSG = 4;
private static final int CALORIES_MSG = 5;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case STEPS_MSG:
mStepValue = (int)msg.arg1;
mStepValueView.setText("" + mStepValue);
break;
case PACE_MSG:
mPaceValue = msg.arg1;
if (mPaceValue <= 0) {
mPaceValueView.setText("0");
}
else {
mPaceValueView.setText("" + (int)mPaceValue);
}
break;
case DISTANCE_MSG:
mDistanceValue = ((int)msg.arg1)/1000f;
if (mDistanceValue <= 0) {
mDistanceValueView.setText("0");
}
else {
mDistanceValueView.setText(
("" + (mDistanceValue + 0.000001f)).substring(0, 5)
);
}
break;
case SPEED_MSG:
mSpeedValue = ((int)msg.arg1)/1000f;
if (mSpeedValue <= 0) {
mSpeedValueView.setText("0");
}
else {
mSpeedValueView.setText(
("" + (mSpeedValue + 0.000001f)).substring(0, 4)
);
}
break;
case CALORIES_MSG:
mCaloriesValue = msg.arg1;
if (mCaloriesValue <= 0) {
mCaloriesValueView.setText("0");
}
else {
mCaloriesValueView.setText("" + (int)mCaloriesValue);
}
break;
default:
super.handleMessage(msg);
}
}
};
}
public class PedometerSettings {
SharedPreferences mSettings;
public static int M_NONE = 1;
public static int M_PACE = 2;
public static int M_SPEED = 3;
public PedometerSettings(SharedPreferences settings) {
mSettings = settings;
}
public boolean isMetric() {
return mSettings.getString("units", "imperial").equals("metric");
}
public float getStepLength() {
try {
return Float.valueOf(mSettings.getString("step_length", "20").trim());
}
catch (NumberFormatException e) {
// TODO: reset value, & notify user somehow
return 0f;
}
}
public float getBodyWeight() {
try {
return Float.valueOf(mSettings.getString("body_weight", "50").trim());
}
catch (NumberFormatException e) {
// TODO: reset value, & notify user somehow
return 0f;
}
}
public boolean isRunning() {
return mSettings.getString("exercise_type", "running").equals("running");
}
public int getMaintainOption() {
String p = mSettings.getString("maintain", "none");
return
p.equals("none") ? M_NONE : (
p.equals("pace") ? M_PACE : (
p.equals("speed") ? M_SPEED : (
0)));
}
//-------------------------------------------------------------------
// Desired pace & speed:
// these can not be set in the preference activity, only on the main
// screen if "maintain" is set to "pace" or "speed"
public int getDesiredPace() {
return mSettings.getInt("desired_pace", 180); // steps/minute
}
public float getDesiredSpeed() {
return mSettings.getFloat("desired_speed", 4f); // km/h or mph
}
public void savePaceOrSpeedSetting(int maintain, float desiredPaceOrSpeed) {
SharedPreferences.Editor editor = mSettings.edit();
if (maintain == M_PACE) {
editor.putInt("desired_pace", (int)desiredPaceOrSpeed);
}
else
if (maintain == M_SPEED) {
editor.putFloat("desired_speed", desiredPaceOrSpeed);
}
editor.commit();
}
//-------------------------------------------------------------------
// Speaking:
public boolean shouldSpeak() {
return mSettings.getBoolean("speak", false);
}
public float getSpeakingInterval() {
try {
return Float.valueOf(mSettings.getString("speaking_interval", "1"));
}
catch (NumberFormatException e) {
// This could not happen as the value is selected from a list.
return 1;
}
}
public boolean shouldTellSteps() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_steps", false);
}
public boolean shouldTellPace() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_pace", false);
}
public boolean shouldTellDistance() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_distance", false);
}
public boolean shouldTellSpeed() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_speed", false);
}
public boolean shouldTellCalories() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_calories", false);
}
public boolean shouldTellFasterslower() {
return mSettings.getBoolean("speak", false)
&& mSettings.getBoolean("tell_fasterslower", false);
}
public boolean wakeAggressively() {
return mSettings.getString("operation_level", "run_in_background").equals("wake_up");
}
public boolean keepScreenOn() {
return mSettings.getString("operation_level", "run_in_background").equals("keep_screen_on");
}
//
// Internal
public void saveServiceRunningWithTimestamp(boolean running) {
SharedPreferences.Editor editor = mSettings.edit();
editor.putBoolean("service_running", running);
editor.putLong("last_seen", Utils.currentTimeInMillis());
editor.commit();
}
public void saveServiceRunningWithNullTimestamp(boolean running) {
SharedPreferences.Editor editor = mSettings.edit();
editor.putBoolean("service_running", running);
editor.putLong("last_seen", 0);
editor.commit();
}
public void clearServiceRunning() {
SharedPreferences.Editor editor = mSettings.edit();
editor.putBoolean("service_running", false);
editor.putLong("last_seen", 0);
editor.commit();
}
public boolean isServiceRunning() {
return mSettings.getBoolean("service_running", false);
}
public boolean isNewStart() {
// activity last paused more than 10 minutes ago
return mSettings.getLong("last_seen", 0) < Utils.currentTimeInMillis() - 1000*60*10;
}
}
public class Settings extends PreferenceActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
public class SpeakingTimer implements StepListener {
PedometerSettings mSettings;
Utils mUtils;
boolean mShouldSpeak;
float mInterval;
long mLastSpeakTime;
public SpeakingTimer(PedometerSettings settings, Utils utils) {
mLastSpeakTime = System.currentTimeMillis();
mSettings = settings;
mUtils = utils;
reloadSettings();
}
public void reloadSettings() {
mShouldSpeak = mSettings.shouldSpeak();
mInterval = mSettings.getSpeakingInterval();
}
public void onStep() {
long now = System.currentTimeMillis();
long delta = now - mLastSpeakTime;
if (delta / 60000.0 >= mInterval) {
mLastSpeakTime = now;
notifyListeners();
}
}
public void passValue() {
// not used
}
//-----------------------------------------------------
// Listener
public interface Listener {
public void speak();
}
private ArrayList<Listener> mListeners = new ArrayList<Listener>();
public void addListener(Listener l) {
mListeners.add(l);
}
public void notifyListeners() {
mUtils.ding();
for (Listener listener : mListeners) {
listener.speak();
}
}
//-----------------------------------------------------
// Speaking
public boolean isSpeaking() {
return mUtils.isSpeakingNow();
}
}
public class SpeedNotifier implements PaceNotifier.Listener, SpeakingTimer.Listener {
public interface Listener {
public void valueChanged(float value);
public void passValue();
}
private Listener mListener;
int mCounter = 0;
float mSpeed = 0;
boolean mIsMetric;
float mStepLength;
PedometerSettings mSettings;
Utils mUtils;
/** Desired speed, adjusted by the user */
float mDesiredSpeed;
/** Should we speak? */
boolean mShouldTellFasterslower;
boolean mShouldTellSpeed;
/** When did the TTS speak last time */
private long mSpokenAt = 0;
public SpeedNotifier(Listener listener, PedometerSettings settings, Utils utils) {
mListener = listener;
mUtils = utils;
mSettings = settings;
mDesiredSpeed = mSettings.getDesiredSpeed();
reloadSettings();
}
public void setSpeed(float speed) {
mSpeed = speed;
notifyListener();
}
public void reloadSettings() {
mIsMetric = mSettings.isMetric();
mStepLength = mSettings.getStepLength();
mShouldTellSpeed = mSettings.shouldTellSpeed();
mShouldTellFasterslower =
mSettings.shouldTellFasterslower()
&& mSettings.getMaintainOption() == PedometerSettings.M_SPEED;
notifyListener();
}
public void setDesiredSpeed(float desiredSpeed) {
mDesiredSpeed = desiredSpeed;
}
private void notifyListener() {
mListener.valueChanged(mSpeed);
}
public void paceChanged(int value) {
if (mIsMetric) {
mSpeed = // kilometers / hour
value * mStepLength // centimeters / minute
/ 100000f * 60f; // centimeters/kilometer
}
else {
mSpeed = // miles / hour
value * mStepLength // inches / minute
/ 63360f * 60f; // inches/mile
}
tellFasterSlower();
notifyListener();
}
/**
* Say slower/faster, if needed.
*/
private void tellFasterSlower() {
if (mShouldTellFasterslower && mUtils.isSpeakingEnabled()) {
long now = System.currentTimeMillis();
if (now - mSpokenAt > 3000 && !mUtils.isSpeakingNow()) {
float little = 0.10f;
float normal = 0.30f;
float much = 0.50f;
boolean spoken = true;
if (mSpeed < mDesiredSpeed * (1 - much)) {
mUtils.say("much faster!");
}
else
if (mSpeed > mDesiredSpeed * (1 + much)) {
mUtils.say("much slower!");
}
else
if (mSpeed < mDesiredSpeed * (1 - normal)) {
mUtils.say("faster!");
}
else
if (mSpeed > mDesiredSpeed * (1 + normal)) {
mUtils.say("slower!");
}
else
if (mSpeed < mDesiredSpeed * (1 - little)) {
mUtils.say("a little faster!");
}
else
if (mSpeed > mDesiredSpeed * (1 + little)) {
mUtils.say("a little slower!");
}
else {
spoken = false;
}
if (spoken) {
mSpokenAt = now;
}
}
}
}
public void passValue() {
// Not used
}
public void speak() {
if (mSettings.shouldTellSpeed()) {
if (mSpeed >= .01f) {
mUtils.say(("" + (mSpeed + 0.000001f)).substring(0, 4) + (mIsMetric ? " kilometers per hour" : " miles per hour"));
}
}
}
}
public class StepBuzzer implements StepListener {
private Context mContext;
private Vibrator mVibrator;
public StepBuzzer(Context context) {
mContext = context;
mVibrator = (Vibrator)mContext.getSystemService(Context.VIBRATOR_SERVICE);
}
public void onStep() {
buzz();
}
public void passValue() {
}
private void buzz() {
mVibrator.vibrate(50);
}
}
import java.util.ArrayList;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
public class StepDetector implements SensorEventListener
{
private final static String TAG = "StepDetector";
private float mLimit = 10;
private float mLastValues[] = new float[3*2];
private float mScale[] = new float[2];
private float mYOffset;
private float mLastDirections[] = new float[3*2];
private float mLastExtremes[][] = { new float[3*2], new float[3*2] };
private float mLastDiff[] = new float[3*2];
private int mLastMatch = -1;
private ArrayList<StepListener> mStepListeners = new ArrayList<StepListener>();
public StepDetector() {
int h = 480; // TODO: remove this constant
mYOffset = h * 0.5f;
mScale[0] = - (h * 0.5f * (1.0f / (SensorManager.STANDARD_GRAVITY * 2)));
mScale[1] = - (h * 0.5f * (1.0f / (SensorManager.MAGNETIC_FIELD_EARTH_MAX)));
}
public void setSensitivity(float sensitivity) {
mLimit = sensitivity; // 1.97 2.96 4.44 6.66 10.00 15.00 22.50 33.75 50.62
}
public void addStepListener(StepListener sl) {
mStepListeners.add(sl);
}
//public void onSensorChanged(int sensor, float[] values) {
public void onSensorChanged(SensorEvent event) {
Sensor sensor = event.sensor;
synchronized (this) {
if (sensor.getType() == Sensor.TYPE_ORIENTATION) {
}
else {
int j = (sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? 1 : 0;
if (j == 1) {
float vSum = 0;
for (int i=0 ; i<3 ; i++) {
final float v = mYOffset + event.values[i] * mScale[j];
vSum += v;
}
int k = 0;
float v = vSum / 3;
float direction = (v > mLastValues[k] ? 1 : (v < mLastValues[k] ? -1 : 0));
if (direction == - mLastDirections[k]) {
// Direction changed
int extType = (direction > 0 ? 0 : 1); // minumum or maximum?
mLastExtremes[extType][k] = mLastValues[k];
float diff = Math.abs(mLastExtremes[extType][k] - mLastExtremes[1 - extType][k]);
if (diff > mLimit) {
boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k]*2/3);
boolean isPreviousLargeEnough = mLastDiff[k] > (diff/3);
boolean isNotContra = (mLastMatch != 1 - extType);
if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough && isNotContra) {
Log.i(TAG, "step");
for (StepListener stepListener : mStepListeners) {
stepListener.onStep();
}
mLastMatch = extType;
}
else {
mLastMatch = -1;
}
}
mLastDiff[k] = diff;
}
mLastDirections[k] = direction;
mLastValues[k] = v;
}
}
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
}
public class StepDisplayer implements StepListener, SpeakingTimer.Listener {
private int mCount = 0;
PedometerSettings mSettings;
Utils mUtils;
public StepDisplayer(PedometerSettings settings, Utils utils) {
mUtils = utils;
mSettings = settings;
notifyListener();
}
public void setUtils(Utils utils) {
mUtils = utils;
}
public void setSteps(int steps) {
mCount = steps;
notifyListener();
}
public void onStep() {
mCount ++;
notifyListener();
}
public void reloadSettings() {
notifyListener();
}
public void passValue() {
}
//-----------------------------------------------------
// Listener
public interface Listener {
public void stepsChanged(int value);
public void passValue();
}
private ArrayList<Listener> mListeners = new ArrayList<Listener>();
public void addListener(Listener l) {
mListeners.add(l);
}
public void notifyListener() {
for (Listener listener : mListeners) {
listener.stepsChanged((int)mCount);
}
}
//-----------------------------------------------------
// Speaking
public void speak() {
if (mSettings.shouldTellSteps()) {
if (mCount > 0) {
mUtils.say("" + mCount + " steps");
}
}
}
}
public interface StepListener {
public void onStep();
public void passValue();
}
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
/**
* This is an example of implementing an application service that runs locally
* in the same process as the application. The {@link StepServiceController}
* and {@link StepServiceBinding} classes show how to interact with the
* service.
*
* <p>Notice the use of the {@link NotificationManager} when interesting things
* happen in the service. This is generally how background services should
* interact with the user, rather than doing something more disruptive such as
* calling startActivity().
*/
public class StepService extends Service {
private static final String TAG = "name.bagi.levente.pedometer.StepService";
private SharedPreferences mSettings;
private PedometerSettings mPedometerSettings;
private SharedPreferences mState;
private SharedPreferences.Editor mStateEditor;
private Utils mUtils;
private SensorManager mSensorManager;
private Sensor mSensor;
private StepDetector mStepDetector;
// private StepBuzzer mStepBuzzer; // used for debugging
private StepDisplayer mStepDisplayer;
private PaceNotifier mPaceNotifier;
private DistanceNotifier mDistanceNotifier;
private SpeedNotifier mSpeedNotifier;
private CaloriesNotifier mCaloriesNotifier;
private SpeakingTimer mSpeakingTimer;
private PowerManager.WakeLock wakeLock;
private NotificationManager mNM;
private int mSteps;
private int mPace;
private float mDistance;
private float mSpeed;
private float mCalories;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class StepBinder extends Binder {
StepService getService() {
return StepService.this;
}
}
@Override
public void onCreate() {
Log.i(TAG, "[SERVICE] onCreate");
super.onCreate();
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
showNotification();
// Load settings
mSettings = PreferenceManager.getDefaultSharedPreferences(this);
mPedometerSettings = new PedometerSettings(mSettings);
mState = getSharedPreferences("state", 0);
mUtils = Utils.getInstance();
mUtils.setService(this);
mUtils.initTTS();
acquireWakeLock();
// Start detecting
mStepDetector = new StepDetector();
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
registerDetector();
// Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
// code be called whenever the phone enters standby mode.
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
mStepDisplayer = new StepDisplayer(mPedometerSettings, mUtils);
mStepDisplayer.setSteps(mSteps = mState.getInt("steps", 0));
mStepDisplayer.addListener(mStepListener);
mStepDetector.addStepListener(mStepDisplayer);
mPaceNotifier = new PaceNotifier(mPedometerSettings, mUtils);
mPaceNotifier.setPace(mPace = mState.getInt("pace", 0));
mPaceNotifier.addListener(mPaceListener);
mStepDetector.addStepListener(mPaceNotifier);
mDistanceNotifier = new DistanceNotifier(mDistanceListener, mPedometerSettings, mUtils);
mDistanceNotifier.setDistance(mDistance = mState.getFloat("distance", 0));
mStepDetector.addStepListener(mDistanceNotifier);
mSpeedNotifier = new SpeedNotifier(mSpeedListener, mPedometerSettings, mUtils);
mSpeedNotifier.setSpeed(mSpeed = mState.getFloat("speed", 0));
mPaceNotifier.addListener(mSpeedNotifier);
mCaloriesNotifier = new CaloriesNotifier(mCaloriesListener, mPedometerSettings, mUtils);
mCaloriesNotifier.setCalories(mCalories = mState.getFloat("calories", 0));
mStepDetector.addStepListener(mCaloriesNotifier);
mSpeakingTimer = new SpeakingTimer(mPedometerSettings, mUtils);
mSpeakingTimer.addListener(mStepDisplayer);
mSpeakingTimer.addListener(mPaceNotifier);
mSpeakingTimer.addListener(mDistanceNotifier);
mSpeakingTimer.addListener(mSpeedNotifier);
mSpeakingTimer.addListener(mCaloriesNotifier);
mStepDetector.addStepListener(mSpeakingTimer);
// Used when debugging:
// mStepBuzzer = new StepBuzzer(this);
// mStepDetector.addStepListener(mStepBuzzer);
// Start voice
reloadSettings();
// Tell the user we started.
Toast.makeText(this, getText(R.string.started), Toast.LENGTH_SHORT).show();
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(TAG, "[SERVICE] onStart");
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "[SERVICE] onDestroy");
mUtils.shutdownTTS();
// Unregister our receiver.
unregisterReceiver(mReceiver);
unregisterDetector();
mStateEditor = mState.edit();
mStateEditor.putInt("steps", mSteps);
mStateEditor.putInt("pace", mPace);
mStateEditor.putFloat("distance", mDistance);
mStateEditor.putFloat("speed", mSpeed);
mStateEditor.putFloat("calories", mCalories);
mStateEditor.commit();
mNM.cancel(R.string.app_name);
wakeLock.release();
super.onDestroy();
// Stop detecting
mSensorManager.unregisterListener(mStepDetector);
// Tell the user we stopped.
Toast.makeText(this, getText(R.string.stopped), Toast.LENGTH_SHORT).show();
}
private void registerDetector() {
mSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER /*|
Sensor.TYPE_MAGNETIC_FIELD |
Sensor.TYPE_ORIENTATION*/);
mSensorManager.registerListener(mStepDetector,
mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
}
private void unregisterDetector() {
mSensorManager.unregisterListener(mStepDetector);
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "[SERVICE] onBind");
return mBinder;
}
/**
* Receives messages from activity.
*/
private final IBinder mBinder = new StepBinder();
public interface ICallback {
public void stepsChanged(int value);
public void paceChanged(int value);
public void distanceChanged(float value);
public void speedChanged(float value);
public void caloriesChanged(float value);
}
private ICallback mCallback;
public void registerCallback(ICallback cb) {
mCallback = cb;
//mStepDisplayer.passValue();
//mPaceListener.passValue();
}
private int mDesiredPace;
private float mDesiredSpeed;
/**
* Called by activity to pass the desired pace value,
* whenever it is modified by the user.
* @param desiredPace
*/
public void setDesiredPace(int desiredPace) {
mDesiredPace = desiredPace;
if (mPaceNotifier != null) {
mPaceNotifier.setDesiredPace(mDesiredPace);
}
}
/**
* Called by activity to pass the desired speed value,
* whenever it is modified by the user.
* @param desiredSpeed
*/
public void setDesiredSpeed(float desiredSpeed) {
mDesiredSpeed = desiredSpeed;
if (mSpeedNotifier != null) {
mSpeedNotifier.setDesiredSpeed(mDesiredSpeed);
}
}
public void reloadSettings() {
mSettings = PreferenceManager.getDefaultSharedPreferences(this);
if (mStepDetector != null) {
mStepDetector.setSensitivity(
Float.valueOf(mSettings.getString("sensitivity", "10"))
);
}
if (mStepDisplayer != null) mStepDisplayer.reloadSettings();
if (mPaceNotifier != null) mPaceNotifier.reloadSettings();
if (mDistanceNotifier != null) mDistanceNotifier.reloadSettings();
if (mSpeedNotifier != null) mSpeedNotifier.reloadSettings();
if (mCaloriesNotifier != null) mCaloriesNotifier.reloadSettings();
if (mSpeakingTimer != null) mSpeakingTimer.reloadSettings();
}
public void resetValues() {
mStepDisplayer.setSteps(0);
mPaceNotifier.setPace(0);
mDistanceNotifier.setDistance(0);
mSpeedNotifier.setSpeed(0);
mCaloriesNotifier.setCalories(0);
}
/**
* Forwards pace values from PaceNotifier to the activity.
*/
private StepDisplayer.Listener mStepListener = new StepDisplayer.Listener() {
public void stepsChanged(int value) {
mSteps = value;
passValue();
}
public void passValue() {
if (mCallback != null) {
mCallback.stepsChanged(mSteps);
}
}
};
/**
* Forwards pace values from PaceNotifier to the activity.
*/
private PaceNotifier.Listener mPaceListener = new PaceNotifier.Listener() {
public void paceChanged(int value) {
mPace = value;
passValue();
}
public void passValue() {
if (mCallback != null) {
mCallback.paceChanged(mPace);
}
}
};
/**
* Forwards distance values from DistanceNotifier to the activity.
*/
private DistanceNotifier.Listener mDistanceListener = new DistanceNotifier.Listener() {
public void valueChanged(float value) {
mDistance = value;
passValue();
}
public void passValue() {
if (mCallback != null) {
mCallback.distanceChanged(mDistance);
}
}
};
/**
* Forwards speed values from SpeedNotifier to the activity.
*/
private SpeedNotifier.Listener mSpeedListener = new SpeedNotifier.Listener() {
public void valueChanged(float value) {
mSpeed = value;
passValue();
}
public void passValue() {
if (mCallback != null) {
mCallback.speedChanged(mSpeed);
}
}
};
/**
* Forwards calories values from CaloriesNotifier to the activity.
*/
private CaloriesNotifier.Listener mCaloriesListener = new CaloriesNotifier.Listener() {
public void valueChanged(float value) {
mCalories = value;
passValue();
}
public void passValue() {
if (mCallback != null) {
mCallback.caloriesChanged(mCalories);
}
}
};
/**
* Show a notification while this service is running.
*/
private void showNotification() {
CharSequence text = getText(R.string.app_name);
Notification notification = new Notification(R.drawable.ic_notification, null,
System.currentTimeMillis());
notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
Intent pedometerIntent = new Intent();
pedometerIntent.setComponent(new ComponentName(this, Pedometer.class));
pedometerIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
pedometerIntent, 0);
notification.setLatestEventInfo(this, text,
getText(R.string.notification_subtitle), contentIntent);
mNM.notify(R.string.app_name, notification);
}
// BroadcastReceiver for handling ACTION_SCREEN_OFF.
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Check action just to be on the safe side.
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
// Unregisters the listener and registers it again.
StepService.this.unregisterDetector();
StepService.this.registerDetector();
if (mPedometerSettings.wakeAggressively()) {
wakeLock.release();
acquireWakeLock();
}
}
}
};
private void acquireWakeLock() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
int wakeFlags;
if (mPedometerSettings.wakeAggressively()) {
wakeFlags = PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
}
else if (mPedometerSettings.keepScreenOn()) {
wakeFlags = PowerManager.SCREEN_DIM_WAKE_LOCK;
}
else {
wakeFlags = PowerManager.PARTIAL_WAKE_LOCK;
}
wakeLock = pm.newWakeLock(wakeFlags, TAG);
wakeLock.acquire();
}
}
import java.util.Locale;
import android.app.Service;
import android.speech.tts.TextToSpeech;
import android.text.format.Time;
import android.util.Log;
public class Utils implements TextToSpeech.OnInitListener {
private static final String TAG = "Utils";
private Service mService;
private static Utils instance = null;
private Utils() {
}
public static Utils getInstance() {
if (instance == null) {
instance = new Utils();
}
return instance;
}
public void setService(Service service) {
mService = service;
}
/********** SPEAKING **********/
private TextToSpeech mTts;
private boolean mSpeak = false;
private boolean mSpeakingEngineAvailable = false;
public void initTTS() {
// Initialize text-to-speech. This is an asynchronous operation.
// The OnInitListener (second argument) is called after initialization completes.
Log.i(TAG, "Initializing TextToSpeech...");
mTts = new TextToSpeech(mService,
this // TextToSpeech.OnInitListener
);
}
public void shutdownTTS() {
Log.i(TAG, "Shutting Down TextToSpeech...");
mSpeakingEngineAvailable = false;
mTts.shutdown();
Log.i(TAG, "TextToSpeech Shut Down.");
}
public void say(String text) {
if (mSpeak && mSpeakingEngineAvailable) {
mTts.speak(text,
TextToSpeech.QUEUE_ADD, // Drop all pending entries in the playback queue.
null);
}
}
// Implements TextToSpeech.OnInitListener.
public void onInit(int status) {
// status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
if (status == TextToSpeech.SUCCESS) {
int result = mTts.setLanguage(Locale.US);
if (result == TextToSpeech.LANG_MISSING_DATA ||
result == TextToSpeech.LANG_NOT_SUPPORTED) {
// Language data is missing or the language is not supported.
Log.e(TAG, "Language is not available.");
} else {
Log.i(TAG, "TextToSpeech Initialized.");
mSpeakingEngineAvailable = true;
}
} else {
// Initialization failed.
Log.e(TAG, "Could not initialize TextToSpeech.");
}
}
public void setSpeak(boolean speak) {
mSpeak = speak;
}
public boolean isSpeakingEnabled() {
return mSpeak;
}
public boolean isSpeakingNow() {
return mTts.isSpeaking();
}
public void ding() {
}
/********** Time **********/
public static long currentTimeInMillis() {
Time time = new Time();
time.setToNow();
return time.toMillis(false);
}
}
public class BodyWeightPreference extends EditMeasurementPreference {
public BodyWeightPreference(Context context) {
super(context);
}
public BodyWeightPreference(Context context, AttributeSet attr) {
super(context, attr);
}
public BodyWeightPreference(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
}
protected void initPreferenceDetails() {
mTitleResource = R.string.body_weight_setting_title;
mMetricUnitsResource = R.string.kilograms;
mImperialUnitsResource = R.string.pounds;
}
}
abstract public class EditMeasurementPreference extends EditTextPreference {
boolean mIsMetric;
protected int mTitleResource;
protected int mMetricUnitsResource;
protected int mImperialUnitsResource;
public EditMeasurementPreference(Context context) {
super(context);
initPreferenceDetails();
}
public EditMeasurementPreference(Context context, AttributeSet attr) {
super(context, attr);
initPreferenceDetails();
}
public EditMeasurementPreference(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
initPreferenceDetails();
}
abstract protected void initPreferenceDetails();
protected void showDialog(Bundle state) {
mIsMetric = PreferenceManager.getDefaultSharedPreferences(getContext()).getString("units", "imperial").equals("metric");
setDialogTitle(
getContext().getString(mTitleResource) +
" (" +
getContext().getString(
mIsMetric
? mMetricUnitsResource
: mImperialUnitsResource) +
")"
);
try {
Float.valueOf(getText());
}
catch (Exception e) {
setText("20");
}
super.showDialog(state);
}
protected void onAddEditTextToDialogView (View dialogView, EditText editText) {
editText.setRawInputType(
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
super.onAddEditTextToDialogView(dialogView, editText);
}
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
try {
Float.valueOf(((CharSequence)(getEditText().getText())).toString());
}
catch (NumberFormatException e) {
this.showDialog(null);
return;
}
}
super.onDialogClosed(positiveResult);
}
}
public class StepLengthPreference extends EditMeasurementPreference {
public StepLengthPreference(Context context) {
super(context);
}
public StepLengthPreference(Context context, AttributeSet attr) {
super(context, attr);
}
public StepLengthPreference(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
}
protected void initPreferenceDetails() {
mTitleResource = R.string.step_length_setting_title;
mMetricUnitsResource = R.string.centimeters;
mImperialUnitsResource = R.string.inches;
}
}
<?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"
android:padding="@dimen/margin"
android:background="@color/screen_background">
<LinearLayout android:id="@+id/row_1"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/row_spacing">
<LinearLayout android:id="@+id/box_steps"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingRight="@dimen/margin"
android:layout_weight="1">
<TextView android:id="@+id/step_value"
android:textSize="@dimen/value"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@color/display_background"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="@dimen/padding"
android:text=""/>
<TextView android:id="@+id/step_units"
android:gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/units"
android:text="@string/steps"
android:background="@color/display_background"
android:paddingBottom="@dimen/padding"/>
</LinearLayout>
<LinearLayout android:id="@+id/box_distance"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_weight="1">
<TextView android:id="@+id/distance_value"
android:textSize="@dimen/value"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@color/display_background"
android:paddingTop="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingLeft="@dimen/padding"
android:text=""/>
<TextView android:id="@+id/distance_units"
android:gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/units"
android:text="@string/kilometers"
android:background="@color/display_background"
android:paddingBottom="@dimen/padding"/>
</LinearLayout>
</LinearLayout>
<LinearLayout android:id="@+id/row_2"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/row_spacing">
<LinearLayout android:id="@+id/box_pace"
android:orientation="vertical"
android:layout_height="wrap_content"
android:paddingRight="@dimen/margin"
android:layout_width="fill_parent"
android:layout_weight="1">
<TextView android:id="@+id/pace_value"
android:gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/display_background"
android:textSize="@dimen/small_value"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="@dimen/padding"
android:text=""/>
<TextView an