Home | History | Annotate | Download | only in com.example.android.wearable.jumpingjack
      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