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
onCreate(Bundle savedInstanceState)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 
setupViews()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
onResume()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
onPause()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
onSensorChanged(SensorEvent event)146     public void onSensorChanged(SensorEvent event) {
147         detectJump(event.values[0], event.timestamp);
148     }
149 
150     @Override
onAccuracyChanged(Sensor sensor, int accuracy)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      */
detectJump(float xValue, long timestamp)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      */
onJumpDetected(boolean up)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      */
setCounter(int i)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 
resetCounter()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      */
renewTimer()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      */
resetFlag()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      */
setIndicator(int i)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