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
}