/*
* Copyright (C) 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.wearable.jumpingjack;
import com.example.android.wearable.jumpingjack.fragments.CounterFragment;
import com.example.android.wearable.jumpingjack.fragments.SettingsFragment;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.WindowManager;
import android.widget.ImageView;
import java.util.Timer;
import java.util.TimerTask;
/**
* The main activity for the Jumping Jack application. This activity registers itself to receive
* sensor values. Since on wearable devices a full screen activity is very short-lived, we set the
* FLAG_KEEP_SCREEN_ON to give user adequate time for taking actions but since we don't want to
* keep screen on for an extended period of time, there is a SCREEN_ON_TIMEOUT_MS that is enforced
* if no interaction is discovered.
*
* This activity includes a {@link android.support.v4.view.ViewPager} with two pages, one that
* shows the current count and one that allows user to reset the counter. the current value of the
* counter is persisted so that upon re-launch, the counter picks up from the last value. At any
* stage, user can set this counter to 0.
*/
public class MainActivity extends Activity
implements SensorEventListener {
private static final String TAG = "JJMainActivity";
/** How long to keep the screen on when no activity is happening **/
private static final long SCREEN_ON_TIMEOUT_MS = 20000; // in milliseconds
/** an up-down movement that takes more than this will not be registered as such **/
private static final long TIME_THRESHOLD_NS = 2000000000; // in nanoseconds (= 2sec)
/**
* Earth gravity is around 9.8 m/s^2 but user may not completely direct his/her hand vertical
* during the exercise so we leave some room. Basically if the x-component of gravity, as
* measured by the Gravity sensor, changes with a variation (delta) > GRAVITY_THRESHOLD,
* we consider that a successful count.
*/
private static final float GRAVITY_THRESHOLD = 7.0f;
private SensorManager mSensorManager;
private Sensor mSensor;
private long mLastTime = 0;
private boolean mUp = false;
private int mJumpCounter = 0;
private ViewPager mPager;
private CounterFragment mCounterPage;
private SettingsFragment mSettingPage;
private ImageView mSecondIndicator;
private ImageView mFirstIndicator;
private Timer mTimer;
private TimerTask mTimerTask;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.jj_layout);
setupViews();
mHandler = new Handler();
mJumpCounter = Utils.getCounterFromPreference(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
renewTimer();
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
}
private void setupViews() {
mPager = (ViewPager) findViewById(R.id.pager);
mFirstIndicator = (ImageView) findViewById(R.id.indicator_0);
mSecondIndicator = (ImageView) findViewById(R.id.indicator_1);
final PagerAdapter adapter = new PagerAdapter(getFragmentManager());
mCounterPage = new CounterFragment();
mSettingPage = new SettingsFragment();
adapter.addFragment(mCounterPage);
adapter.addFragment(mSettingPage);
setIndicator(0);
mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i2) {
}
@Override
public void onPageSelected(int i) {
setIndicator(i);
renewTimer();
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
mPager.setAdapter(adapter);
}
@Override
protected void onResume() {
super.onResume();
if (mSensorManager.registerListener(this, mSensor,
SensorManager.SENSOR_DELAY_NORMAL)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Successfully registered for the sensor updates");
}
}
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unregistered for sensor events");
}
}
@Override
public void onSensorChanged(SensorEvent event) {
detectJump(event.values[0], event.timestamp);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* A simple algorithm to detect a successful up-down movement of hand(s). The algorithm is
* based on the assumption that when a person is wearing the watch, the x-component of gravity
* as measured by the Gravity Sensor is +9.8 when the hand is downward and -9.8 when the hand
* is upward (signs are reversed if the watch is worn on the right hand). Since the upward or
* downward may not be completely accurate, we leave some room and instead of 9.8, we use
* GRAVITY_THRESHOLD. We also consider the up <-> down movement successful if it takes less than
* TIME_THRESHOLD_NS.
*/
private void detectJump(float xValue, long timestamp) {
if ((Math.abs(xValue) > GRAVITY_THRESHOLD)) {
if(timestamp - mLastTime < TIME_THRESHOLD_NS && mUp != (xValue > 0)) {
onJumpDetected(!mUp);
}
mUp = xValue > 0;
mLastTime = timestamp;
}
}
/**
* Called on detection of a successful down -> up or up -> down movement of hand.
*/
private void onJumpDetected(boolean up) {
// we only count a pair of up and down as one successful movement
if (up) {
return;
}
mJumpCounter++;
setCounter(mJumpCounter);
renewTimer();
}
/**
* Updates the counter on UI, saves it to preferences and vibrates the watch when counter
* reaches a multiple of 10.
*/
private void setCounter(int i) {
mCounterPage.setCounter(i);
Utils.saveCounterToPreference(this, i);
if (i > 0 && i % 10 == 0) {
Utils.vibrate(this, 0);
}
}
public void resetCounter() {
setCounter(0);
renewTimer();
}
/**
* Starts a timer to clear the flag FLAG_KEEP_SCREEN_ON.
*/
private void renewTimer() {
if (null != mTimer) {
mTimer.cancel();
}
mTimerTask = new TimerTask() {
@Override
public void run() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
"Removing the FLAG_KEEP_SCREEN_ON flag to allow going to background");
}
resetFlag();
}
};
mTimer = new Timer();
mTimer.schedule(mTimerTask, SCREEN_ON_TIMEOUT_MS);
}
/**
* Resets the FLAG_KEEP_SCREEN_ON flag so activity can go into background.
*/
private void resetFlag() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Resetting FLAG_KEEP_SCREEN_ON flag to allow going to background");
}
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
finish();
}
});
}
/**
* Sets the page indicator for the ViewPager.
*/
private void setIndicator(int i) {
switch (i) {
case 0:
mFirstIndicator.setImageResource(R.drawable.full_10);
mSecondIndicator.setImageResource(R.drawable.empty_10);
break;
case 1:
mFirstIndicator.setImageResource(R.drawable.empty_10);
mSecondIndicator.setImageResource(R.drawable.full_10);
break;
}
}
}