blob: f344cfeefbfdeefb6afea9b0cb0853030f38c84a [file] [log] [blame]
/*
* 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 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.util.Log;
import android.widget.ImageView;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager.widget.ViewPager;
import androidx.wear.ambient.AmbientModeSupport;
import com.example.android.wearable.jumpingjack.fragments.CounterFragment;
import com.example.android.wearable.jumpingjack.fragments.SettingsFragment;
import java.util.concurrent.TimeUnit;
/**
* The main activity for the Jumping Jack application. This activity registers itself to receive
* sensor values.
*
* This activity includes a {@link 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 FragmentActivity
implements AmbientModeSupport.AmbientCallbackProvider, SensorEventListener {
private static final String TAG = "MainActivity";
// An up-down movement that takes more than 2 seconds will not be registered (in nanoseconds).
private static final long TIME_THRESHOLD_NS = TimeUnit.SECONDS.toNanos(2);
/**
* 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 > 0.03 from the hand down
* and hand up threshold we define below, we consider that a successful count.
*
* This is a very rudimentary formula and is by no means production accurate. You will want to
* take into account Y and Z gravity changes to get a truly accurate jumping jack.
*
* This sample is just meant to show how to easily get sensor values and use them.
*/
private static final float HAND_DOWN_GRAVITY_X_THRESHOLD = -.040f;
private static final float HAND_UP_GRAVITY_X_THRESHOLD = -.010f;
private SensorManager mSensorManager;
private Sensor mSensor;
private long mLastTime = 0;
private int mJumpCounter = 0;
private boolean mHandDown = true;
private ViewPager mPager;
private CounterFragment mCounterPage;
private SettingsFragment mSettingPage;
private ImageView mSecondIndicator;
private ImageView mFirstIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.jumping_jack_layout);
AmbientModeSupport.attach(this);
setupViews();
mJumpCounter = Utils.getCounterFromPreference(this);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
}
private void setupViews() {
mPager = findViewById(R.id.pager);
mFirstIndicator = findViewById(R.id.indicator_0);
mSecondIndicator = 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);
}
@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) {
// No op.
}
/**
* A very simple algorithm to detect a successful up-down movement of hand(s). The algorithm
* is based on a delta of the handing being up vs. down and taking less than TIME_THRESHOLD_NS
* to happen.
*
*
* This algorithm isn't intended to be used in production but just to show what's possible with
* sensors. You will want to take into account other components (y and z) and other sensors to
* get a more accurate reading.
*/
private void detectJump(float xGravity, long timestamp) {
if ((xGravity <= HAND_DOWN_GRAVITY_X_THRESHOLD)
|| (xGravity >= HAND_UP_GRAVITY_X_THRESHOLD)) {
if (timestamp - mLastTime < TIME_THRESHOLD_NS) {
// Hand is down when yValue is negative.
onJumpDetected(xGravity <= HAND_DOWN_GRAVITY_X_THRESHOLD);
}
mLastTime = timestamp;
}
}
/**
* Called on detection of a successful down -> up or up -> down movement of hand.
*/
private void onJumpDetected(boolean handDown) {
if (mHandDown != handDown) {
mHandDown = handDown;
// Only count when the hand is down (means the hand has gone up, then down).
if (mHandDown) {
mJumpCounter++;
setCounter(mJumpCounter);
}
}
}
/**
* 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) {
mJumpCounter = i;
mCounterPage.setCounter(i);
Utils.saveCounterToPreference(this, i);
if (i > 0 && i % 10 == 0) {
Utils.vibrate(this, 0);
}
}
public void resetCounter() {
setCounter(0);
}
/**
* 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;
}
}
@Override
public AmbientModeSupport.AmbientCallback getAmbientCallback() {
return new MyAmbientCallback();
}
/** Customizes appearance for Ambient mode. (We don't do anything minus default.) */
private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
/** Prepares the UI for ambient mode. */
@Override
public void onEnterAmbient(Bundle ambientDetails) {
super.onEnterAmbient(ambientDetails);
}
/**
* Updates the display in ambient mode on the standard interval. Since we're using a custom
* refresh cycle, this method does NOT update the data in the display. Rather, this method
* simply updates the positioning of the data in the screen to avoid burn-in, if the display
* requires it.
*/
@Override
public void onUpdateAmbient() {
super.onUpdateAmbient();
}
/** Restores the UI to active (non-ambient) mode. */
@Override
public void onExitAmbient() {
super.onExitAmbient();
}
}
}