1 /* 2 * Copyright (C) 2014 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.wearable.jumpingjack; 18 19 import com.example.android.wearable.jumpingjack.fragments.CounterFragment; 20 import com.example.android.wearable.jumpingjack.fragments.SettingsFragment; 21 22 import android.app.Activity; 23 import android.content.Context; 24 import android.hardware.Sensor; 25 import android.hardware.SensorEvent; 26 import android.hardware.SensorEventListener; 27 import android.hardware.SensorManager; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.support.v4.view.ViewPager; 31 import android.util.Log; 32 import android.view.WindowManager; 33 import android.widget.ImageView; 34 35 import java.util.Timer; 36 import java.util.TimerTask; 37 38 /** 39 * The main activity for the Jumping Jack application. This activity registers itself to receive 40 * sensor values. Since on wearable devices a full screen activity is very short-lived, we set the 41 * FLAG_KEEP_SCREEN_ON to give user adequate time for taking actions but since we don't want to 42 * keep screen on for an extended period of time, there is a SCREEN_ON_TIMEOUT_MS that is enforced 43 * if no interaction is discovered. 44 * 45 * This activity includes a {@link android.support.v4.view.ViewPager} with two pages, one that 46 * shows the current count and one that allows user to reset the counter. the current value of the 47 * counter is persisted so that upon re-launch, the counter picks up from the last value. At any 48 * stage, user can set this counter to 0. 49 */ 50 public class MainActivity extends Activity 51 implements SensorEventListener { 52 53 private static final String TAG = "JJMainActivity"; 54 55 /** How long to keep the screen on when no activity is happening **/ 56 private static final long SCREEN_ON_TIMEOUT_MS = 20000; // in milliseconds 57 58 /** an up-down movement that takes more than this will not be registered as such **/ 59 private static final long TIME_THRESHOLD_NS = 2000000000; // in nanoseconds (= 2sec) 60 61 /** 62 * Earth gravity is around 9.8 m/s^2 but user may not completely direct his/her hand vertical 63 * during the exercise so we leave some room. Basically if the x-component of gravity, as 64 * measured by the Gravity sensor, changes with a variation (delta) > GRAVITY_THRESHOLD, 65 * we consider that a successful count. 66 */ 67 private static final float GRAVITY_THRESHOLD = 7.0f; 68 69 private SensorManager mSensorManager; 70 private Sensor mSensor; 71 private long mLastTime = 0; 72 private boolean mUp = false; 73 private int mJumpCounter = 0; 74 private ViewPager mPager; 75 private CounterFragment mCounterPage; 76 private SettingsFragment mSettingPage; 77 private ImageView mSecondIndicator; 78 private ImageView mFirstIndicator; 79 private Timer mTimer; 80 private TimerTask mTimerTask; 81 private Handler mHandler; 82 83 @Override 84 protected void onCreate(Bundle savedInstanceState) { 85 super.onCreate(savedInstanceState); 86 setContentView(R.layout.jj_layout); 87 setupViews(); 88 mHandler = new Handler(); 89 mJumpCounter = Utils.getCounterFromPreference(this); 90 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 91 renewTimer(); 92 mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 93 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); 94 } 95 96 private void setupViews() { 97 mPager = (ViewPager) findViewById(R.id.pager); 98 mFirstIndicator = (ImageView) findViewById(R.id.indicator_0); 99 mSecondIndicator = (ImageView) findViewById(R.id.indicator_1); 100 final PagerAdapter adapter = new PagerAdapter(getFragmentManager()); 101 mCounterPage = new CounterFragment(); 102 mSettingPage = new SettingsFragment(); 103 adapter.addFragment(mCounterPage); 104 adapter.addFragment(mSettingPage); 105 setIndicator(0); 106 mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 107 @Override 108 public void onPageScrolled(int i, float v, int i2) { 109 } 110 111 @Override 112 public void onPageSelected(int i) { 113 setIndicator(i); 114 renewTimer(); 115 } 116 117 @Override 118 public void onPageScrollStateChanged(int i) { 119 } 120 }); 121 122 mPager.setAdapter(adapter); 123 } 124 125 @Override 126 protected void onResume() { 127 super.onResume(); 128 if (mSensorManager.registerListener(this, mSensor, 129 SensorManager.SENSOR_DELAY_NORMAL)) { 130 if (Log.isLoggable(TAG, Log.DEBUG)) { 131 Log.d(TAG, "Successfully registered for the sensor updates"); 132 } 133 } 134 } 135 136 @Override 137 protected void onPause() { 138 super.onPause(); 139 mSensorManager.unregisterListener(this); 140 if (Log.isLoggable(TAG, Log.DEBUG)) { 141 Log.d(TAG, "Unregistered for sensor events"); 142 } 143 } 144 145 @Override 146 public void onSensorChanged(SensorEvent event) { 147 detectJump(event.values[0], event.timestamp); 148 } 149 150 @Override 151 public void onAccuracyChanged(Sensor sensor, int accuracy) { 152 } 153 154 /** 155 * A simple algorithm to detect a successful up-down movement of hand(s). The algorithm is 156 * based on the assumption that when a person is wearing the watch, the x-component of gravity 157 * as measured by the Gravity Sensor is +9.8 when the hand is downward and -9.8 when the hand 158 * is upward (signs are reversed if the watch is worn on the right hand). Since the upward or 159 * downward may not be completely accurate, we leave some room and instead of 9.8, we use 160 * GRAVITY_THRESHOLD. We also consider the up <-> down movement successful if it takes less than 161 * TIME_THRESHOLD_NS. 162 */ 163 private void detectJump(float xValue, long timestamp) { 164 if ((Math.abs(xValue) > GRAVITY_THRESHOLD)) { 165 if(timestamp - mLastTime < TIME_THRESHOLD_NS && mUp != (xValue > 0)) { 166 onJumpDetected(!mUp); 167 } 168 mUp = xValue > 0; 169 mLastTime = timestamp; 170 } 171 } 172 173 /** 174 * Called on detection of a successful down -> up or up -> down movement of hand. 175 */ 176 private void onJumpDetected(boolean up) { 177 // we only count a pair of up and down as one successful movement 178 if (up) { 179 return; 180 } 181 mJumpCounter++; 182 setCounter(mJumpCounter); 183 renewTimer(); 184 } 185 186 /** 187 * Updates the counter on UI, saves it to preferences and vibrates the watch when counter 188 * reaches a multiple of 10. 189 */ 190 private void setCounter(int i) { 191 mCounterPage.setCounter(i); 192 Utils.saveCounterToPreference(this, i); 193 if (i > 0 && i % 10 == 0) { 194 Utils.vibrate(this, 0); 195 } 196 } 197 198 public void resetCounter() { 199 setCounter(0); 200 renewTimer(); 201 } 202 203 /** 204 * Starts a timer to clear the flag FLAG_KEEP_SCREEN_ON. 205 */ 206 private void renewTimer() { 207 if (null != mTimer) { 208 mTimer.cancel(); 209 } 210 mTimerTask = new TimerTask() { 211 @Override 212 public void run() { 213 if (Log.isLoggable(TAG, Log.DEBUG)) { 214 Log.d(TAG, 215 "Removing the FLAG_KEEP_SCREEN_ON flag to allow going to background"); 216 } 217 resetFlag(); 218 } 219 }; 220 mTimer = new Timer(); 221 mTimer.schedule(mTimerTask, SCREEN_ON_TIMEOUT_MS); 222 } 223 224 /** 225 * Resets the FLAG_KEEP_SCREEN_ON flag so activity can go into background. 226 */ 227 private void resetFlag() { 228 mHandler.post(new Runnable() { 229 @Override 230 public void run() { 231 if (Log.isLoggable(TAG, Log.DEBUG)) { 232 Log.d(TAG, "Resetting FLAG_KEEP_SCREEN_ON flag to allow going to background"); 233 } 234 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 235 finish(); 236 } 237 }); 238 } 239 240 /** 241 * Sets the page indicator for the ViewPager. 242 */ 243 private void setIndicator(int i) { 244 switch (i) { 245 case 0: 246 mFirstIndicator.setImageResource(R.drawable.full_10); 247 mSecondIndicator.setImageResource(R.drawable.empty_10); 248 break; 249 case 1: 250 mFirstIndicator.setImageResource(R.drawable.empty_10); 251 mSecondIndicator.setImageResource(R.drawable.full_10); 252 break; 253 } 254 } 255 256 257 } 258