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