How-tos with Sample Code for Android

How to privacy mask controls

Custom privacy masking is a feature that matches specified IDs and regular expressions and then does character substitutions. In the example that follows, custom privacy masking converts actual values to the letters that are supplied as replacements. If custom privacy masking is set to false, it returns an empty string. You specify privacy masking in the TealeafBasicConfig.properties file that is in the assets folder of the Android application.

#Masking settings
HasMasking=true
#It can be a series of ids and regular expressions comma delimited
MaskIdList=com.tealeaf.sp:id\/EditText*,com.tealeaf.sp:id\/login.password
#If set to false it will return an empty string
HasCustomMask=true
#It will turn small letters to value given
SensitiveSmallCaseAlphabet=x
#It will turn capital letters to value given
SensitiveCapitalCaseAlphabet=X
#It will turn symbols to value given
SensitiveSymbol=#
#It will turn digits to value given
SensitiveNumber=9

How to privacy mask specific rows of a RecycleView

You can privacy mask specific rows of a RecycleView by adjusting your privacy masking settings.

  1. Inside the RecyclerAdapter class, go to the function where you set the content of the TextView you want to mask. This function is called onBindViewHolder .
@Override
public void onBindViewHolder(final ProductViewHolder productViewHolder, int i) {

     productViewHolder.productName.setText(products.get(i).name);
     productViewHolder.productDescription.setText(products.get(i).description);
    productViewHolder.productPhoto.setImageResource(products.get(i).photoId);
fun onBindViewHolder(productViewHolder: ProductViewHolder, i: Int) {
        productViewHolder.productName.setText(products.get(i).name)
        productViewHolder.productDescription.setText(products.get(i).description)
        productViewHolder.productPhoto.setImageResource(products.get(i).photoId)
    }
  1. Set the tag of the TextView that you would like to mask. In this example, we used the product name on rows 0 and 6, meaning the product name from the first and seventh rows.
if(productViewHolder.getLayoutPosition() == 0 || productViewHolder.getLayoutPosition() == 6){
     productViewHolder.productName.setTag("customTag");
}
if (productViewHolder.getLayoutPosition() === 0 || productViewHolder.getLayoutPosition() === 6) {
            productViewHolder.productName.setTag("customTag")
        }
  1. In your application folder, go to Assets and TealeafBasicConfig.properties to find the Masking Settings. Make sure that the name of your tag (in this example, customTag) is added to the MaskIdList. This indicates that every UI component that has customTag as a tag will be masked.
#Masking settings
HasMasking=true
MaskIdList=com.tealeaf.sp:id\/EditText*,com.tealeaf.sp:id\/login.password,customTag
HasCustomMask=true
SensitiveSmallCaseAlphabet=x
SensitiveCapitalCaseAlphabet=x
SensitiveSymbol=#
SensitiveNumber=9

How to privacy mask specific rows of a ListView that is reused by two activities

You can privacy mask specific rows of a ListView that is reused by two activities, similar to masking specific rows of RecycleView.

  1. In the CustomBaseAdapter class, go to the function where you set the content of the TextView you want to mask (this function is called onView).
public View getView(int position, View convertView, ViewGroup parent) {
     holder = null;

     LayoutInflater mInflater = (LayoutInflater)
             context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
     if (convertView == null) {
         convertView = mInflater.inflate(R.layout.custom_main_list_row, parent, false);
         holder = =new ViewHolder();
         holder.txtDesc = (TextView) convertView.findViewById(R.id.desc);
         holder.imageView = (ImageView) convertView.findViewById(R.id.icon);
         convertView.setTag(holder);
     } else {
         holder = (ViewHolder) convertView.getTag();
     }

     RowItem rowItem = (RowItem) getItem(position);

     final int indexRow = position;

     holder.txtDesc.setText(rowItem.getDesc());

     holder.imageView.setImageResource(rowItem.getImageId());

     if(fromSearch){
         holder.imageView.setVisibility(View.GONE);
     }
fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        var convertView = convertView
        holder = null
        val mInflater = context!!.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.custom_main_list_row, parent, false)
            holder =
                    ViewHolder()
            holder.txtDesc = convertView!!.findViewById<View>(R.id.desc) as TextView
            holder.imageView = convertView.findViewById<View>(R.id.icon) as ImageView
            convertView.tag = holder
        } else {
            holder = convertView.tag as ViewHolder
        }
        val rowItem: RowItem = getItem(position) as RowItem
        val indexRow = position
        holder.txtDesc.setText(rowItem.getDesc())
        holder.imageView.setImageResource(rowItem.getImageId())
        if (fromSearch) {
            holder.imageView.setVisibility(View.GONE)
        }
    }
  1. Set the tag of the TextView you want to privacy mask. In this example, the description of the third row from the MainActivity class.
if(indexRow == 2 && context.getClass().equals(MainActivity.class)){
     holder.txtDesc.setTag("third-row");
}
if (indexRow === 2 && context.getClass().equals(MainActivity::class.java)) {
            holder.txtDesc.setTag("third-row")
        }
  1. In your application folder, go to Assets and TealeafBasicConfig.properties to find the Masking Settings. Make sure that the name of your tag (in this example,third-row) is added to the MaskIdList. This indicates that every UI component that has third-row as a tag will be masked.
#Masking settings
HasMasking=true
MaskIdList=third-row,clicked
HasCustomMask=true
SensitiveSmallCaseAlphabet=x
SensitiveCapitalCaseAlphabet=x
SensitiveSymbol=#
SensitiveNumber=9

How to instrument Form Completion in your application

If you use OverStat in an Activity or Fragment, you must implement logFormCompletion to generate reports based on user activity within a form.

πŸ“˜

Note:

This step is completely manual and will not be automated because of the different architectures that an application can have, it is difficult to instrument. The form page can also have additional custom validation that would indicate if completion was correct or not.

Example when a form is complete and ready to submit:

Button addToCart = (Button) productView.findViewById(R.id.buttonAddCart);
addToCart.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Tealeaf.logFormCompletion(true);
val addToCart = productView.findViewById(R.id.buttonAddCart) as Button
        addToCart.setOnClickListener(object : View.OnClickListener {
            override fun onClick(view: View) {
                Tealeaf.logFormCompletion(true)

How to extract and capture application images for replay

Acoustic Tealeaf provides capabilities to capture and replay an application's local and external images.

Local images

Images that are bundled within an application can be extracted by using the Acoustic Tealeaf Android Image Extraction tool as an initial step for Tealeaf SDK integration and better replay experience. To get started, find the README.txt file under the Android Release folder: AndroidRelease/Tealeaf/AndroidImageCaptureTool/README.txt.

The tool extracts all the packaged image resources in the APK file and set an extra attribute called "tag" on the application's layout XML file for Views that contain images.

For example, android:tag="@drawable/home_background" tag is automatically added by the tool as shown. You can also manually update it to select a different image resource ID if needed:

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/home_background"
android:tag="@drawable/home_background" />

External images

These images are usually hosted on external servers, which are loaded dynamically during an application's runtime environment via network requests.

Due to Android framework limitations on capturing runtime images and performance considerations, you would need to pass external image URL paths to Tealeaf SDK by using one of the following mechanisms:

  • Use the ImageView.setTag((String) url) method

πŸ“˜

Note:

This method assumes that you are not using the ImageView's tag object in your code.

  • Use the ImageView.setTag(resourceId,(String) url) method

πŸ“˜

Note:

This method assumes that you are using the View's tag object in your code, then you can follow the following code snippet to redirect the Tealeaf tag to ImageView.setTag(ImageView.getId(), tag).

The following sample ImageView definition shows an "android:tag" attribute, which would be automatically generated by the image extraction tool, or you can manually update support image replay:

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/home_background"
android:tag="@drawable/home_background" />
  • For image replay, use a View's tag object. The following example shows how to redirect the tag object to the View's setTag:
ImageView myView = (ImageView) findViewById(R.i.d.image1);
//Potential XML tag found
Object tag = myView.getTag();

//Assuming myTag is the object you want to set for the view
if ((tag == null) || (tag != null && !(tag instance of myTag))) {

//Redirect the tag to the other setTag(View.setTag(resourceId, object) method which operated on a HashMap data structure.
if ( View.NO_ID !=view.getID() && view.getId() != 0 && tag != null) {
myView.setTag(myView.getId(), tag);
}
val myView = findViewById(R.i.d.image1) as ImageView
        //Potential XML tag found
        val tag = myView.tag

        //Assuming myTag is the object you want to set for the view
        if (tag == null || tag != null && !(tag instance of myTag)) {
            //Redirect the tag to the other setTag(View.setTag(resourceId, object) method which operated on a HashMap data structure.
            if (View.NO_ID != view.getID() && view!!.id !== 0 && tag != null) {
                myView.setTag(myView.id, tag)
            }
        }

Android auto instrumentation capability configuration files

teacuts.jar is a module that provides Android auto instrumentation capabilities. The two configuration files are shown:

  • TeaCutsAdvancedConfig.json - Advanced configuration file for enabling or disabling auto instrumentation features.
{
"TeaCutsLibraryVersion":"2.0.0.1",
"DefaultAlertDialogLayoutDelay":500
}

DefaultAlertDialogLayoutDelay property is default at 500 milliseconds. You can change this value if the replay shows a timing issue when the AlertDialog is displayed.

  • TeaCutsBasicConfig.properties - Basic configuration file for enabling or disabling auto instrumentation features.
  • Starting the 2.0.0.1 ability to auto instrument AlertDialogs
    It defaults to true. If you used manual instrumentation for AlertDialog, then you can set it to false.
AlertDialogEnabled=true
  • Starting 2.0.0.1 ability to auto instrument TextView/Button/EditText without explicit OnClick listener

πŸ“˜

Note:

This feature supports only API level 15 or above.

  • It defaults to true. If you used manual instrumentation, then you can set it to false.
TextViewEnabled=true