Home | History | Annotate | Download | only in batchstepsensor
      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();
    127         // BEGIN_INCLUDE(onpause)
    128         // Unregister the listener when the application is paused
    129         unregisterListeners();
    130         // END_INCLUDE(onpause)
    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() {
    144         // BEGIN_INCLUDE(iskitkatsensor)
    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);
    152         // END_INCLUDE(iskitkatsensor)
    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) {
    169             // BEGIN_INCLUDE(onclick)
    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;
    199             // END_INCLUDE(onclick)
    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) {
    233         // BEGIN_INCLUDE(register)
    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 
    281         // END_INCLUDE(register)
    282 
    283     }
    284 
    285     /**
    286      * Unregisters the sensor listener if it is registered.
    287      */
    288     private void unregisterListeners() {
    289         // BEGIN_INCLUDE(unregister)
    290         SensorManager sensorManager =
    291                 (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
    292         sensorManager.unregisterListener(mListener);
    293         Log.i(TAG, "Sensor listener unregistered.");
    294 
    295         // END_INCLUDE(unregister)
    296     }
    297 
    298     /**
    299      * Resets the step counter by clearing all counting variables and lists.
    300      */
    301     private void resetCounter() {
    302         // BEGIN_INCLUDE(reset)
    303         mSteps = 0;
    304         mCounterSteps = 0;
    305         mEventLength = 0;
    306         mEventDelays = new float[EVENT_QUEUE_LENGTH];
    307         mPreviousCounterSteps = 0;
    308         // END_INCLUDE(reset)
    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) {
    318             // BEGIN_INCLUDE(sensorevent)
    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);
    363                 // END_INCLUDE(sensorevent)
    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) {
    421         // BEGIN_INCLUDE(saveinstance)
    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);
    427         // END_INCLUDE(saveinstance)
    428     }
    429 
    430     @Override
    431     public void onActivityCreated(Bundle savedInstanceState) {
    432         super.onActivityCreated(savedInstanceState);
    433         // BEGIN_INCLUDE(restore)
    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         }
    450         // END_INCLUDE(restore)
    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 }
    593