Java tutorial
/* * 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.wordpress.tonytam.avatar; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.wearable.DataApi; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.MessageApi; import com.google.android.gms.wearable.Node; import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import com.wordpress.tonytam.avatar.fragments.CounterFragment; import com.wordpress.tonytam.avatar.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 AvatarWearActivity extends Activity implements SensorEventListener, DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { 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 = 2000000; // 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) private static final long TIME_THRESHOLD_NS = 2000000000; // in nanoseconds (= 2sec) /** We care about 1/2 second measurements */ private static final long TIME_THRESHOLD_NS_WAIT = 50000000; /** * 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 UP_GRAVITY_THRESHOLD = -4.0f; //private static final float DOWN_GRAVITY_THRESHOLD = 7.0f; private static final float UP_GRAVITY_THRESHOLD = -0.1f; private static final float DOWN_GRAVITY_THRESHOLD = 1.0f; private SensorManager mSensorManager; private Sensor mSensor, mSensorAccelerometer; 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; private static final String COUNT_KEY = "com.example.key.count"; private static final String RAW_X = "com.wordpress.tonytam.avatar.sensorx"; private static final String RAW_Y = "com.wordpress.tonytam.avatar.sensory"; private int heightValue = 0; private GoogleApiClient mGoogleApiClient; private int count = 0; @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); mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).addApi(Wearable.API).build(); } 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 onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override public void onConnected(Bundle connectionHint) { //String message = "Hello wearable\n Via the data layer"; //Requires a new thread to avoid blocking the UI //new SendToDataLayerThread("/message_path", message).start(); } @Override protected void onStop() { if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } super.onStop(); } @Override public void onConnectionSuspended(int i) { } @Override public void onDataChanged(DataEventBuffer dataEventBuffer) { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.d("Avatar: FAILED", connectionResult.toString()); } @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"); } } // TODO: if (false) { if (mSensorManager.registerListener(this, mSensorAccelerometer, 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, event); if (false) { Log.d("jump:", String.valueOf(event.values[0]) + "," + String.valueOf(event.values[1]) + " - " + String.valueOf(event.sensor) + String.valueOf(event.accuracy) + " : " + String.valueOf(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, SensorEvent v) { heightValue = Math.round(xValue); if (false) { if (timestamp - mLastTime < TIME_THRESHOLD_NS) { onJumpDetected(!mUp, v); } } if ((timestamp - mLastTime) > TIME_THRESHOLD_NS_WAIT) { onJumpDetected(!mUp, v); mLastTime = timestamp; } if (false) { if (((xValue > 0) && (xValue > DOWN_GRAVITY_THRESHOLD)) || ((xValue < 0) && (xValue < UP_GRAVITY_THRESHOLD))) { // TODO String message = "Hello wearable\n Via the data layer" + count; // new SendToDataLayerThread("/count", message).start(); if (timestamp - mLastTime < TIME_THRESHOLD_NS && mUp != (xValue > 0)) { onJumpDetected(!mUp, v); } mUp = xValue > 0; mLastTime = timestamp; } } } /** * Called on detection of a successful down -> up or up -> down movement of hand. */ private void onJumpDetected(boolean up, SensorEvent v) { increaseCounter(v); // we only count a pair of up and down as one successful movement if (up) { return; } Log.d("jump", "************************************"); Log.d("jump", "DETECTED"); Log.d("jump", "************************************"); mJumpCounter++; // increaseCounter(); setCounter(mJumpCounter); renewTimer(); } // Create a data map and put data in it private void increaseCounter(SensorEvent v) { PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/count"); putDataMapReq.getDataMap().putInt(COUNT_KEY, heightValue); putDataMapReq.getDataMap().putFloat(RAW_X, v.values[0]); putDataMapReq.getDataMap().putFloat(RAW_Y, v.values[1]); PutDataRequest putDataReq = putDataMapReq.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq); Log.d("Avatar", "sending data" + String.valueOf(heightValue)); String message = "Hello wearable\n Via the data layer" + count; //Requires a new thread to avoid blocking the UI //new SendToDataLayerThread("/count", message).start(); } /** * 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 % 5 == 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; } } class SendToDataLayerThread extends Thread { String path; String message; // Constructor to send a message to the data layer SendToDataLayerThread(String p, String msg) { path = p; message = msg; } public void run() { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); for (Node node : nodes.getNodes()) { MessageApi.SendMessageResult result = Wearable.MessageApi .sendMessage(mGoogleApiClient, node.getId(), path, message.getBytes()).await(); if (result.getStatus().isSuccess()) { Log.v("Avatar", "Message: {" + message + "} sent to: " + node.getDisplayName()); } else { // Log an error Log.v("Avatar", "ERROR: failed to send Message"); } } } } }