aboutsummaryrefslogtreecommitdiff
path: root/sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java')
-rw-r--r--sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java588
1 files changed, 588 insertions, 0 deletions
diff --git a/sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java b/sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java
new file mode 100644
index 00000000..aab0fc38
--- /dev/null
+++ b/sensors/BatchStepSensor/BatchStepSensorSample/src/main/java/com/example/android/batchstepsensor/BatchStepSensorFragment.java
@@ -0,0 +1,588 @@
+/*
+* Copyright 2014 The Android Open Source Project
+*
+* 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.batchstepsensor;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+import com.example.android.common.logger.Log;
+
+public class BatchStepSensorFragment extends Fragment implements OnCardClickListener {
+
+ public static final String TAG = "StepSensorSample";
+ // Cards
+ private CardStreamFragment mCards = null;
+
+ // Card tags
+ public static final String CARD_INTRO = "intro";
+ public static final String CARD_REGISTER_DETECTOR = "register_detector";
+ public static final String CARD_REGISTER_COUNTER = "register_counter";
+ public static final String CARD_BATCHING_DESCRIPTION = "register_batching_description";
+ public static final String CARD_COUNTING = "counting";
+ public static final String CARD_EXPLANATION = "explanation";
+ public static final String CARD_NOBATCHSUPPORT = "error";
+
+ // Actions from REGISTER cards
+ public static final int ACTION_REGISTER_DETECT_NOBATCHING = 10;
+ public static final int ACTION_REGISTER_DETECT_BATCHING_5s = 11;
+ public static final int ACTION_REGISTER_DETECT_BATCHING_10s = 12;
+ public static final int ACTION_REGISTER_COUNT_NOBATCHING = 21;
+ public static final int ACTION_REGISTER_COUNT_BATCHING_5s = 22;
+ public static final int ACTION_REGISTER_COUNT_BATCHING_10s = 23;
+ // Action from COUNTING card
+ public static final int ACTION_UNREGISTER = 1;
+ // Actions from description cards
+ private static final int ACTION_BATCHING_DESCRIPTION_DISMISS = 2;
+ private static final int ACTION_EXPLANATION_DISMISS = 3;
+
+ // State of application, used to register for sensors when app is restored
+ public static final int STATE_OTHER = 0;
+ public static final int STATE_COUNTER = 1;
+ public static final int STATE_DETECTOR = 2;
+
+ // Bundle tags used to store data when restoring application state
+ private static final String BUNDLE_STATE = "state";
+ private static final String BUNDLE_LATENCY = "latency";
+ private static final String BUNDLE_STEPS = "steps";
+
+ // max batch latency is specified in microseconds
+ private static final int BATCH_LATENCY_0 = 0; // no batching
+ private static final int BATCH_LATENCY_10s = 10000000;
+ private static final int BATCH_LATENCY_5s = 5000000;
+
+ /*
+ For illustration we keep track of the last few events and show their delay from when the
+ event occurred until it was received by the event listener.
+ These variables keep track of the list of timestamps and the number of events.
+ */
+ // Number of events to keep in queue and display on card
+ private static final int EVENT_QUEUE_LENGTH = 10;
+ // List of timestamps when sensor events occurred
+ private float[] mEventDelays = new float[EVENT_QUEUE_LENGTH];
+
+ // number of events in event list
+ private int mEventLength = 0;
+ // pointer to next entry in sensor event list
+ private int mEventData = 0;
+
+ // Steps counted in current session
+ private int mSteps = 0;
+ // Value of the step counter sensor when the listener was registered.
+ // (Total steps are calculated from this value.)
+ private int mCounterSteps = 0;
+ // Steps counted by the step counter previously. Used to keep counter consistent across rotation
+ // changes
+ private int mPreviousCounterSteps = 0;
+ // State of the app (STATE_OTHER, STATE_COUNTER or STATE_DETECTOR)
+ private int mState = STATE_OTHER;
+ // When a listener is registered, the batch sensor delay in microseconds
+ private int mMaxDelay = 0;
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ CardStreamFragment stream = getCardStream();
+ if (stream.getVisibleCardCount() < 1) {
+ // No cards are visible, started for the first time
+ // Prepare all cards and show the intro card.
+ initialiseCards();
+ showIntroCard();
+ // Show the registration card if the hardware is supported, show an error otherwise
+ if (isKitkatWithStepSensor()) {
+ showRegisterCard();
+ } else {
+ showErrorCard();
+ }
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // BEGIN_INCLUDE(onpause)
+ // Unregister the listener when the application is paused
+ unregisterListeners();
+ // END_INCLUDE(onpause)
+ }
+
+ /**
+ * Returns true if this device is supported. It needs to be running Android KitKat (4.4) or
+ * higher and has a step counter and step detector sensor.
+ * This check is useful when an app provides an alternative implementation or different
+ * functionality if the step sensors are not available or this code runs on a platform version
+ * below Android KitKat. If this functionality is required, then the minSDK parameter should
+ * be specified appropriately in the AndroidManifest.
+ *
+ * @return True iff the device can run this sample
+ */
+ private boolean isKitkatWithStepSensor() {
+ // BEGIN_INCLUDE(iskitkatsensor)
+ // Require at least Android KitKat
+ int currentApiVersion = android.os.Build.VERSION.SDK_INT;
+ // Check that the device supports the step counter and detector sensors
+ PackageManager packageManager = getActivity().getPackageManager();
+ return currentApiVersion >= android.os.Build.VERSION_CODES.KITKAT
+ && packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_COUNTER)
+ && packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR);
+ // END_INCLUDE(iskitkatsensor)
+ }
+
+ /**
+ * Handles a click on a card action.
+ * Registers a SensorEventListener (see {@link #registerEventListener(int, int)}) with the
+ * selected delay, dismisses cards and unregisters the listener
+ * (see {@link #unregisterListeners()}).
+ * Actions are defined when a card is created.
+ *
+ * @param cardActionId
+ * @param cardTag
+ */
+ @Override
+ public void onCardClick(int cardActionId, String cardTag) {
+
+ switch (cardActionId) {
+ // BEGIN_INCLUDE(onclick)
+ // Register Step Counter card
+ case ACTION_REGISTER_COUNT_NOBATCHING:
+ registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_COUNTER);
+ break;
+ case ACTION_REGISTER_COUNT_BATCHING_5s:
+ registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_COUNTER);
+ break;
+ case ACTION_REGISTER_COUNT_BATCHING_10s:
+ registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_COUNTER);
+ break;
+
+ // Register Step Detector card
+ case ACTION_REGISTER_DETECT_NOBATCHING:
+ registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_DETECTOR);
+ break;
+ case ACTION_REGISTER_DETECT_BATCHING_5s:
+ registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_DETECTOR);
+ break;
+ case ACTION_REGISTER_DETECT_BATCHING_10s:
+ registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_DETECTOR);
+ break;
+
+ // Unregister card
+ case ACTION_UNREGISTER:
+ showRegisterCard();
+ unregisterListeners();
+ // reset the application state when explicitly unregistered
+ mState = STATE_OTHER;
+ break;
+ // END_INCLUDE(onclick)
+ // Explanation cards
+ case ACTION_BATCHING_DESCRIPTION_DISMISS:
+ // permanently remove the batch description card, it will not be shown again
+ getCardStream().removeCard(CARD_BATCHING_DESCRIPTION);
+ break;
+ case ACTION_EXPLANATION_DISMISS:
+ // permanently remove the explanation card, it will not be shown again
+ getCardStream().removeCard(CARD_EXPLANATION);
+ }
+
+ // For register cards, display the counting card
+ if (cardTag.equals(CARD_REGISTER_COUNTER) || cardTag.equals(CARD_REGISTER_DETECTOR)) {
+ showCountingCards();
+ }
+ }
+
+ /**
+ * Register a {@link android.hardware.SensorEventListener} for the sensor and max batch delay.
+ * The maximum batch delay specifies the maximum duration in microseconds for which subsequent
+ * sensor events can be temporarily stored by the sensor before they are delivered to the
+ * registered SensorEventListener. A larger delay allows the system to handle sensor events more
+ * efficiently, allowing the system to switch to a lower power state while the sensor is
+ * capturing events. Once the max delay is reached, all stored events are delivered to the
+ * registered listener. Note that this value only specifies the maximum delay, the listener may
+ * receive events quicker. A delay of 0 disables batch mode and registers the listener in
+ * continuous mode.
+ * The optimium batch delay depends on the application. For example, a delay of 5 seconds or
+ * higher may be appropriate for an application that does not update the UI in real time.
+ *
+ * @param maxdelay
+ * @param sensorType
+ */
+ private void registerEventListener(int maxdelay, int sensorType) {
+ // BEGIN_INCLUDE(register)
+
+ // Keep track of state so that the correct sensor type and batch delay can be set up when
+ // the app is restored (for example on screen rotation).
+ mMaxDelay = maxdelay;
+ if (sensorType == Sensor.TYPE_STEP_COUNTER) {
+ mState = STATE_COUNTER;
+ /*
+ Reset the initial step counter value, the first event received by the event listener is
+ stored in mCounterSteps and used to calculate the total number of steps taken.
+ */
+ mCounterSteps = 0;
+ Log.i(TAG, "Event listener for step counter sensor registered with a max delay of "
+ + mMaxDelay);
+ } else {
+ mState = STATE_DETECTOR;
+ Log.i(TAG, "Event listener for step detector sensor registered with a max delay of "
+ + mMaxDelay);
+ }
+
+ // Get the default sensor for the sensor type from the SenorManager
+ SensorManager sensorManager =
+ (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
+ // sensorType is either Sensor.TYPE_STEP_COUNTER or Sensor.TYPE_STEP_DETECTOR
+ Sensor sensor = sensorManager.getDefaultSensor(sensorType);
+
+ // Register the listener for this sensor in batch mode.
+ // If the max delay is 0, events will be delivered in continuous mode without batching.
+ final boolean batchMode = sensorManager.registerListener(
+ mListener, sensor, SensorManager.SENSOR_DELAY_NORMAL, maxdelay);
+
+ if (!batchMode) {
+ // Batch mode could not be enabled, show a warning message and switch to continuous mode
+ getCardStream().getCard(CARD_NOBATCHSUPPORT)
+ .setDescription(getString(R.string.warning_nobatching));
+ getCardStream().showCard(CARD_NOBATCHSUPPORT);
+ Log.w(TAG, "Could not register sensor listener in batch mode, " +
+ "falling back to continuous mode.");
+ }
+
+ if (maxdelay > 0 && batchMode) {
+ // Batch mode was enabled successfully, show a description card
+ getCardStream().showCard(CARD_BATCHING_DESCRIPTION);
+ }
+
+ // Show the explanation card
+ getCardStream().showCard(CARD_EXPLANATION);
+
+ // END_INCLUDE(register)
+
+ }
+
+ /**
+ * Unregisters the sensor listener if it is registered.
+ */
+ private void unregisterListeners() {
+ // BEGIN_INCLUDE(unregister)
+ SensorManager sensorManager =
+ (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
+ sensorManager.unregisterListener(mListener);
+ Log.i(TAG, "Sensor listener unregistered.");
+
+ // END_INCLUDE(unregister)
+ }
+
+ /**
+ * Resets the step counter by clearing all counting variables and lists.
+ */
+ private void resetCounter() {
+ // BEGIN_INCLUDE(reset)
+ mSteps = 0;
+ mCounterSteps = 0;
+ mEventLength = 0;
+ mEventDelays = new float[EVENT_QUEUE_LENGTH];
+ mPreviousCounterSteps = 0;
+ // END_INCLUDE(reset)
+ }
+
+
+ /**
+ * Listener that handles step sensor events for step detector and step counter sensors.
+ */
+ private final SensorEventListener mListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // BEGIN_INCLUDE(sensorevent)
+ // store the delay of this event
+ recordDelay(event);
+ final String delayString = getDelayString();
+
+ if (event.sensor.getType() == Sensor.TYPE_STEP_DETECTOR) {
+ // A step detector event is received for each step.
+ // This means we need to count steps ourselves
+
+ mSteps += event.values.length;
+
+ // Update the card with the latest step count
+ getCardStream().getCard(CARD_COUNTING)
+ .setTitle(getString(R.string.counting_title, mSteps))
+ .setDescription(getString(R.string.counting_description,
+ getString(R.string.sensor_detector), mMaxDelay, delayString));
+
+ Log.i(TAG,
+ "New step detected by STEP_DETECTOR sensor. Total step count: " + mSteps);
+
+ } else if (event.sensor.getType() == Sensor.TYPE_STEP_COUNTER) {
+
+ /*
+ A step counter event contains the total number of steps since the listener
+ was first registered. We need to keep track of this initial value to calculate the
+ number of steps taken, as the first value a listener receives is undefined.
+ */
+ if (mCounterSteps < 1) {
+ // initial value
+ mCounterSteps = (int) event.values[0];
+ }
+
+ // Calculate steps taken based on first counter value received.
+ mSteps = (int) event.values[0] - mCounterSteps;
+
+ // Add the number of steps previously taken, otherwise the counter would start at 0.
+ // This is needed to keep the counter consistent across rotation changes.
+ mSteps = mSteps + mPreviousCounterSteps;
+
+ // Update the card with the latest step count
+ getCardStream().getCard(CARD_COUNTING)
+ .setTitle(getString(R.string.counting_title, mSteps))
+ .setDescription(getString(R.string.counting_description,
+ getString(R.string.sensor_counter), mMaxDelay, delayString));
+ Log.i(TAG, "New step detected by STEP_COUNTER sensor. Total step count: " + mSteps);
+ // END_INCLUDE(sensorevent)
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ };
+
+ /**
+ * Records the delay for the event.
+ *
+ * @param event
+ */
+ private void recordDelay(SensorEvent event) {
+ // Calculate the delay from when event was recorded until it was received here in ms
+ // Event timestamp is recorded in us accuracy, but ms accuracy is sufficient here
+ mEventDelays[mEventData] = System.currentTimeMillis() - (event.timestamp / 1000000L);
+
+ // Increment length counter
+ mEventLength = Math.min(EVENT_QUEUE_LENGTH, mEventLength + 1);
+ // Move pointer to the next (oldest) location
+ mEventData = (mEventData + 1) % EVENT_QUEUE_LENGTH;
+ }
+
+ private final StringBuffer mDelayStringBuffer = new StringBuffer();
+
+ /**
+ * Returns a string describing the sensor delays recorded in
+ * {@link #recordDelay(android.hardware.SensorEvent)}.
+ *
+ * @return
+ */
+ private String getDelayString() {
+ // Empty the StringBuffer
+ mDelayStringBuffer.setLength(0);
+
+ // Loop over all recorded delays and append them to the buffer as a decimal
+ for (int i = 0; i < mEventLength; i++) {
+ if (i > 0) {
+ mDelayStringBuffer.append(", ");
+ }
+ final int index = (mEventData + i) % EVENT_QUEUE_LENGTH;
+ final float delay = mEventDelays[index] / 1000f; // convert delay from ms into s
+ mDelayStringBuffer.append(String.format("%1.1f", delay));
+ }
+
+ return mDelayStringBuffer.toString();
+ }
+
+ /**
+ * Records the state of the application into the {@link android.os.Bundle}.
+ *
+ * @param outState
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // BEGIN_INCLUDE(saveinstance)
+ super.onSaveInstanceState(outState);
+ // Store all variables required to restore the state of the application
+ outState.putInt(BUNDLE_LATENCY, mMaxDelay);
+ outState.putInt(BUNDLE_STATE, mState);
+ outState.putInt(BUNDLE_STEPS, mSteps);
+ // END_INCLUDE(saveinstance)
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // BEGIN_INCLUDE(restore)
+ // Fragment is being restored, reinitialise its state with data from the bundle
+ if (savedInstanceState != null) {
+ resetCounter();
+ mSteps = savedInstanceState.getInt(BUNDLE_STEPS);
+ mState = savedInstanceState.getInt(BUNDLE_STATE);
+ mMaxDelay = savedInstanceState.getInt(BUNDLE_LATENCY);
+
+ // Register listeners again if in detector or counter states with restored delay
+ if (mState == STATE_DETECTOR) {
+ registerEventListener(mMaxDelay, Sensor.TYPE_STEP_DETECTOR);
+ } else if (mState == STATE_COUNTER) {
+ // store the previous number of steps to keep step counter count consistent
+ mPreviousCounterSteps = mSteps;
+ registerEventListener(mMaxDelay, Sensor.TYPE_STEP_COUNTER);
+ }
+ }
+ // END_INCLUDE(restore)
+ }
+
+ /**
+ * Hides the registration cards, reset the counter and show the step counting card.
+ */
+ private void showCountingCards() {
+ // Hide the registration cards
+ getCardStream().hideCard(CARD_REGISTER_DETECTOR);
+ getCardStream().hideCard(CARD_REGISTER_COUNTER);
+
+ // Show the explanation card if it has not been dismissed
+ getCardStream().showCard(CARD_EXPLANATION);
+
+ // Reset the step counter, then show the step counting card
+ resetCounter();
+
+ // Set the inital text for the step counting card before a step is recorded
+ String sensor = "-";
+ if (mState == STATE_COUNTER) {
+ sensor = getString(R.string.sensor_counter);
+ } else if (mState == STATE_DETECTOR) {
+ sensor = getString(R.string.sensor_detector);
+ }
+ // Set initial text
+ getCardStream().getCard(CARD_COUNTING)
+ .setTitle(getString(R.string.counting_title, 0))
+ .setDescription(getString(R.string.counting_description, sensor, mMaxDelay, "-"));
+
+ // Show the counting card and make it undismissable
+ getCardStream().showCard(CARD_COUNTING, false);
+
+ }
+
+ /**
+ * Show the introduction card
+ */
+ private void showIntroCard() {
+ Card c = new Card.Builder(this, CARD_INTRO)
+ .setTitle(getString(R.string.intro_title))
+ .setDescription(getString(R.string.intro_message))
+ .build(getActivity());
+ getCardStream().addCard(c, true);
+ }
+
+ /**
+ * Show two registration cards, one for the step detector and counter sensors.
+ */
+ private void showRegisterCard() {
+ // Hide the counting and explanation cards
+ getCardStream().hideCard(CARD_BATCHING_DESCRIPTION);
+ getCardStream().hideCard(CARD_EXPLANATION);
+ getCardStream().hideCard(CARD_COUNTING);
+
+ // Show two undismissable registration cards, one for each step sensor
+ getCardStream().showCard(CARD_REGISTER_DETECTOR, false);
+ getCardStream().showCard(CARD_REGISTER_COUNTER, false);
+ }
+
+ /**
+ * Show the error card.
+ */
+ private void showErrorCard() {
+ getCardStream().showCard(CARD_NOBATCHSUPPORT, false);
+ }
+
+ /**
+ * Initialise Cards.
+ */
+ private void initialiseCards() {
+ // Step counting
+ Card c = new Card.Builder(this, CARD_COUNTING)
+ .setTitle("Steps")
+ .setDescription("")
+ .addAction("Unregister Listener", ACTION_UNREGISTER, Card.ACTION_NEGATIVE)
+ .build(getActivity());
+ getCardStream().addCard(c);
+
+ // Register step detector listener
+ c = new Card.Builder(this, CARD_REGISTER_DETECTOR)
+ .setTitle(getString(R.string.register_detector_title))
+ .setDescription(getString(R.string.register_detector_description))
+ .addAction(getString(R.string.register_0),
+ ACTION_REGISTER_DETECT_NOBATCHING, Card.ACTION_NEUTRAL)
+ .addAction(getString(R.string.register_5),
+ ACTION_REGISTER_DETECT_BATCHING_5s, Card.ACTION_NEUTRAL)
+ .addAction(getString(R.string.register_10),
+ ACTION_REGISTER_DETECT_BATCHING_10s, Card.ACTION_NEUTRAL)
+ .build(getActivity());
+ getCardStream().addCard(c);
+
+ // Register step counter listener
+ c = new Card.Builder(this, CARD_REGISTER_COUNTER)
+ .setTitle(getString(R.string.register_counter_title))
+ .setDescription(getString(R.string.register_counter_description))
+ .addAction(getString(R.string.register_0),
+ ACTION_REGISTER_COUNT_NOBATCHING, Card.ACTION_NEUTRAL)
+ .addAction(getString(R.string.register_5),
+ ACTION_REGISTER_COUNT_BATCHING_5s, Card.ACTION_NEUTRAL)
+ .addAction(getString(R.string.register_10),
+ ACTION_REGISTER_COUNT_BATCHING_10s, Card.ACTION_NEUTRAL)
+ .build(getActivity());
+ getCardStream().addCard(c);
+
+
+ // Batching description
+ c = new Card.Builder(this, CARD_BATCHING_DESCRIPTION)
+ .setTitle(getString(R.string.batching_queue_title))
+ .setDescription(getString(R.string.batching_queue_description))
+ .addAction(getString(R.string.action_notagain),
+ ACTION_BATCHING_DESCRIPTION_DISMISS, Card.ACTION_POSITIVE)
+ .build(getActivity());
+ getCardStream().addCard(c);
+
+ // Explanation
+ c = new Card.Builder(this, CARD_EXPLANATION)
+ .setDescription(getString(R.string.explanation_description))
+ .addAction(getString(R.string.action_notagain),
+ ACTION_EXPLANATION_DISMISS, Card.ACTION_POSITIVE)
+ .build(getActivity());
+ getCardStream().addCard(c);
+
+ // Error
+ c = new Card.Builder(this, CARD_NOBATCHSUPPORT)
+ .setTitle(getString(R.string.error_title))
+ .setDescription(getString(R.string.error_nosensor))
+ .build(getActivity());
+ getCardStream().addCard(c);
+ }
+
+ /**
+ * Returns the cached CardStreamFragment used to show cards.
+ *
+ * @return
+ */
+ private CardStreamFragment getCardStream() {
+ if (mCards == null) {
+ mCards = ((CardStream) getActivity()).getCardStream();
+ }
+ return mCards;
+ }
+
+}