/*
* Copyright (C) 2012 - 2013 jonas.oreland@gmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.runnerup.view;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.runnerup.R;
import org.runnerup.BuildConfig;
import org.runnerup.tracker.Tracker;
import org.runnerup.tracker.component.TrackerHRM;
import org.runnerup.util.Formatter;
import org.runnerup.util.HRZones;
import org.runnerup.util.TickListener;
import org.runnerup.widget.WidgetUtil;
import org.runnerup.workout.Intensity;
import org.runnerup.workout.Scope;
import org.runnerup.workout.Step;
import org.runnerup.workout.Workout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
@TargetApi(Build.VERSION_CODES.FROYO)
public class RunActivity extends Activity implements TickListener {
Workout workout = null;
Tracker mTracker = null;
final Handler handler = new Handler();
Button pauseButton = null;
Button stopButton = null;
Button newLapButton = null;
TextView activityTime = null;
TextView activityDistance = null;
TextView activityPace = null;
TextView lapTime = null;
TextView lapDistance = null;
TextView lapPace = null;
TextView intervalTime = null;
TextView intervalDistance = null;
TextView intervalPace = null;
TextView countdownView = null;
ListView workoutList = null;
View tableRowInterval = null;
org.runnerup.workout.Step currentStep = null;
Formatter formatter = null;
private TextView activityHr;
private TextView lapHr;
private TextView intervalHr;
private TextView activityHeaderHr;
class WorkoutRow {
org.runnerup.workout.Step step = null;
ContentValues lap = null;
public int level;
}
final ArrayList<WorkoutRow> workoutRows = new ArrayList<WorkoutRow>();
final ArrayList<BaseAdapter> adapters = new ArrayList<BaseAdapter>(2);
boolean simpleWorkout;
HRZones hrZones = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.run);
formatter = new Formatter(this);
hrZones = new HRZones(this);
stopButton = (Button) findViewById(R.id.stop_button);
stopButton.setOnClickListener(stopButtonClick);
pauseButton = (Button) findViewById(R.id.pause_button);
pauseButton.setOnClickListener(pauseButtonClick);
newLapButton = (Button) findViewById(R.id.new_lap_button);
activityHeaderHr = (TextView) findViewById(R.id.activity_header_hr);
activityTime = (TextView) findViewById(R.id.activity_time);
activityDistance = (TextView) findViewById(R.id.activity_distance);
activityPace = (TextView) findViewById(R.id.activity_pace);
activityHr = (TextView) findViewById(R.id.activity_hr);
lapTime = (TextView) findViewById(R.id.lap_time);
lapDistance = (TextView) findViewById(R.id.lap_distance);
lapPace = (TextView) findViewById(R.id.lap_pace);
lapHr = (TextView) findViewById(R.id.lap_hr);
intervalTime = (TextView) findViewById(R.id.interval_time);
intervalDistance = (TextView) findViewById(R.id.intervall_distance);
tableRowInterval = findViewById(R.id.table_row_interval);
intervalPace = (TextView) findViewById(R.id.interval_pace);
intervalHr = (TextView) findViewById(R.id.interval_hr);
countdownView = (TextView) findViewById(R.id.countdown_text_view);
workoutList = (ListView) findViewById(R.id.workout_list);
WorkoutAdapter adapter = new WorkoutAdapter(workoutRows);
workoutList.setAdapter(adapter);
bindGpsTracker();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e(getClass().getName(), "onConfigurationChange => do NOTHING!!");
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroy() {
super.onDestroy();
unbindGpsTracker();
stopTimer();
}
void onGpsTrackerBound() {
if (mTracker == null) {
// should not happen
return;
}
if (mTracker.getWorkout() == null) {
// should not happen
return;
}
workout = mTracker.getWorkout();
{
/**
* Countdown view can't be bound until RunActivity is started
* since it's not created until then
*/
HashMap<String, Object> bindValues = new HashMap<String, Object>();
bindValues.put(Workout.KEY_COUNTER_VIEW, countdownView);
workout.onBind(workout, bindValues);
}
startTimer();
populateWorkoutList();
simpleWorkout = false;
if (workoutRows.size() == 1 ||
workoutRows.size() == 2
&& workoutRows.get(0).step.getIntensity() == Intensity.RESTING) {
simpleWorkout = true;
}
if (simpleWorkout) {
newLapButton.setOnClickListener(newLapButtonClick);
newLapButton.setText(getString(R.string.New_lap));
} else {
newLapButton.setOnClickListener(nextStepButtonClick);
newLapButton.setText(getString(R.string.Next_lap));
}
}
private void populateWorkoutList() {
List<Workout.StepListEntry> list = workout.getStepList();
for (Workout.StepListEntry aList : list) {
WorkoutRow row = new WorkoutRow();
row.level = aList.level;
row.step = aList.step;
row.lap = null;
workoutRows.add(row);
}
for (BaseAdapter a : adapters) {
a.notifyDataSetChanged();
}
}
Timer timer = null;
void startTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
RunActivity.this.handler.post(new Runnable() {
public void run() {
RunActivity.this.onTick();
}
});
}
}, 0, 500);
}
void stopTimer() {
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
}
}
Location l = null;
public void onTick() {
if (workout != null) {
workout.onTick();
updateView();
if (mTracker != null) {
Location l2 = mTracker.getLastKnownLocation();
if (l2 != null && !l2.equals(l)) {
l = l2;
}
}
}
}
final OnClickListener stopButtonClick = new OnClickListener() {
public void onClick(View v) {
if (timer != null) {
workout.onStop(workout);
stopTimer(); // set timer=null;
mTracker.stopForeground(true); // remove notification
Intent intent = new Intent(RunActivity.this, DetailActivity.class);
/**
* The same activity is used to show details and to save
* activity they show almost the same information
*/
intent.putExtra("mode", "save");
intent.putExtra("ID", mTracker.getActivityId());
RunActivity.this.startActivityForResult(intent, workout.isPaused() ? 1 : 0);
}
}
};
@Override
public void onBackPressed() {
boolean ignore_back = true; // atleast magnus belives that this is
// better...
if (ignore_back == false) {
stopButtonClick.onClick(stopButton);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (workout == null) {
// "should not happen"
finish();
return;
}
if (resultCode == Activity.RESULT_OK) {
/**
* they saved
*/
workout.onComplete(Scope.ACTIVITY, workout);
workout.onSave();
mTracker = null;
finish();
return;
} else if (resultCode == Activity.RESULT_CANCELED) {
/**
* they discarded
*/
workout.onComplete(Scope.ACTIVITY, workout);
workout.onDiscard();
mTracker = null;
finish();
return;
} else if (resultCode == Activity.RESULT_FIRST_USER) {
startTimer();
if (requestCode == 0) {
workout.onResume(workout);
} else {
// we were paused before stopButtonClick...don't resume
}
} else {
if (BuildConfig.DEBUG) { throw new AssertionError(); }
}
}
final OnClickListener pauseButtonClick = new OnClickListener() {
public void onClick(View v) {
if (workout.isPaused()) {
workout.onResume(workout);
} else {
workout.onPause(workout);
}
setPauseButtonEnabled(!workout.isPaused());
}
};
private void setPauseButtonEnabled(boolean enabled) {
if (enabled) {
pauseButton.setText(getString(R.string.Pause));
WidgetUtil.setBackground(pauseButton, getResources().getDrawable(R.drawable.btn_blue));
pauseButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_av_pause, 0);
} else {
pauseButton.setText(getString(R.string.Resume));
WidgetUtil.setBackground(pauseButton, getResources().getDrawable(R.drawable.btn_green));
pauseButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_av_play_arrow, 0);
}
}
final OnClickListener newLapButtonClick = new OnClickListener() {
public void onClick(View v) {
workout.onNewLap();
}
};
final OnClickListener nextStepButtonClick = new OnClickListener() {
public void onClick(View v) {
workout.onNextStep();
}
};
private void updateView() {
setPauseButtonEnabled(!workout.isPaused());
double ad = workout.getDistance(Scope.ACTIVITY);
double at = workout.getTime(Scope.ACTIVITY);
double ap = workout.getPace(Scope.ACTIVITY);
activityTime.setText(formatter.formatElapsedTime(Formatter.Format.TXT_LONG, Math.round(at)));
activityDistance.setText(formatter.formatDistance(Formatter.Format.TXT_SHORT, Math.round(ad)));
activityPace.setText(formatter.formatPace(Formatter.Format.TXT_SHORT, ap));
double ahr = workout.getHeartRate(Scope.ACTIVITY);
double ld = workout.getDistance(Scope.LAP);
double lt = workout.getTime(Scope.LAP);
double lp = workout.getPace(Scope.LAP);
lapTime.setText(formatter.formatElapsedTime(Formatter.Format.TXT_LONG, Math.round(lt)));
lapDistance.setText(formatter.formatDistance(Formatter.Format.TXT_LONG, Math.round(ld)));
lapPace.setText(formatter.formatPace(Formatter.Format.TXT_SHORT, lp));
double lhr = workout.getHeartRate(Scope.LAP);
double id = workout.getDistance(Scope.STEP);
double it = workout.getTime(Scope.STEP);
double ip = workout.getPace(Scope.STEP);
if (tableRowInterval != null && this.currentStep != null && !simpleWorkout
&& this.currentStep.getIntensity() == Intensity.ACTIVE)
tableRowInterval.setVisibility(View.VISIBLE);
else
tableRowInterval.setVisibility(View.GONE);
intervalTime.setText(formatter.formatElapsedTime(Formatter.Format.TXT_LONG, Math.round(it)));
intervalDistance.setText(formatter.formatDistance(Formatter.Format.TXT_LONG, Math.round(id)));
intervalPace.setText(formatter.formatPace(Formatter.Format.TXT_SHORT, ip));
double ihr = workout.getHeartRate(Scope.STEP);
if (mTracker.isComponentConnected(TrackerHRM.NAME)) {
lapHr.setText(formatter.formatHeartRate(Formatter.Format.TXT_SHORT, lhr));
intervalHr.setText(formatter.formatHeartRate(Formatter.Format.TXT_SHORT, ihr));
activityHr.setText(formatter.formatHeartRate(Formatter.Format.TXT_SHORT, ahr));
activityHr.setVisibility(View.VISIBLE);
lapHr.setVisibility(View.VISIBLE);
intervalHr.setVisibility(View.VISIBLE);
activityHeaderHr.setVisibility(View.VISIBLE);
} else {
activityHr.setVisibility(View.GONE);
lapHr.setVisibility(View.GONE);
intervalHr.setVisibility(View.GONE);
activityHeaderHr.setVisibility(View.GONE);
}
Step curr = workout.getCurrentStep();
if (curr != currentStep) {
((WorkoutAdapter) workoutList.getAdapter()).notifyDataSetChanged();
currentStep = curr;
workoutList.setSelection(getPosition(workoutRows, currentStep));
if (!simpleWorkout && workout.isLastStep())
{
newLapButton.setEnabled(false);
}
}
}
private int getPosition(ArrayList<WorkoutRow> workoutRows,
org.runnerup.workout.Step currentActivity) {
for (int i = 0; i < workoutRows.size(); i++) {
if (workoutRows.get(i).step == currentActivity)
return i;
}
return 0;
}
private boolean mIsBound = false;
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
if (mTracker == null) {
mTracker = ((Tracker.LocalBinder) service).getService();
// Tell the user about this for our demo.
RunActivity.this.onGpsTrackerBound();
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mIsBound = false;
mTracker = null;
}
};
void bindGpsTracker() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
getApplicationContext().bindService(new Intent(this, Tracker.class),
mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void unbindGpsTracker() {
if (mIsBound) {
// Detach our existing connection.
getApplicationContext().unbindService(mConnection);
mIsBound = false;
}
}
class WorkoutAdapter extends BaseAdapter {
ArrayList<WorkoutRow> rows = null;
WorkoutAdapter(ArrayList<WorkoutRow> workoutRows) {
this.rows = workoutRows;
}
@Override
public int getCount() {
return rows.size();
}
@Override
public Object getItem(int position) {
return rows.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
WorkoutRow tmp = rows.get(position);
if (tmp.step != null)
{
return getWorkoutRow(tmp.step, tmp.level, convertView, parent);
}
else
{
return getLapRow(tmp.lap, convertView, parent);
}
}
private View getWorkoutRow(org.runnerup.workout.Step step, int level, View convertView,
ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(RunActivity.this);
View view = inflater.inflate(R.layout.workout_row, parent, false);
TextView intensity = (TextView) view.findViewById(R.id.step_intensity);
TextView durationType = (TextView) view.findViewById(R.id.step_duration_type);
TextView durationValue = (TextView) view.findViewById(R.id.step_duration_value);
TextView targetPace = (TextView) view.findViewById(R.id.step_pace);
intensity.setPadding(level * 10, 0, 0, 0);
intensity.setText(getResources().getText(step.getIntensity().getTextId()));
if (step.getDurationType() != null) {
durationType.setText(getResources().getText(step.getDurationType().getTextId()));
durationValue.setText(formatter.format(Formatter.Format.TXT_LONG, step.getDurationType(),
step.getDurationValue()));
} else {
durationType.setText("");
durationValue.setText("");
}
if (currentStep == step) {
view.setBackgroundResource(android.R.color.background_light);
} else {
view.setBackgroundResource(android.R.color.black);
}
if (step.getTargetType() == null) {
targetPace.setText("");
} else {
double minValue = step.getTargetValue().minValue;
double maxValue = step.getTargetValue().maxValue;
if (minValue == maxValue) {
targetPace.setText(formatter.format(Formatter.Format.TXT_SHORT, step.getTargetType(),
minValue));
} else {
targetPace.setText(formatter.format(Formatter.Format.TXT_SHORT, step.getTargetType(),
minValue) + "-" +
formatter.format(Formatter.Format.TXT_SHORT, step.getTargetType(), maxValue));
}
}
if (step.getRepeatCount() > 0) {
if (step.getCurrentRepeat() == step.getRepeatCount()) {
durationValue.setText(getString(R.string.Finished));
} else {
durationValue.setText("" + (step.getCurrentRepeat() + 1) + "/"
+ step.getRepeatCount());
}
}
return view;
}
private View getLapRow(ContentValues tmp, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(RunActivity.this);
View view = inflater.inflate(R.layout.laplist_row, parent, false);
return view;
}
@Override
public boolean hasStableIds() {
return false;
}
}
}