1 /* 2 * Copyright 2014 The Android Open Source Project 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.batchstepsensor; 18 19 import android.app.Activity; 20 import android.content.pm.PackageManager; 21 import android.hardware.Sensor; 22 import android.hardware.SensorEvent; 23 import android.hardware.SensorEventListener; 24 import android.hardware.SensorManager; 25 import android.os.Bundle; 26 import android.support.v4.app.Fragment; 27 28 import com.example.android.common.logger.Log; 29 import com.example.android.batchstepsensor.cardstream.Card; 30 import com.example.android.batchstepsensor.cardstream.CardStream; 31 import com.example.android.batchstepsensor.cardstream.CardStreamFragment; 32 import com.example.android.batchstepsensor.cardstream.OnCardClickListener; 33 34 public class BatchStepSensorFragment extends Fragment implements OnCardClickListener { 35 36 public static final String TAG = "StepSensorSample"; 37 // Cards 38 private CardStreamFragment mCards = null; 39 40 // Card tags 41 public static final String CARD_INTRO = "intro"; 42 public static final String CARD_REGISTER_DETECTOR = "register_detector"; 43 public static final String CARD_REGISTER_COUNTER = "register_counter"; 44 public static final String CARD_BATCHING_DESCRIPTION = "register_batching_description"; 45 public static final String CARD_COUNTING = "counting"; 46 public static final String CARD_EXPLANATION = "explanation"; 47 public static final String CARD_NOBATCHSUPPORT = "error"; 48 49 // Actions from REGISTER cards 50 public static final int ACTION_REGISTER_DETECT_NOBATCHING = 10; 51 public static final int ACTION_REGISTER_DETECT_BATCHING_5s = 11; 52 public static final int ACTION_REGISTER_DETECT_BATCHING_10s = 12; 53 public static final int ACTION_REGISTER_COUNT_NOBATCHING = 21; 54 public static final int ACTION_REGISTER_COUNT_BATCHING_5s = 22; 55 public static final int ACTION_REGISTER_COUNT_BATCHING_10s = 23; 56 // Action from COUNTING card 57 public static final int ACTION_UNREGISTER = 1; 58 // Actions from description cards 59 private static final int ACTION_BATCHING_DESCRIPTION_DISMISS = 2; 60 private static final int ACTION_EXPLANATION_DISMISS = 3; 61 62 // State of application, used to register for sensors when app is restored 63 public static final int STATE_OTHER = 0; 64 public static final int STATE_COUNTER = 1; 65 public static final int STATE_DETECTOR = 2; 66 67 // Bundle tags used to store data when restoring application state 68 private static final String BUNDLE_STATE = "state"; 69 private static final String BUNDLE_LATENCY = "latency"; 70 private static final String BUNDLE_STEPS = "steps"; 71 72 // max batch latency is specified in microseconds 73 private static final int BATCH_LATENCY_0 = 0; // no batching 74 private static final int BATCH_LATENCY_10s = 10000000; 75 private static final int BATCH_LATENCY_5s = 5000000; 76 77 /* 78 For illustration we keep track of the last few events and show their delay from when the 79 event occurred until it was received by the event listener. 80 These variables keep track of the list of timestamps and the number of events. 81 */ 82 // Number of events to keep in queue and display on card 83 private static final int EVENT_QUEUE_LENGTH = 10; 84 // List of timestamps when sensor events occurred 85 private float[] mEventDelays = new float[EVENT_QUEUE_LENGTH]; 86 87 // number of events in event list 88 private int mEventLength = 0; 89 // pointer to next entry in sensor event list 90 private int mEventData = 0; 91 92 // Steps counted in current session 93 private int mSteps = 0; 94 // Value of the step counter sensor when the listener was registered. 95 // (Total steps are calculated from this value.) 96 private int mCounterSteps = 0; 97 // Steps counted by the step counter previously. Used to keep counter consistent across rotation 98 // changes 99 private int mPreviousCounterSteps = 0; 100 // State of the app (STATE_OTHER, STATE_COUNTER or STATE_DETECTOR) 101 private int mState = STATE_OTHER; 102 // When a listener is registered, the batch sensor delay in microseconds 103 private int mMaxDelay = 0; 104 105 @Override 106 public void onResume() { 107 super.onResume(); 108 109 CardStreamFragment stream = getCardStream(); 110 if (stream.getVisibleCardCount() < 1) { 111 // No cards are visible, started for the first time 112 // Prepare all cards and show the intro card. 113 initialiseCards(); 114 showIntroCard(); 115 // Show the registration card if the hardware is supported, show an error otherwise 116 if (isKitkatWithStepSensor()) { 117 showRegisterCard(); 118 } else { 119 showErrorCard(); 120 } 121 } 122 } 123 124 @Override 125 public void onPause() { 126 super.onPause(); 128 // Unregister the listener when the application is paused 129 unregisterListeners(); 131 } 132 133 /** 134 * Returns true if this device is supported. It needs to be running Android KitKat (4.4) or 135 * higher and has a step counter and step detector sensor. 136 * This check is useful when an app provides an alternative implementation or different 137 * functionality if the step sensors are not available or this code runs on a platform version 138 * below Android KitKat. If this functionality is required, then the minSDK parameter should 139 * be specified appropriately in the AndroidManifest. 140 * 141 * @return True iff the device can run this sample 142 */ 143 private boolean isKitkatWithStepSensor() { 145 // Require at least Android KitKat 146 int currentApiVersion = android.os.Build.VERSION.SDK_INT; 147 // Check that the device supports the step counter and detector sensors 148 PackageManager packageManager = getActivity().getPackageManager(); 149 return currentApiVersion >= android.os.Build.VERSION_CODES.KITKAT 150 && packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_COUNTER) 151 && packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR); 153 } 154 155 /** 156 * Handles a click on a card action. 157 * Registers a SensorEventListener (see {@link #registerEventListener(int, int)}) with the 158 * selected delay, dismisses cards and unregisters the listener 159 * (see {@link #unregisterListeners()}). 160 * Actions are defined when a card is created. 161 * 162 * @param cardActionId 163 * @param cardTag 164 */ 165 @Override 166 public void onCardClick(int cardActionId, String cardTag) { 167 168 switch (cardActionId) { 170 // Register Step Counter card 171 case ACTION_REGISTER_COUNT_NOBATCHING: 172 registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_COUNTER); 173 break; 174 case ACTION_REGISTER_COUNT_BATCHING_5s: 175 registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_COUNTER); 176 break; 177 case ACTION_REGISTER_COUNT_BATCHING_10s: 178 registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_COUNTER); 179 break; 180 181 // Register Step Detector card 182 case ACTION_REGISTER_DETECT_NOBATCHING: 183 registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_DETECTOR); 184 break; 185 case ACTION_REGISTER_DETECT_BATCHING_5s: 186 registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_DETECTOR); 187 break; 188 case ACTION_REGISTER_DETECT_BATCHING_10s: 189 registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_DETECTOR); 190 break; 191 192 // Unregister card 193 case ACTION_UNREGISTER: 194 showRegisterCard(); 195 unregisterListeners(); 196 // reset the application state when explicitly unregistered 197 mState = STATE_OTHER; 198 break; 200 // Explanation cards 201 case ACTION_BATCHING_DESCRIPTION_DISMISS: 202 // permanently remove the batch description card, it will not be shown again 203 getCardStream().removeCard(CARD_BATCHING_DESCRIPTION); 204 break; 205 case ACTION_EXPLANATION_DISMISS: 206 // permanently remove the explanation card, it will not be shown again 207 getCardStream().removeCard(CARD_EXPLANATION); 208 } 209 210 // For register cards, display the counting card 211 if (cardTag.equals(CARD_REGISTER_COUNTER) || cardTag.equals(CARD_REGISTER_DETECTOR)) { 212 showCountingCards(); 213 } 214 } 215 216 /** 217 * Register a {@link android.hardware.SensorEventListener} for the sensor and max batch delay. 218 * The maximum batch delay specifies the maximum duration in microseconds for which subsequent 219 * sensor events can be temporarily stored by the sensor before they are delivered to the 220 * registered SensorEventListener. A larger delay allows the system to handle sensor events more 221 * efficiently, allowing the system to switch to a lower power state while the sensor is 222 * capturing events. Once the max delay is reached, all stored events are delivered to the 223 * registered listener. Note that this value only specifies the maximum delay, the listener may 224 * receive events quicker. A delay of 0 disables batch mode and registers the listener in 225 * continuous mode. 226 * The optimium batch delay depends on the application. For example, a delay of 5 seconds or 227 * higher may be appropriate for an application that does not update the UI in real time. 228 * 229 * @param maxdelay 230 * @param sensorType 231 */ 232 private void registerEventListener(int maxdelay, int sensorType) { 234 235 // Keep track of state so that the correct sensor type and batch delay can be set up when 236 // the app is restored (for example on screen rotation). 237 mMaxDelay = maxdelay; 238 if (sensorType == Sensor.TYPE_STEP_COUNTER) { 239 mState = STATE_COUNTER; 240 /* 241 Reset the initial step counter value, the first event received by the event listener is 242 stored in mCounterSteps and used to calculate the total number of steps taken. 243 */ 244 mCounterSteps = 0; 245 Log.i(TAG, "Event listener for step counter sensor registered with a max delay of " 246 + mMaxDelay); 247 } else { 248 mState = STATE_DETECTOR; 249 Log.i(TAG, "Event listener for step detector sensor registered with a max delay of " 250 + mMaxDelay); 251 } 252 253 // Get the default sensor for the sensor type from the SenorManager 254 SensorManager sensorManager = 255 (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE); 256 // sensorType is either Sensor.TYPE_STEP_COUNTER or Sensor.TYPE_STEP_DETECTOR 257 Sensor sensor = sensorManager.getDefaultSensor(sensorType); 258 259 // Register the listener for this sensor in batch mode. 260 // If the max delay is 0, events will be delivered in continuous mode without batching. 261 final boolean batchMode = sensorManager.registerListener( 262 mListener, sensor, SensorManager.SENSOR_DELAY_NORMAL, maxdelay); 263 264 if (!batchMode) { 265 // Batch mode could not be enabled, show a warning message and switch to continuous mode 266 getCardStream().getCard(CARD_NOBATCHSUPPORT) 267 .setDescription(getString(R.string.warning_nobatching)); 268 getCardStream().showCard(CARD_NOBATCHSUPPORT); 269 Log.w(TAG, "Could not register sensor listener in batch mode, " + 270 "falling back to continuous mode."); 271 } 272 273 if (maxdelay > 0 && batchMode) { 274 // Batch mode was enabled successfully, show a description card 275 getCardStream().showCard(CARD_BATCHING_DESCRIPTION); 276 } 277 278 // Show the explanation card 279 getCardStream().showCard(CARD_EXPLANATION); 280 282 283 } 284 285 /** 286 * Unregisters the sensor listener if it is registered. 287 */ 288 private void unregisterListeners() { 290 SensorManager sensorManager = 291 (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE); 292 sensorManager.unregisterListener(mListener); 293 Log.i(TAG, "Sensor listener unregistered."); 294 296 } 297 298 /** 299 * Resets the step counter by clearing all counting variables and lists. 300 */ 301 private void resetCounter() { 303 mSteps = 0; 304 mCounterSteps = 0; 305 mEventLength = 0; 306 mEventDelays = new float[EVENT_QUEUE_LENGTH]; 307 mPreviousCounterSteps = 0; 309 } 310 311 312 /** 313 * Listener that handles step sensor events for step detector and step counter sensors. 314 */ 315 private final SensorEventListener mListener = new SensorEventListener() { 316 @Override 317 public void onSensorChanged(SensorEvent event) { 319 // store the delay of this event 320 recordDelay(event); 321 final String delayString = getDelayString(); 322 323 if (event.sensor.getType() == Sensor.TYPE_STEP_DETECTOR) { 324 // A step detector event is received for each step. 325 // This means we need to count steps ourselves 326 327 mSteps += event.values.length; 328 329 // Update the card with the latest step count 330 getCardStream().getCard(CARD_COUNTING) 331 .setTitle(getString(R.string.counting_title, mSteps)) 332 .setDescription(getString(R.string.counting_description, 333 getString(R.string.sensor_detector), mMaxDelay, delayString)); 334 335 Log.i(TAG, 336 "New step detected by STEP_DETECTOR sensor. Total step count: " + mSteps); 337 338 } else if (event.sensor.getType() == Sensor.TYPE_STEP_COUNTER) { 339 340 /* 341 A step counter event contains the total number of steps since the listener 342 was first registered. We need to keep track of this initial value to calculate the 343 number of steps taken, as the first value a listener receives is undefined. 344 */ 345 if (mCounterSteps < 1) { 346 // initial value 347 mCounterSteps = (int) event.values[0]; 348 } 349 350 // Calculate steps taken based on first counter value received. 351 mSteps = (int) event.values[0] - mCounterSteps; 352 353 // Add the number of steps previously taken, otherwise the counter would start at 0. 354 // This is needed to keep the counter consistent across rotation changes. 355 mSteps = mSteps + mPreviousCounterSteps; 356 357 // Update the card with the latest step count 358 getCardStream().getCard(CARD_COUNTING) 359 .setTitle(getString(R.string.counting_title, mSteps)) 360 .setDescription(getString(R.string.counting_description, 361 getString(R.string.sensor_counter), mMaxDelay, delayString)); 362 Log.i(TAG, "New step detected by STEP_COUNTER sensor. Total step count: " + mSteps); 364 } 365 } 366 367 @Override 368 public void onAccuracyChanged(Sensor sensor, int accuracy) { 369 370 } 371 }; 372 373 /** 374 * Records the delay for the event. 375 * 376 * @param event 377 */ 378 private void recordDelay(SensorEvent event) { 379 // Calculate the delay from when event was recorded until it was received here in ms 380 // Event timestamp is recorded in us accuracy, but ms accuracy is sufficient here 381 mEventDelays[mEventData] = System.currentTimeMillis() - (event.timestamp / 1000000L); 382 383 // Increment length counter 384 mEventLength = Math.min(EVENT_QUEUE_LENGTH, mEventLength + 1); 385 // Move pointer to the next (oldest) location 386 mEventData = (mEventData + 1) % EVENT_QUEUE_LENGTH; 387 } 388 389 private final StringBuffer mDelayStringBuffer = new StringBuffer(); 390 391 /** 392 * Returns a string describing the sensor delays recorded in 393 * {@link #recordDelay(android.hardware.SensorEvent)}. 394 * 395 * @return 396 */ 397 private String getDelayString() { 398 // Empty the StringBuffer 399 mDelayStringBuffer.setLength(0); 400 401 // Loop over all recorded delays and append them to the buffer as a decimal 402 for (int i = 0; i < mEventLength; i++) { 403 if (i > 0) { 404 mDelayStringBuffer.append(", "); 405 } 406 final int index = (mEventData + i) % EVENT_QUEUE_LENGTH; 407 final float delay = mEventDelays[index] / 1000f; // convert delay from ms into s 408 mDelayStringBuffer.append(String.format("%1.1f", delay)); 409 } 410 411 return mDelayStringBuffer.toString(); 412 } 413 414 /** 415 * Records the state of the application into the {@link android.os.Bundle}. 416 * 417 * @param outState 418 */ 419 @Override 420 public void onSaveInstanceState(Bundle outState) { 422 super.onSaveInstanceState(outState); 423 // Store all variables required to restore the state of the application 424 outState.putInt(BUNDLE_LATENCY, mMaxDelay); 425 outState.putInt(BUNDLE_STATE, mState); 426 outState.putInt(BUNDLE_STEPS, mSteps); 428 } 429 430 @Override 431 public void onActivityCreated(Bundle savedInstanceState) { 432 super.onActivityCreated(savedInstanceState); 434 // Fragment is being restored, reinitialise its state with data from the bundle 435 if (savedInstanceState != null) { 436 resetCounter(); 437 mSteps = savedInstanceState.getInt(BUNDLE_STEPS); 438 mState = savedInstanceState.getInt(BUNDLE_STATE); 439 mMaxDelay = savedInstanceState.getInt(BUNDLE_LATENCY); 440 441 // Register listeners again if in detector or counter states with restored delay 442 if (mState == STATE_DETECTOR) { 443 registerEventListener(mMaxDelay, Sensor.TYPE_STEP_DETECTOR); 444 } else if (mState == STATE_COUNTER) { 445 // store the previous number of steps to keep step counter count consistent 446 mPreviousCounterSteps = mSteps; 447 registerEventListener(mMaxDelay, Sensor.TYPE_STEP_COUNTER); 448 } 449 } 451 } 452 453 /** 454 * Hides the registration cards, reset the counter and show the step counting card. 455 */ 456 private void showCountingCards() { 457 // Hide the registration cards 458 getCardStream().hideCard(CARD_REGISTER_DETECTOR); 459 getCardStream().hideCard(CARD_REGISTER_COUNTER); 460 461 // Show the explanation card if it has not been dismissed 462 getCardStream().showCard(CARD_EXPLANATION); 463 464 // Reset the step counter, then show the step counting card 465 resetCounter(); 466 467 // Set the inital text for the step counting card before a step is recorded 468 String sensor = "-"; 469 if (mState == STATE_COUNTER) { 470 sensor = getString(R.string.sensor_counter); 471 } else if (mState == STATE_DETECTOR) { 472 sensor = getString(R.string.sensor_detector); 473 } 474 // Set initial text 475 getCardStream().getCard(CARD_COUNTING) 476 .setTitle(getString(R.string.counting_title, 0)) 477 .setDescription(getString(R.string.counting_description, sensor, mMaxDelay, "-")); 478 479 // Show the counting card and make it undismissable 480 getCardStream().showCard(CARD_COUNTING, false); 481 482 } 483 484 /** 485 * Show the introduction card 486 */ 487 private void showIntroCard() { 488 Card c = new Card.Builder(this, CARD_INTRO) 489 .setTitle(getString(R.string.intro_title)) 490 .setDescription(getString(R.string.intro_message)) 491 .build(getActivity()); 492 getCardStream().addCard(c, true); 493 } 494 495 /** 496 * Show two registration cards, one for the step detector and counter sensors. 497 */ 498 private void showRegisterCard() { 499 // Hide the counting and explanation cards 500 getCardStream().hideCard(CARD_BATCHING_DESCRIPTION); 501 getCardStream().hideCard(CARD_EXPLANATION); 502 getCardStream().hideCard(CARD_COUNTING); 503 504 // Show two undismissable registration cards, one for each step sensor 505 getCardStream().showCard(CARD_REGISTER_DETECTOR, false); 506 getCardStream().showCard(CARD_REGISTER_COUNTER, false); 507 } 508 509 /** 510 * Show the error card. 511 */ 512 private void showErrorCard() { 513 getCardStream().showCard(CARD_NOBATCHSUPPORT, false); 514 } 515 516 /** 517 * Initialise Cards. 518 */ 519 private void initialiseCards() { 520 // Step counting 521 Card c = new Card.Builder(this, CARD_COUNTING) 522 .setTitle("Steps") 523 .setDescription("") 524 .addAction("Unregister Listener", ACTION_UNREGISTER, Card.ACTION_NEGATIVE) 525 .build(getActivity()); 526 getCardStream().addCard(c); 527 528 // Register step detector listener 529 c = new Card.Builder(this, CARD_REGISTER_DETECTOR) 530 .setTitle(getString(R.string.register_detector_title)) 531 .setDescription(getString(R.string.register_detector_description)) 532 .addAction(getString(R.string.register_0), 533 ACTION_REGISTER_DETECT_NOBATCHING, Card.ACTION_NEUTRAL) 534 .addAction(getString(R.string.register_5), 535 ACTION_REGISTER_DETECT_BATCHING_5s, Card.ACTION_NEUTRAL) 536 .addAction(getString(R.string.register_10), 537 ACTION_REGISTER_DETECT_BATCHING_10s, Card.ACTION_NEUTRAL) 538 .build(getActivity()); 539 getCardStream().addCard(c); 540 541 // Register step counter listener 542 c = new Card.Builder(this, CARD_REGISTER_COUNTER) 543 .setTitle(getString(R.string.register_counter_title)) 544 .setDescription(getString(R.string.register_counter_description)) 545 .addAction(getString(R.string.register_0), 546 ACTION_REGISTER_COUNT_NOBATCHING, Card.ACTION_NEUTRAL) 547 .addAction(getString(R.string.register_5), 548 ACTION_REGISTER_COUNT_BATCHING_5s, Card.ACTION_NEUTRAL) 549 .addAction(getString(R.string.register_10), 550 ACTION_REGISTER_COUNT_BATCHING_10s, Card.ACTION_NEUTRAL) 551 .build(getActivity()); 552 getCardStream().addCard(c); 553 554 555 // Batching description 556 c = new Card.Builder(this, CARD_BATCHING_DESCRIPTION) 557 .setTitle(getString(R.string.batching_queue_title)) 558 .setDescription(getString(R.string.batching_queue_description)) 559 .addAction(getString(R.string.action_notagain), 560 ACTION_BATCHING_DESCRIPTION_DISMISS, Card.ACTION_POSITIVE) 561 .build(getActivity()); 562 getCardStream().addCard(c); 563 564 // Explanation 565 c = new Card.Builder(this, CARD_EXPLANATION) 566 .setDescription(getString(R.string.explanation_description)) 567 .addAction(getString(R.string.action_notagain), 568 ACTION_EXPLANATION_DISMISS, Card.ACTION_POSITIVE) 569 .build(getActivity()); 570 getCardStream().addCard(c); 571 572 // Error 573 c = new Card.Builder(this, CARD_NOBATCHSUPPORT) 574 .setTitle(getString(R.string.error_title)) 575 .setDescription(getString(R.string.error_nosensor)) 576 .build(getActivity()); 577 getCardStream().addCard(c); 578 } 579 580 /** 581 * Returns the cached CardStreamFragment used to show cards. 582 * 583 * @return 584 */ 585 private CardStreamFragment getCardStream() { 586 if (mCards == null) { 587 mCards = ((CardStream) getActivity()).getCardStream(); 588 } 589 return mCards; 590 } 591 592 }