to top
BatchStepSensor / src / com.example.android.batchstepsensor /

BatchStepSensorFragment.java

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
}