Overview

Skill Level: Intermediate

After you install Acoustic Experience Analytics (Tealeaf), you complete several tasks to implement Acoustic Experience Analytics (Tealeaf) functions in your application. These tasks involve modifying your application to capture controls, events, and screen views.

Prerequisites

Before you can implement Tealeaf, perform these tasks:

Step-by-step

  1. Configure the logging screen layout for Android Mobile App session replay.

    Acoustic Experience Analytics (Tealeaf) has functions to log screen layouts for screenviews of native mobile app sessions. You can replay a mobile app session in cxImpact Browser Based Replay as you would an HTML web session instead of viewing the mobile app session as a series of screen captures.

    The screen layouts of the native mobile app sessions are captured in Acoustic Experience Analytics (Tealeaf) JSON format. The screen layouts are then sent back to replay server. The replay server uses a template engine, which interprets the JSON into HTML format. You can then replay the screen layout from the native mobile app session as HTML pages in cxImpact Browser Based Replay.

    There are several advantages to using JSON data to replay mobile app session over screen captures.

    • Reduce bandwidth. Screen captures for each screenview generate relatively large image data. It not only consumes large amounts of wireless and cellular bandwidth, but it also consumes more memory inside the device. It also impacts the app performance.
    • Mask sensitive information. You cannot mask sensitive information in a screen capture. When you use JSON data to replay mobile app sessions, you can mask EditTexts by adding View IDs to the MaskIdList attribute in TealeafBasicConfig.properties.
    • Draw user interactions (UI events) onto the HTML pages that are created from the JSON data.

    TealeafBasicConfig.properties changes
    For native app session replay to be activated, you must set LogViewLayoutOnScreenTransition to true. If you do not, the library functions as it currently does.

    #Capture native layout
    LogViewLayoutOnScreenTransition=true
    
     

    During predeployment, you must perform all the replay cases to collect all the images with GetImageDataOnScreenLayout set to true. This creates a large payload sent to server that contains base64 images that are used for replay. When the application is ready to be deployed to Play Store, GetImageDataOnScreenLayout must be changed to false.

    #Current only done on ImageView
    GetImageDataOnScreenLayout=true 
    
     

    Understand your activity
    In Android, an Activity can be considered a page, which is displayed on mobile device. By default, you should record an activity that is displayed.

    For more information, see http://developer.android.com/training/basics/activity-lifecycle/starting.html.

    You can record an activity that is displayed, by placing the following information in the OnCreate method.

    // this will indicate logical page name.
    Tealeaf.logScreenview(activity, "Name", ScreenviewType.LOAD);
    // this will get layout of page after it being created.
    Tealeaf.logScreenLayoutOnCreate(activity, "Name");
    
    
    
    

    If you need to log a layout, you can enable the AutoLayout controller in your application which gives that ability to log a screen layout without making additional changes to your application code.

    The Acoustic Experience Analytics (Tealeaf) Android SDK can use the settings that are defined in TealeafLayoutConfig.json to log type 10 screen layouts for screenviews of native mobile application sessions. The AutoLayout controller also enables the application to automatically continue logging type 10 screen layouts when it resumes to the foreground. You can replay a mobile app session in cxImpact Browser Based Replay as you would an HTML web session instead of viewing the mobile app session as a series of screen captures.

    TealeafLayoutConfig.json is in the assets folder and is formatted as a JSON file.

    Edit TealeafLayoutConfig.json to configure Autolayout to log screen layouts. 

    Sub entry Description
    ScreenChange

    This entry is replacing do in Tealeaf SDK 10.3.4 (and later).

     

    Boolean value

    Indicates if the screen should be tracked or not.

    • true¬†(Capture Type 2s for this screen): Tracks the screen.
    • false (Don’t Capture Type 2s for this screen): Does not track the screen.

     

    Example: To enable tracking for the screen: 

    "ScreenChange": true
    DisplayName

    This entry is replacing screenViewName in Tealeaf SDK 10.3.4(and later).

     

    String value

    Provides the name of the screen to be shown during replay, instead of showing the view controller name or activity name. DisplayName is displayed during replay in the navigation list. If DisplayName is empty, view controller class name is used.

    For example, the DisplayName for a payments page might be “Payment Screen”.

    Example: To set the value of DisplayName to Payment Screen: 

    "DisplayName": "Payment Screen" 

     

    CaptureLayoutDelay

    This entry is replacing delay in Tealeaf SDK 10.3.4 (and later).

     

    Numeric value

    The delay in milliseconds before Tealeaf SDK takes a layout capture of a screen. Increasing the value of this setting increases the amount of time that must pass between when the layout is loaded and when the layout logging action occurs. The CaptureLayoutDelay value is used for ScreenChange and ScreenShot.

    Example: To set the delay between layout load and layout logging to 500 milliseconds:

    "CaptureLayoutDelay": 500
    ScreenShot

    This entry is replacing takeScreenShot in Tealeaf SDK 10.3.4 (and later). Note that takeScreenShot applied only for type 2 load events but ScreenShot applies to type 2, 4, 10, 11.

     

    Boolean value

    Indicates whether or not to capture screenshots on this screen.

    • true (Capture screenshots on type 2, 4, 10, 11): Takes a screen capture.
    • false (Don’t capture screenshots on this screen): Does not take a screen capture. If you do not want Tealeaf SDK to take any screenshots on a specific screen, set ScreenShot to false.

    Example: To turn on screen capturing for the screen activity:

    "ScreenShot": true

    Note: Android SDK per screen configuration is not supported in this release. Use IBMGlobalSettings to enable/disable screenshot.

    CaptureUserEvents

    This entry is replacing CaptureScreenContents in Tealeaf SDK 10.3.4 (and later).

    Boolean value

    Indicates whether or not to track user events like type 4s or 11s.

    • true (Capture type 4, 11): Tealeaf SDK resumes capturing user events (type 4, 11) on the specified screen.
    • false (Don’t capture type 4, 11): Tealeaf SDK pauses, does not capture type 4, 11 events, and based on the value of CaptureScreenVisits, captures screen load/unload events.

    Example:  To turn on user event capturing for the screen activity: 

    "CaptureUserEvents": true
    CaptureLayoutOn

    Numeric value

    The event to capture layout on. Never, or on first user gesture, or on screen change.

    • 2 (Capture Layout on screen change): Tealeaf SDK captures the layout as soon as the view controller is loaded.
    • 1 (Capture Layout on first user gesture): Tealeaf SDK captures the layout after the end user makes a first gesture on a given view controller.
    • 0 (Don’t Capture Layout): the layout is not captured.

    Example:  To capture layout on screen changes: 

    "CaptureLayoutOn": 2
    CaptureScreenshotOn

    Numeric value

    The event to capture screenshots for view controller load events: Never, or on first user gesture, or on screen changes. 

    • 2¬†(Capture screen load screenshot on screen change): Captures screen load screenshot on screen changes.¬† Tealeaf SDK captures the screenshot as soon as the view controller is loaded.
    • 1 (Capture screen load screenshot on first user gesture): Tealeaf SDK captures the screenshot after the end user makes a first gesture on a specified view controller.
    • 0 (Don’t capture screen load screenshot): Does not take a screen capture. Note that even if CaptureScreenshotOn is set to 0 and ScreenShot is true, the Tealeaf SDK continues to capture screenshots on other user events, such as type 4 and type 11. CaptureScreenshotOn applies only to screenshots on view controller load.

    Example: To capture screen load screenshot on screen changes: 

    "CaptureScreenshotOn": 2

    Note: The Android SDK does not support this property in this release.

    CaptureWebViewScreenshotOn

    Numeric value

    The event to capture the first screenshot on for a web view, if there is any.

    • 2 (Capture webview screen load screenshot on screen change): Captures the webview screen load screenshot on screen changes.¬†
    • 1 (Capture webview screen load screenshot on first user gesture): Captures the webview screen load screenshot on the first user gesture.¬†
    • 0 (Don’t capture webview screen load screenshot): Does not capture the webview screen load screenshot.¬†

    Example: To capture webview screen load screenshot on screen changes: 

    "CaptureWebViewScreenshotOn": 2

     

    NumberOfWebViews

    This entry is replacing numberOfWebviews and isWebView in Tealeaf SDK 10.3.4 (and later). If you set isWebView = false in earlier releases, you can now set NumberOfWebViews = 0. Non-zero values for NumberOfWebViews indicate the number of web views on a view controller.

     

    Numeric value

    Indicates the amount of webviews on the page. Default value is 0.

    CaptureScreenVisits

    Boolean value

    Indicates whether you want type 2 objects on pages that you set CaptureUserEvents to false. This property helps you understand how your users are using your application by showing how they get to a page. Default value is true.

    If you do not want to track screen load/unload events, set this entry to false. CaptureScreenVisits applies only if CaptureUserEvents = false. If CaptureUserEvents = true, CaptureScreenVisits is ignored and load/unload events are tracked because it doesn’t make any sense to track user events on a screen where visits are not tracked.

    AppendMapIds

    JSON

    Assigns an identifier to a target item. You can assign a readable identifier to the mid that maps to the target item. You can then configure events to fire when the identifier is encountered. You can use the same identifier for Android devices as well as iOS devices. When you assign the same identifier to your Android and iOS devices, you can create a single event in Event Manager that fires on the identifier. The event fires for both Android and iOS devices.

    Example:

      "AppendMapIds": {
     "[PWDV,0][ABOL,0][FL,0][TH,0][LL,0][TW,0][LL,1][LL,0]": {
       "mid": "LoginButton"
       },
     "ibm.com.demoapp.main_activity_login:id\/send": {
       "mid": "LoginButton"
       }
    
    
    

    Uses the mid setting to assign an identifier to two targets. The first target is for an iOS device and the second target is for an Android device. The target for both devices is identified as LoginButton. You can create a single event that fires when LoginButton is encountered in either application.

     

     The following snippet shows an example of the TealeafLayoutConfig.json file.

    {
      "AutoLayout": {
          "IBMGlobalScreenSettings":{
              "ScreenChange": true,
              "DisplayName": "",
              "CaptureLayoutDelay": 0,
              "ScreenShot": false,
              "NumberOfWebViews": 0,
              "CaptureUserEvents": true,
              "CaptureScreenVisits": true,
              "CaptureLayoutOn": 2,
              "CaptureScreenshotOn": 0
          },
          "PaymentActivity":{
              "ScreenChange": false,
              "DisplayName": "The Payment Screen",
              "CaptureLayoutDelay": 0,
              "ScreenShot": false,
              "NumberOfWebViews": 0,
              "CaptureUserEvents": false,
              "CaptureScreenVisits": true,
              "CaptureLayoutOn": 0,
              "CaptureScreenshotOn": 0
          }
      },
      "AppendMapIds": {
          "[w,9290],[v,0]": {
              "mid": "ASimpleUIView"
          },
          "tag2999999": {
              "mid": "giveAdditionalId1"
          },
          "idxPathValue": {
              "mid": "giveAdditionalId2"
          }
      }
    }
    

     

     You can also manually configure a screen to log a layout by adding the following code snippet.

    Tealeaf.logScreenLayout(activity, "Name", delayInMS);
    
    
    

    Capture dialog fragments 

    You can capture dialog fragments by using MyDialogFragment to extend DialogFragment.  

    // How to capture DialogFragment
    public class MyDialogFragment extends DialogFragment {
    
        public MyDialogFragment() {
            // Empty constructor is required for DialogFragment
            // Make sure not to add arguments to the constructor
            // Use `newInstance` instead as shown below
        }
    
        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return super.onCreateDialog(savedInstanceState);
        }
    
        public static MyDialogFragment newInstance(String title) {
            MyDialogFragment frag = new MyDialogFragment();
            Bundle args = new Bundle();
            args.putString("title", title);
            frag.setArguments(args);
            return frag;
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_dialog, container);
        }
    
        @Override
        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            Activity activity = this.getActivity();
            while (activity != null && activity.getParent() != null) {
                activity = activity.getParent();
            }
    
            // Handles case where onShow method is being overridden
            final DialogLogScreenTask dialogLogScreenTask = new DialogLogScreenTask(activity, "", this.getDialog(), Tealeaf.getCurrentSessionId());
            dialogLogScreenTask.execute();
        }
    } 

    Capture specific pages by pausing and resuming the library

    There are two ways of pausing and resuming the library:

    • Edit¬†TealeafLayoutConfig.json. This is the preferred method.
    • Call TealeafLayoutConfig.json.

    Edit TealeafLayoutConfig.json

    By default, if you are missing the IBMGlobalScreenSettings section, you have the following defaults, which capture all pages, events, and gestures, but no screenshots:

     

    "AutoLayout": {
    "IBMGlobalScreenSettings":{
              "ScreenChange": true,
              "DisplayName": "",
              "CaptureLayoutDelay": 0,
              "ScreenShot": false,
              "NumberOfWebViews": 0,
              "CaptureUserEvents": true,
              "CaptureScreenVisits": true,
              "CaptureLayoutOn": 2,
              "CaptureScreenshotOn": 0
          },
    },
    "AppendMapIds": {
      }
    
    /

    You use CaptureUserEvents to indicate that you want to pause the library and no longer capture information on a page. To pause the library, set CaptureUserEvents to false. To resume the library, set CaptureUserEvents to true. Remember that pausing the library pauses only capture of user events. If you do not want to capture screens at all, set ScreenChange to false. If you do not want to capture screen visits, set CaptureScreenVisits to false.

    For example, to capture all data except for user events on “FirstViewController”, which is the first page, enable the library to resume on “SecondViewController”, which is the second page.

    {
       "AutoLayout": {
    "IBMGlobalScreenSettings":{
            "ScreenChange": true,
             "DisplayName": "",
             "CaptureLayoutDelay": 0,
             "ScreenShot": false,
             "NumberOfWebViews": 0,
             "CaptureUserEvents": true,
             "CaptureScreenVisits": true,
             "CaptureLayoutOn": 2,
             "CaptureScreenshotOn": 0
           },
           "FirstViewController": {
            "ScreenChange": false,
             "DisplayName": "First Screen",
             "CaptureLayoutDelay": 0,
             "ScreenShot": false,
             "NumberOfWebViews": 0,
             "CaptureUserEvents": false,
             "CaptureScreenVisits": false,
             "CaptureLayoutOn": 0,
             "CaptureScreenshotOn": 0
           },
           "SecondViewController": {
            "ScreenChange": true,
             "DisplayName": "Second Screen",
             "CaptureLayoutDelay": 0,
             "ScreenShot": false,
             "NumberOfWebViews": 0,
             "CaptureUserEvents": true,
             "CaptureScreenVisits": true,
             "CaptureLayoutOn": 2,
             "CaptureScreenshotOn": 0
           }
       },
       "AppendMapIds": {
       }
    }
    
    
    

    Additionally, you can disable capture of all data except for specific pages, as shown in the following example.

    {
      "AutoLayout": {
    "IBMGlobalScreenSettings":{
           "ScreenChange": false,
            "DisplayName": "",
            "CaptureLayoutDelay": 0,
            "ScreenShot": false,
            "NumberOfWebViews": 0,
            "CaptureUserEvents": false,
            "CaptureScreenVisits": false,
            "CaptureLayoutOn": 0,
            "CaptureScreenshotOn": 0
          },
          "FirstViewController": {
           "ScreenChange": true,
            "DisplayName": "First Screen",
            "CaptureLayoutDelay": 0,
            "ScreenShot": false,
            "NumberOfWebViews": 0,
            "CaptureUserEvents": true,
            "CaptureScreenVisits": true,
            "CaptureLayoutOn": 2,
            "CaptureScreenshotOn": 0
          },
          "SecondViewController": {
           "ScreenChange": true,
            "DisplayName": "Second Screen",
            "CaptureLayoutDelay": 0,
            "ScreenShot": false,
            "NumberOfWebViews": 0,
            "CaptureUserEvents": true,
            "CaptureScreenVisits": true,
            "CaptureLayoutOn": 2,
            "CaptureScreenshotOn": 0
          }
      },
      "AppendMapIds": {
      }
    }
    
    
    

    For more information about configuring properties in TealeafLayoutConfig.json for pausing and resuming the library, see  Table 1. 

    Call TealeafLayoutConfig.json programmatically

    Use IBMGlobalScreenSettings to set values for all view controllers in TealeafLayoutConfig.json that are not documented.

    If you want to call TealeafLayoutConfig.json programmatically, go to the page where you want to start pausing the library on onStart and onResume, which is always called when an activity is displayed. 

     

       @Override
        protected void onResume() {
            Tealeaf.pauseTealeaf();
            super.onResume();
        }
    
        @Override
        protected void onStart() {
            Tealeaf.pauseTealeaf();
            super.onStart();
        }
    

    Next, go to the page where you want to start resuming the library on onPause, which is always called when an activity is displayed.

         @Override
        protected void onPause() {
            Tealeaf.resumeTealeaf(true);
            super.onPause();
        }
    

     

  2. Integrate Acoustic Tealeaf and IBM MobileFirst Platform Foundation with Eclipse

    IBM MobileFirst Platform Foundation is IBM’s MobileFirst Platform for developing both hybrid and native applications on multiple mobile platforms. IBM MobileFirst Platform Foundation provides an Eclipse plug-in called “IBM MobileFirst Platform Foundation Developer Studio” to help Developers create Mobile Apps more productively. For logging activities on your application, you might want to integrate the Acoustic Experience Analytics (Tealeaf) library with your MobileFirst “Hybrid” application.

    Procedure

    1. Install and setup Eclipse.
    2. Install and setup your MobileFirst development environment with Eclipse. For more information on how to setup your development environment, see https://mobilefirstplatform.ibmcloud.com/tutorials/en/foundation/7.1/setting-up-your-development-environment/setting-up-the-mobilefirst-development-environment/.
    3. In Eclipse, import your MobileFirst project into Eclipse. Right-click Project Explorer and select Import > Import > Existing Projects into Workspace; then, select your MobileFirst project.
    4. Copy the tealeaf.js and defaultConfiguration.js file to the \apps\demoApp\common\js folder. Static assets are located in \apps\demoApp\common and are shared across all hybrid applications. Additionally, .css and image files are located in \apps\demoApp\common.
    5. Add a reference to tealeaf.js and defaultConfiguration.js in your index.html file so that it loads when the application starts.
    6. Copy the contents of \apps\demoApp\common to the Replay server. For most installations, copy the contents to the \Apps\App_name\App_version\www\default on the Replay server. The files are referenced during Replay.
    7. Build the project for your preferred mobile platform. As part of the build process, MobileFirst generates a separate project.
    8. Test the new application by running the MobileFirst project that was created in step 7.

    Note: In order for replay to work with your MobileFirst application, make sure that the contents of \apps\demoApp\common are copied to the Replay server. For most Acoustic Experience Analytics (Tealeaf) installations, copy the contents to the \Apps\App_name\App_version\www\default on the Replay server.

    Extending the MobileFirst Java classes for Acoustic Experience Analytics (Tealeaf)

    After your MobileFirst development environment is configured, you need to extend the Java classes for integration with Acoustic Experience Analytics (Tealeaf).

    Procedure

    1. Extend the org.apache.cordova.CordovaChromeClient class using the following snippet.
      public class MyChromeClient extends CordovaChromeClient {
          /** 
            * Constructor.
            * 
            * @param cordova
            */
          public MyChromeClient(CordovaInterface cordova) {
              super(cordova);
          }
          /** 
            * Constructor.
            * 
            * @param ctx 
            * @param app 
            */
          public MyChromeClient(CordovaInterface ctx, CordovaWebView app) {
              super(ctx, app);
          }
          /** 
           * {@inheritDoc} 
           */
          public final void onConsoleMessage(final String message, final int lineNumber, final String sourceID) {
              super.onConsoleMessage(message, lineNumber, sourceID);
              LogInternal.log("WebView: " + message + " -- From line " + lineNumber + " of" + sourceID);
          }
      }
      
    2. Extend the Extendorg.apache.cordova.CordovaWebView class using the following snippet.
      public class MyWebView extends CordovaWebView {
          private PropertyName webViewId;
          private MyWebViewClient myWebViewClient;
          private MyChromeClient myChromeClient;
          /**    
            * @param context     
            */
          public MyWebView(Context context) {
              super(context);
              init();
          }
          /**
           * @param context
           * @param attrs
           */
          public MyWebView(Context context, AttributeSet attrs) {
              super(context, attrs);
              init();
          }
          /**
            * @param context
            * @param attrs
            * @param defStyle
            */
          public MyWebView(Context context, AttributeSet attrs, int defStyle) {
              super(context, attrs, defStyle);
              init();
          }
          /**
            * Get webview id.
            *
            * @return Webview id.
            */
          public final PropertyName getWebViewId() {
              return webViewId;
          }
          /**
            * Set webview id.
            *
            * @param webviewId Webview id.
            */
          public final void setWebViewId(final PropertyName webviewId) {
              this.webViewId = webviewId;
          }
          /**
            * {@Override}
            */
          public void loadUrl(final String url) {
              loadUrl(url, null);
          }
          /**
            * {@inheritDoc}
            */
          public final void loadUrl(final String url, final Map < String, String > extraHeaders) {
              Tealeaf.setTLCookie(url);
              super.loadUrl(url, extraHeaders);
          }
          public MyWebViewClient getMyWebViewClient() {
              return myWebViewClient;
          }
          public MyChromeClient getMyChromeClient() {
              return myChromeClient;
          }
          /**
            * Initializes WebView.
            */
          private void init() {
              CordovaInterface cordovaInterface = (CordovaInterface) this.getContext();
              this.myWebViewClient = new MyWebViewClient(cordovaInterface, this);
              this.myChromeClient = new MyChromeClient(cordovaInterface, this);
              this.setWebViewClient(this.myWebViewClient);
              this.setWebChromeClient(this.myChromeClient);
              this.setWebViewId(Tealeaf.getPropertyName(this));
              // This sets up the javascript bridge to communicate from web to native to collect data
              this.addJavascriptInterface(new JavaScriptInterface(this.getContext(),
                  getWebViewId().getId()), "tlBridge");
          }
      }
      
      
      
    3. Extend the Extend org.apache.cordova.CordovaWebViewClient class using the following snippet.
      public class MyWebViewClient extends CordovaWebViewClient {
          public MyWebViewClient(CordovaInterface cordova, CordovaWebView view) {
              super(cordova, view);
          }
          /**
            * {@inheritDoc}
            */
          @Override
          public void onPageFinished(final WebView view, final String url) {
              view.loadUrl("javascript:TLT.registerBridgeCallbacks([
                  "            + " {
                      enabled: true,
                      cbType: 'screenCapture',
                      cbFunction: function()
                      {
                          tlBridge.screenCapture();
                      }
                  }, "            + "
                  {
                      enabled: true,
                      cbType: 'messageRedirect',
                      cbFunction: function(data)
                      {
                          tlBridge.addMessage(data);
                      }
                  }]);
          ");
      }
      }
      
       
    4. Integrate your WebView into your CordovaActivity with the following snippet.
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          //Tealeaf Integration
          MyWebView myWebView = new MyWebView(this);
          MyWebViewClient myWebViewClient = myWebView.getMyWebViewClient();
          MyChromeClient myChromeClient = myWebView.getMyChromeClient();
          super.init(myWebView, myWebViewClient, myChromeClient);
          WL.createInstance(this);
          WL.getInstance().showSplashScreen(this);
          WL.getInstance().initializeWebFramework(getApplicationContext(), this);
      }
      
       
    5. Integrate the Acoustic Tealeaf ScreenLayout API call into your CordovaActivity with the following snippet. 
      /**
        * The IBM MobileFirst Platform calls this method after its initialization is complete
          and web resources are ready to be used.
        */public void onInitWebFrameworkComplete(WLInitWebFrameworkResult result) {
          if (result.getStatusCode() == WLInitWebFrameworkResult.SUCCESS) {
              super.loadUrl(WL.getInstance().getMainHtmlFilePath());
              //Tealeaf Integration
              Tealeaf.logScreenLayout(this, this.getClass().getName(), 30000);
          } else {
              handleWebFrameworkInitFailure(result);
          }
      }
      
       
    6. Add the standard Acoustic Tealeaf callbacks with the following snippet.
      //Tealeaf Integration
      public void onResume() {
          // Handle Tealeaf during onResume event
          Tealeaf.onResume(this, this.getClass().getName());
          super.onResume();
      }
      public void onPause() {
          // Handle Tealeaf during onPause event
          Tealeaf.onPause(this, this.getClass().getName());
          super.onPause();
      }
      public void onDestroy() {
          // Handle Tealeaf during
          onResume event
          Tealeaf.onDestroy(this, this.getClass().getName());
          super.onDestroy();
      }
      
       
  3. Implement screenViews.

    For pages in which the state or context can be switched without re-rendering the page, Acoustic Experience Analytics (Tealeaf) segments the data between states by using a screenView object.

    For example, if a page contains multiple tabs, each of which represents a different stage in a checkout process, you instrument each tab in the page as a distinct screenView.

    To implement a screenView for a page, follow these steps.

    1. If you are extending from UICActivity, set a logicalPageName to indicate the use of the activity. Otherwise, logicalPageName is set to the name of the class of the activity.
    2. If the prior step is not complete, call Tealeaf.logScreenview and pass the logicalPageName. You must also indicate if the page being loaded and unloaded is optional. For example:
      Tealeaf.logScreenview(activity, logicalPageName, ScreenviewType.LOAD);
      Tealeaf.logScreenview(activity, logicalPageName, ScreenviewType.UNLOAD);
      
       
  4. Set up a target page on your web server.

    The target page acknowledges that events are captured. See Quick start server configuration.

    Set the target page’s address in the TealeafBasicConfig.properties configuration file under the key PostMessageUrl.

    For more information, see Configuration file. 

  5. Configure data privacy by specifying fields that are blocked or masked during capture.

    Acoustic Experience Analytics (Tealeaf) provides mechanisms for masking or blocking sensitive customer information, such as credit card numbers, from being transmitted and captured by Acoustic Experience Analytics (Tealeaf). Through the Android SDK, you can specify the fields that need to be blocked or masked in your web application.

    When applied, data privacy ensures that these data elements are never transmitted to Acoustic Experience Analytics (Tealeaf).

    Note: Due to changes in how client framework data is submitted to Acoustic Experience Analytics (Tealeaf) for capture, the best method for masking or blocking sensitive data is to apply the filtering through the capturing client framework. While other Acoustic Experience Analytics (Tealeaf) features to manage data privacy can be deployed, they are not easy to implement on the new format of data captured from the client frameworks. Acoustic Experience Analytics (Tealeaf) recommends using the methods referenced below. 

    For more information, see Configuration file.

  6. Configure sessionization for Android applications on the client.

    The Android SDK auto-generates a session ID if one is not provided. This session ID is used to identify the session on the Acoustic Experience Analytics (Tealeaf) server.

    Acoustic Experience Analytics (Tealeaf) injects cookies to create session in the Acoustic Experience Analytics (Tealeaf) system.

    Note: When an Android native or hybrid application is placed into background, the library flushes the collected data and sleeps, instead of disabling it. This happens unless the session expired due to the session timeout property. The timeout property is indicated with SessionTimeout in TealeafBasicConfig.properties. The default value for this property is 30 minutes. After a 30-minute timeout, a new session identifier is created.

    There are two ways to configure sessionization; either through TLTSID provide by Acoustic Experience Analytics (Tealeaf), or through customer session ID, called JSESSIONID. Both methods function as a unique session identifier within the Android SDK environment for Acoustic Experience Analytics (Tealeaf) to track on customer sessions. CookieParam can be set to use customer session ID or JSESSIONID.

    The following is a typical setting in TealeafBasicConfig.properties using customer session ID.

    #
    Sessionization settings on customer cookies
    CookieUrl = http: //www.sample.com
        CookieDomain = .sample.com
    CookiePath = /
    CookieParam = JSESSIONID
    CookieExpires = false
    SessionTimeout = 30
    SessoinTimeoutKillSwitch = false
    
     

    In this example, the cookie expires 30 minutes from current time. When the session timeout occurs, Android SDK retrieves the new cookie from your application server and posts the rest of request to application server using this new acquired cookie in request header. PCA groups all the used JSESSIONIDs into one single session even though the JSESSIONID was consistently changing. When using cookies generated from customer application server, SessoinTimeoutKillSwitch can be set to true or false. Setting the SessoinTimeoutKillSwitch to false means the session timeout user does not go to recheck on KillSwitchUrl to see if it is responding.

    Network traffic used in application contains requests only

     Android SDK uses cookies to add values to the TLFConfigurableItems.properties file.

    Uses session ID generated by Acoustic Experience Analytics (Tealeaf) Android SDK
    Android SDK uses cookies to add the following values in TLFConfigurableItems.properties.

    • CookieUrl is for url of site that is posted and getting cookie to sessionize on.
    • CookieParam is the parameter that has a session id.
    • CookiePath is the path of the cookie.
    • CookieDomain is the domain that the cookie belongs to.
    • CookieSecure is to add a secure cookie that can only be posted to an https url that has a valid certificate.
    • CookieExpiresFormat can have the date format of ASCTIME, RFC1036, or RFC1123, which will have an expiration date of current time + session timeout indicated in the variable below.
    • SessionTimeout is session timeout is in minutes. When this value expires a new session id is created.

    An example follows.

    #
    Sessionization settings
    CookieUrl = http: //m.ibm.com
        CookieParam = TLTSID
    CookiePath = /
    CookieDomain = .ibm.com# Whether you want to create a secure cookie which can only be sent using a
    https url in PostMessageUrl.
    CookieSecure = false# Valid date formats: ASCTIME, RFC1036, RFC1123
    CookieExpiresFormat = ASCTIME
    # When post is sent, expiration of cookie will be current time + session timeout# Session timeout is in minutes
    SessionTimeout = 30
    
     

    Note: It is important to first call your server to get first cookie to sessionize on, which is automatically obtained when you enable the kill switch URL on the application. This is used to aggregate all the data on capture.

  7. Configure Tealeaf to put session identifiers in cookies.

    Acoustic Experience Analytics (Tealeaf) needs all the requests to have the session id to be placed in the cookies of the request. This enables theAcoustic Experience Analytics (Tealeaf) CX PCA to collect all the resources together in a single captured session.

    Add the following cookie into the GET and POST requests to enable the PCA to listen to requests and responses.

    httpClient.setRequestProperty("Cookie", 
    Tealeaf.getTLCookie(Tealeaf.getCurrentSessionId()));
    
     

    Extend org.apache.http.impl.client.DefaultHTTPClient

    If you do not use the Acoustic Experience Analytics (Tealeaf) extended TLDefaultHttpClient class, you must add the following code to the following classes.

    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.HttpParams;
    /**
     * @author ohernandez
     * 
     */
    public class TLDefaultHttpClient extends DefaultHttpClient {
        /**
         * 
         */
        public TLDefaultHttpClient() {
            super();
            this.init(null);
        }
        /**
         * @param params Http parameters.
         */
        public TLDefaultHttpClient(final HttpParams params) {
            super(params);
            this.init(null);
        }
        /**
         * @param params Http parameters.
         * @param sessionId Tealeaf session id.
         */
        public TLDefaultHttpClient(final HttpParams params,
            final String sessionId) {
            super(params);
            this.init(sessionId);
        }
        /**
         * @param conman ClientConnectionManager.
         * @param params Http parameters.
         */
        public TLDefaultHttpClient(final ClientConnectionManager conman,
            final HttpParams params) {
            super(conman, params);
            this.init(null);
        }
        /**
         * @param sessionId Tealeaf session id.
         */
        private void init(final String sessionId) {
            final TLHttpRequestInterceptor tlHttpRequestInterceptor =
                new TLHttpRequestInterceptor(sessionId);
            this.addRequestInterceptor(tlHttpRequestInterceptor);
            this.addResponseInterceptor(new TLHttpResponseInterceptor(tlHttpRequestInterceptor));
        }
    }
    
     

    Extend org.apache.http.HttpRequestInterceptor

    This class is used to inject session id as a cookie and additional headers that the Acoustic Experience Analytics (Tealeaf) system uses. 

    import java.io.IOException;
    import java.util.Map.Entry;
    import org.apache.http.HttpException;
    import org.apache.http.HttpRequest;
    import org.apache.http.HttpRequestInterceptor;
    import org.apache.http.protocol.HttpContext;
    import android.webkit.CookieManager;
    import com.tl.uic.Tealeaf;
    import com.tl.uic.util.LogInternal;
    /**
     * @author ohernandez
     */
    public class TLHttpRequestInterceptor implements HttpRequestInterceptor {
        private String url;
        private final String sessionId;
        /**
         * Constructor.
         */
        public TLHttpRequestInterceptor() {
            super();
            this.sessionId = null;
        }
        /**
         * Constructor. 
         * @param sessionId Tealeaf session id.
         */
        public TLHttpRequestInterceptor(final String sessionId) {
            this.sessionId = sessionId;
        }
        /**
         * Get url of the request. 
         * @return Url of the request.
         */
        public final String getUrl() {
            return url;
        }
        /**
         * Url of the request. 
         * @param url Url of the request.
         */
        public final void setUrl(final String url) {
            this.url = url;
        }
        /**
         * {@inheritDoc}
         */
        public final void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
            try {
                this.url = request.getRequestLine().getUri();
                if (!request.containsHeader(Tealeaf.TLF_HEADER)) {
                    request.addHeader(Tealeaf.TLF_HEADER, "device (Android) Lib/" +
                        Tealeaf.getLibraryVersion());
                }
                if (!request.containsHeader(Tealeaf.TLF_PROPERTY_HEADER)) {
                    request.addHeader(Tealeaf.TLF_PROPERTY_HEADER,
                        Tealeaf.getHttpXTealeafProperty());
                }
                if (Tealeaf.getAdditionalHeaders() != null) {
                    for (final EntryString, < String > entry:
                 
           Tealeaf.getAdditionalHeaders().entrySet()) {
                        request.addHeader(entry.getKey(), entry.getValue());
                    }
                }
                final StringBuffer cookies = new StringBuffer(Tealeaf.getTLCookie(this.sessionId));
                if (this.getUrl() != null) {
                    final String extistingCookies = CookieManager.getInstance().
                    getCookie(this.getUrl());
                    if (extistingCookies != null) {
                        cookies.append(';');
                        cookies.append(extistingCookies);
                    }
                }
                request.addHeader("Cookie", cookies.toString());
                LogInternal.log("Session cookie:" + cookies.toString());
            } catch (final Exception e) {
                Tealeaf.logException(e);
            }
        }
    }
    
    
    

    Extend org.apache.http.HttpResponseInterceptor

    This class is used to get information to fill out a Acoustic Experience Analytics (Tealeaf) connection object.

    import java.io.IOException;
    import java.util.Date;
    import org.apache.http.HttpException;
    import org.apache.http.HttpResponse;
    import org.apache.http.HttpResponseInterceptor;
    import org.apache.http.protocol.HttpContext;
    import com.tl.uic.TLFCache;
    import com.tl.uic.Tealeaf;
    import com.tl.uic.util.LogInternal;
    /**
     * @author ohernandez
     *
     */
    public class TLHttpResponseInterceptor implements HttpResponseInterceptor {
        private final TLHttpRequestInterceptor tlHttpRequestInterceptor;
        private final Date startTime;
        private final long initTime;
        /**
         * Constructor.
         * 
         * @param tlHttpRequestInterceptor TLHttpRequestInterceptor used.
         */
        public TLHttpResponseInterceptor(final TLHttpRequestInterceptor tlHttpRequestInterceptor) {
            this.tlHttpRequestInterceptor = tlHttpRequestInterceptor;
            this.startTime = new Date();
            this.initTime = TLFCache.timestampFromSession();
        }
        /**
         * {@inheritDoc}
         */
        public final void process(final HttpResponse response,
            final HttpContext context) throws HttpException, IOException {
            try {
                final Date endTime = new Date();
                final Date startLoad = new Date();
                final long loadTime = (new Date()).getTime() - startLoad.getTime();
                final long responseTime = endTime.getTime() - this.startTime.getTime();
                Tealeaf.logConnection(this.tlHttpRequestInterceptor.getUrl(), response,
                    this.initTime, loadTime, responseTime);
            } catch (final Exception e) {
                LogInternal.logException(e);
            }
        }
    }
    
    
    
     
  8. Configure your generated session IDs to be used when sessions are enabled or new sessions start.

    You must get your generated session ID and use it when you enable or start a new session in Android SDK.

    // If enabling of Android Logging Framework use
    Tealeaf.enable();
    Tealeaf.enable("your session id");
    // If Android Logging Framework is enabled and a new session is to be created use
    Tealeaf.startSession();
    Tealeaf.startSession("your session id");
    
     
  9. Instrument your application for Android gestures.

    You can capture gestures that the users make on your Android application. Several types of gestures are captured, as described in the Gesture events captured section of this step. 

    To enable gestures for an Activity class, you modify the Activity class. For example, you modify the MainActivity.java file and call the Tealeaf.dispatchTouchEvent method inside dispatchTouchEvent or onTouchEvent method. Additionally, for any Activity class that you want Gesture logging, you edit the TealeafBasicConfig.properties file and set the SetGestureDetector property to true.

    Touch event methods
    You add your variables to one of these methods:

    • onTouchEvent – use this method if your activity is not using a customized gesture view.
    • dispatchTouchEvent – use this method if your activity is using a customized gesture view.

    If your application uses a customized gesture view, onTouchEvent does not detect the gestures because they are already captured by the customized gesture view. If you are using a customized gesture view, you must use dispatchTouchEvent.

    You can use either the onTouchEvent or the dispatchTouchEvent. If you use both, the gestures are captured twice.

    To instrument your application for Android gestures, follow these steps:

    1. To use the Android gestures module, you must compile the Android Support Library in the build.gradle file:
      compile 'com.android.support:support-v4:xx.x.x'
      
      
      
    2. Open the MainActivity.java file for your application.
    3. Override either the dispatchTouchEvent or the onTouchEvent method, depending on how you are using gestures in your application:
      ¬†If your application is…

      Then…

      using a customized gesture view Override the dispatchTouchEvent

      publc boolean dispatchTouchEvent( MotionEvent e) {
       Tealeaf.dispatchTouchEvent(this, e);
       return super.dispatchTouchEvent(e);
       }
      
       
      not using a customized gesture view Override the onTouchEvent

      publc boolean onTouchEvent( MotionEvent e) {
       Tealeaf.dispatchTouchEvent(this, e);
       return super.onTouchEvent(e);
       }
      
       

    Example:

    This example shows the code snippets that are added in this task for an application that does not use a customized gesture view:

    import com.tl.uic.Tealeaf;
    import com.tl.uic.app.UICActivity;
    import android.os.Bundle;
    import android.view.MotionEvent;
    public class MainActivity extends UICActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Tealeaf.logScreenLayout(this, this.getLogicalPageName(), 1000);
        }
        public boolean dispatchTouchEvent(MotionEvent e) {
            Tealeaf.dispatchTouchEvent(this, e);
            return super.dispatchTouchEvent(e);
        }
    }

    4. Enable gesture capture by modifying the TLFConfiguratableItems.properties file. 

    5. Edit the the TealeafBasicConfig.properties file. 

    6. Set SetGestureDetector to true.

    7. Save and exit the TealeafBasicConfig.properties file.

     

    Gesture events captured

    Gestures that are used to select items in an application or to adjust views in the application are captured by Acoustic Experience Analytics (Tealeaf).

    Note: The default usability request for traffic type=MOBILE_APP in native and hybrid apps is taps and swipes, never clicks. Thus, if you are requesting usability data for the MOBILE_APP traffic type, request metrics for gestures (Gestures Рtype 11), such as taps, double taps, tap and hold, pinch, and spread. Otherwise, heatmaps in the overlay will not show data.
    If you are using the webview traffic type for hybrid apps, configure Tealeaf to capture all gestures.

    Tap gestures
    This table lists and describes the tap gestures that are captured from web and mobile apps. Note:The arrows that illustrate the direction of a swipe or pinch gesture are not supported by the Internet Explorer browser.

    Gesture name Description Image displayed in Replay
    Tap

     
    This gesture is a one-finger gesture.

    For a tap gesture, one-finger taps and lifts from the screen in 1 location.

     

     singletap
     Tap and Hold

     
    This gesture is a one-finger gesture.

    For a Tap and Hold gesture, one-finger presses and stays on the screen until information is displayed or an action occurs. Note: The response to a Tap and Hold gesture can vary from one application to another. For example, a Tap and Hold gesture might display an information bubble, magnify content under the finger, or present the user with a context menu.

     

     
     Double tap  

     
    This gesture is a one-finger gesture.

    For a double tap gesture, one-finger taps twice in close succession in 1 location of the screen.

     doubletap

    Swipe gestures

    This table lists and describes the swipe gestures that are captured from web and mobile apps:

     Gesture name Description  Image displayed in Replay
     Swipe vertically  

    This gesture is a one-finger gesture.

    For a swipe vertically gesture, one-finger:taps and holds in 1 location of screen,
    continues to engage screen while it moves up or down
    lifts from the screen in different location. Note: The initial tap becomes lighter in color, while the destination is highlighted by a darker color

     
     Swipe horizontally

    This gesture is a one-finger gesture.

    For a swipe horizontally gesture, one-finger:

    1. taps and holds in 1 location of screen,
    2. continues to engage screen while it moves left or right
    3. lifts from the screen in different location.

    Note: The initial tap becomes lighter in color, while the destination is highlighted by a darker color

     

     

    Resize gestures

    This table lists and describes the resize gestures that are captured from web and mobile apps:

     Gesture name Description Image displayed in Replay
     Pinch open  

    Sometimes referred to as a spread gesture, this is a two-finger gesture.

    For a pinch open gesture, 2 fingers:

    1. tap and hold in 1 location of the screen,

    2. maintain contact with the screen while the fingers move apart from each other in any direction,

    3. lift from the screen at a new location.

    Note: Accompanying arrows indicate the direction (open or close) of the pinch

     Pinch close  

    This gesture is a two-finger gesture.

    For a pinch close resize gesture, 2 fingers:

    1. tap and hold in 1 location on the screen,

    2. maintain contact with the screen while the fingers move toward each other,

    3. lift from the screen at a new location.

     

    Unresponsive gesture events captured

    Unresponsive gestures are gestures that do not respond when a user tries to select items in an application or tries to adjust views in the application. Like other gesture events, unresponsive gestures are captured by Acoustic Experience Analytics (Tealeaf).

    Unresponsive gestures are displayed graphically in BBR as orange outlined icons accompanied by a circled “X” . The circled “X” denotes that the gesture was unresponsive. For example, if a double tap gesture did not yield a response in the mobile application, then at replay time that gesture is displayed with the following icon in BBR:

    The following table lists the unresponsive gestures that are captured from web and mobile apps and shows the images that are displayed in BBR:

     Unresponsive Gesture  Image displayed in Replay
    Tap gestures  
     Unresponsive tap  
    Unresponsive double tap  
    Unresponsive tap and hold   
    Swipe gestures  
     Swipe vertically
    Note: Accompanying arrows indicate the direction of the swipe.
    Swipe horizontally

    Note: Accompanying arrows indicate the direction of the swipe.

    Resize gestures  
    Pinch open

    Note: Accompanying arrows indicate the direction of the pinch.

    Pinch close

     

    Note: Accompanying arrows indicate the direction of the pinch.

     

  10. Add exception logging to your application.

    Exceptions are the way that a system or framework communicates to the application that something is wrong. Acoustic Experience Analytics (Tealeaf) provides two mechanisms to log exceptions.

    • You can manually log exceptions by using the logException API.
    • Or, the Acoustic Experience Analytics (Tealeaf) SDK automatically logs uncaught exceptions.

    The exception information can be used for analytics.

    Automatically log exceptions
    When your application throws an uncaught exception, Acoustic Experience Analytics (Tealeaf) Android SDK records the stack trace and state of the device at the time the exception was thrown. Acoustic Experience Analytics (Tealeaf) Android SDK sends the crash information to your target servers for processing.

    Manually log exceptions
    In the Android SDK, you modify your catch block to report caught exceptions to the target server for processing. When you modify your catch block, you get the full stack trace and same device information that Acoustic Experience Analytics (Tealeaf) collects for fatal crashes.

    This table shows the method that is used to log exceptions and describes the parameters that are used in the method:

    Method  Parameters
    public static Boolean logException
        (final Throwable exception, final HashMap < String, String >
            data, final Boolean unhandled)
    
    
    

     

    Where:

    • @param exception – the exception to be logged.
    • @param data – the value to be given to event.
    • @param unhandled – whether the exception is unhandled.
    • @return – whether exception was logged.

     

    To add exception logging to your application, follow these examples:

    1. Determine the method for which you want to log exceptions. For example, you have a method:
      public void clientMethod1( ) {
      }
    2. Optional: Add the exception method to the method for which you want to log the exceptions. Add @try, @catch, and the Tealeaf.logException(e, data, false) method to handle the exception:
      public void clientMethod1() {
          try {
              int[] array = new int[1];
              int i = array[2]; // Index out of bound exception
          }
          Catch(Exception e) {
              HashMap < String, String > data = new HashMap < String, String > ();
              data.put("extraMessage", "custom value1");
              Tealeaf.logException(e, data, false);
          }
      }
      
       
  11. Configure automatic geolocation logging in your application.

    You can log geolocation information for the users of your applications. You can configure automatic geolocation logging when the application starts or you can use APIs to log geolocation information at specific places in your application. Replay and reporting of geolocation data is not available currently.

    Automatic logging
    You configure automatic logging with the TealeafBasicConfig.properties file and the manifest file.

    1. To give permission to the device to log geolocation data, add the following to the Android manifest file. 
      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" > 
      
       

      and

      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
      
      
      
    2. Configure the following items in TealeafBasicConfig.properties:
     Property  Description
    LogLocationEnabled 

    Use this property to enable geolocation logging. Values are:

    True – logging is enabled
    False – logging is not enabled

    LogLocationTries¬† Use this property to set the number of times the device tries to get the geolocation information when the device’s signal is weak.
    LogLocationTimeout¬† Use this property to set the amount of time between retries for the device to get the geolocation information when the device’s signal is weak.

    For example, when the device signal is weak you want the device to try 3 times to get the geolocation signal and for the device to wait 30 seconds between retries, you set the configurable items to these values:

    #
    Auto Geolocation logging enabled or not
    LogLocationEnabled = true
    LogLocationTries = 3
    LogLocationTimeout = 30
    
     

     APIs

     There are three APIs you can use to manually enable geolocation information logging in your application:

    • public static Boolean logGeolocation() – Use this API at a specific point in your application to log geolocation only at that point.
    • public static GeoLocation logGeolocation(final Boolean getGeolocation) – Use this API at a specific point in your application to return a geolocation object that contains latitude, longitude, and accuracy values.
    • public static Boolean logGeolocation(final int logLevel) – Use this API at a specific point in your application to log geolocation information based on log level.

    Disable geolocation capture in the Web application

    Note: The UI Capture library typically tracks geolocation information within the Web application. When geolocation is enabled in a hybrid mobile application, the geolocation tracking feature in the Web application should be disabled and the application should use the native SDK capture the geolocation information.

    To disable geolocation capture in the Web application:

    1. Locate the UI Capture library configuration. The configuration is typically at the end of the UI Capture SDK file as part of the call to the TLT.init() API.
    2. Locate the geolocation configuration section in the replay module configuration object (modules.replay.geolocation).
    3. If the geolocation configuration section does not exist, the feature is disabled automatically. If the section does exist, then ensure that the enabled setting is set to false. The following example shows the geolocation section for the UI Capture SDK with geolocation disabled.
      geolocation: {
          enabled: false,
          triggers: [{
              event: "load"
          }]
      },
      
       

    Logging geolocation information in your app automatically

    Before you begin this task you need to know:

    • The number of times you want the application to try to get the geolocation information when the signal is weak.
    • The amount of time, in seconds, that you want the application to wait before again trying to get the geolocation information.
    1. In your application in Eclipse, open the TealeafBasicConfig.properties file.
      1. Set LogLocationEnabled to true.
      2. Set LogLocationTries to the number of times you want the application to try log the geolocation information.
      3. Set LogLocationTimeout to the number of seconds that the device should retry when the signal is weak.
      4. Save and exit the file.
    2. In your application in Eclipse, open the AndroidManifest.xml file.
      1. Add the following line to the file.
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" 
      2. Save and exit the file.

Expected outcome

Your Acoustic Experience Analytics (Tealeaf) SDK for Andorid is implemented.

Next, set up theAcoustic Experience Analytics (Tealeaf) and Windows based servers to capture and process data that is submitted. See Quick start server configuration.