Once you have instrumented your Android application with the Android SDK, you can also use the APIs exposed by the SDK to customize the data for your application that appears in the Controller UI. The following sections show you how to use the Android API to customize your instrumentation.

Because the Android Agent stores data about events in a local buffer before reporting the information, we recommend you use the APIs with discretion.

Collect Additional Types of Data

The Instrumentation class has additional methods to allow you to extend the kinds of application data you can collect and aggregate using Mobile RUM. There are six basic kinds of extensions that you can create:

Extension

Description

Specification

Where Data is Displayed in the Controller UI

Info pointsHow often a method is invoked, and how long it takes to run.
  • Data is numeric
  • Names must consist of
    alphanumeric characters and/or spaces
Custom timersAny arbitrary sequence of events within your code timed, even spanning multiple methods.
  • Data is numeric
  • Metric names must consist of
    alphanumeric characters and/or spaces
Custom metricsAny integer-based data you wish to collect.
  • Data is numeric
  • Metric names must consist of
    alphanumeric characters and/or spaces
User data

Any string key/value pair you think might be useful. This data is included with all listed instrumentation types until it is cleared. 

  • Any data type
  • Metric names have no restrictions
BreadcrumbsThe context of a crash.
  • Any data type
  • Metric names have no restrictions
User interactionsCaptured when users press buttons, click on lists, and select text.
  • Any data type
  • Metric names have no restrictions

When you have set up info points, custom timers, custom metrics, and/or user data, the Mobile Agent packages that data in a mobile beacon. Normally, the beacon is transmitted when the instrumented application sends an HTTP request or when the application starts after a crash, but if custom data has been collected and neither of those events has occurred for at least 5 minutes, the custom data is sent at that time.

Info Points

Information points allow you to track how your own code is running. You can see how often a method is invoked, how long it takes to run, and if an exception is thrown. The simplest way to set up an information point is to use the @InfoPoint annotation. For example:

@InfoPoint
 public void infoPointMethod(String arg1, int arg2, long value) {
   System.out.println("Executing infoPointMethod!");
 }
JAVA

You can also do this manually, using the CallTracker interface. For example, to collect information on the downloadImage method, you could use code similar to this:

private void downloadImage(URL url) {
     CallTracker tracker =
          Instrumentation.beginCall("com.example.android.awesomeapp.ImageDownloader", "downloadImage")
              .withArguments(url);
     try {
          //download image.
          tracker.reportCallEnded()
     } catch(Exception e) {
         //handle exception thrown
         tracker.reportCallEndedWithException(e);
     }
 }
JAVA

This information appears in the Custom Data view of the Controller UI.

Custom Timers

Custom timers allow you to time any arbitrary sequence of events within your code, even spanning multiple methods, by using startTimer and stopTimer.

public class MyActivity extends Activity {
   @Override
   protected void onStart(){
       Instrumentation.startTimer("Time Spent on MyActivity");
       //your code here.
   }
 
   @Override
   protected void onStop(){
       Instrumentation.stopTimer("Time Spent on MyActivity");
       //your code here.
   }
 }
JAVA

The methods startTimer(String) and stopTime(String) can be called from different threads. Calling startTimer again with the same name value resets a named timer.

This information appears in the Custom Data view of the Controller UI.

Custom Metrics

Any integer-based data can be passed to the Android Agent. The first parameter to the reportMetric call is the name you want the metric to appear under in the Controller UI. The metric name should only contain alphanumeric characters and spaces. Illegal characters are replaced by their ASCII hex value.

For example, to track the number of times your users click the checkout button in your UI, you could use code similar to this.

findViewById(R.id.checkout_button).setOnClickListener(new View.OnClickListener(){
      @Override
      public void onClick(View view){
          //run your checkout routine.
          Instrumentation.reportMetric("Checkout Count", 1);
      }
 });
JAVA

This information appears in the Custom Data view of the Controller UI.

Custom User Data

You can set and later remove any string key/value pair you think might be useful with the following methods:

  • setUserData(key, value, success, error)
  • removeUserData(key)

Once this is set, user data will continue to be sent along with any instrumentation for network requests, sessions, or crashes for the rest of the current run of the application. You can remove any previously set user data on a per-key basis. Alternatively, you can remove previously set user data for all keys at once using clearAllUserData

Parameters

The following table describes the parameters:

NameTypeDescription
keystringThe key identifying the key-value pair.
valuestringThe value associated with the key.
successfunctionThe user-defined callback for successful cases.
errorfunctionThe user-defined callback for failed cases.

Example

The code example below shows how to set user data with the SDK API:

void onUserLoggedIn(String userid) {
    Instrumentation.setUserData("User ID", userid);
    ...
}
JAVA

The code example below shows how to remove user data with the SDK API:

void onUserLoggedIn(String userid) {
    Instrumentation.removeUserData("User ID", userid);
    ...
}
JAVA

This information is available in Network Request Analyze and is added to any crash snapshot. Keys and values are limited to 2048 characters each.

You can also set user data with values of other types (Long, Boolean, Double, Date) using the following methods:

Breadcrumbs

Breadcrumbs allow you to situate a crash in the context of your user's experience. Set a breadcrumb when you want to capture a series of events. If your application crashes at some point in the future, the breadcrumb will be displayed along with the crash report.

There are two ways of leaving breadcrumbs:

  • Crash Reports Only
  • Modal

Crash Reports Only

Using this method means that breadcrumbs are reported in crash reports only.

public static void leaveBreadcrumb(java.lang.String breadcrumb)
JAVA

Modal

Using this method lets you fine tune where the breadcrumbs are reported, either only in crash reports or in crash reports and sessions.

public static void leaveBreadcrumb(java.lang.String breadcrumb, int mode)
JAVA

Where mode is either:

  • CRASHES_ONLY
  • CRASHES_AND_SESSIONS

If the  breadcrumb is over 2048 characters, it is truncated.  If it is empty, no breadcrumb is recorded. Each crash report displays the most recent 99 breadcrumbs.

Add a Crash Reporting Callback

You may want to make crash report information (that the Android Agent collects) available to other parts of your code, for example, to Google Analytics. To enable passing on summary crash information, you can set up a crash report runtime callback. To get a callback when the Android Agent detects and then reports a crash, implement the following interface in your code:

public interface CrashReportCallback {
    void onCrashesReported(Collection<CrashReportSummary> summaries);
}
JAVA

The Android Agent sends a collection of crash report summaries instead of an individual callback in the event that there is more than one crash, producing multiple crash report summaries.

The method onCrashesReported is invoked during the next initialization of the Android Agent after a crash has occurred.

This callback is invoked on your app's UI thread, so any work should be done on a separate work thread.

Each CrashReportSummary has the following properties:

public class CrashReportSummary {
    public final String crashId;
    public final String exceptionClass;
    public final String exceptionMessage;
}
JAVA


If you are sending the information to another analytics tool, such as Google Analytics, we recommend to include all three properties: exceptionClass and exceptionMessage are useful for quick identification of the crash, and crashId is useful to look up the crash in the Controller UI.

For example, to print the crash information to Android's logger, you could implement a CrashReportCallback class like this:

public static class MyCrashReportCallback implements CrashReportCallback {
    @Override
    public void onCrashesReported(Collection<CrashReportSummary> summaries) {
        for (CrashReportSummary crash : summaries) {
            Log.e("MyApp", "Crash Detected: " + crash.exceptionClass + " : " + crash.exceptionMessage + " (" + crash.crashId + ")");
        }
    }
}
JAVA

You set your callback as using the AgentConfiguration object:

final AgentConfiguration config = AgentConfiguration.builder()
        .withAppKey(appKey)
        .withContext(context)
        .withCrashCallback(new MyCrashReportCallback())
        .build();
JAVA

Your callback is invoked after a crash, during the next initialization, on the main thread. For more information, see the latest JavaDocs for the complete Android SDK API.

Disable Crash Reporting

Crash reporting is enabled by default, but you can manually disable crash reporting through the instrumentation configuration. If you are using other crash reporting tools, you might disable crash reporting to minimize conflicts and optimize the crash report results. 

You can disable crash reporting by configuring the instrumentation with the crashReportingEnabled property as shown in the following:

let config = ADEumAgentConfiguration(appKey: <#EUM_APP_KEY#>);
config.crashReportingEnabled = false;
ADEumInstrumentation.initWith(config);
JAVA

Report Errors and Exceptions

The string message used to construct a new throwable used for reportError should not use unique string such as hashes, StackTraceID or ThreadID. Including those will create an infinite number of Error Group which will impact performance.

To add unique error strings and values, you have two data collection options:

  • Add a string value as a breadcrumb. If the event/error, you are reporting has a single value attached, add the value to leaveBreadcrumb. See Breadcrumbs
  • Add string name and integer values. If the event/error, you are reporting has both name and integer values attached, add the values to reportMetric. See Custom Metrics.


You can report exceptions using the method reportError from the Instrumentation class. Reported exceptions will appear in session details.

You can also set one of the severity levels below for an issue. With the severity level, you can filter errors in the Code Issues Dashboard or Code Issues Analyze.

  • ErrorSeverityLevel.INFO
  • ErrorSeverityLevel.WARNING
  • ErrorSeverityLevel.CRITICAL

The example below uses the API to report possible exceptions and sets the severity level to ErrorSeverityLevel.CRITICAL (critical) when writing to a file.

private void writeToFile(String filePath, String data) {
    try {
        OutputStream outputStream = new FileOutputStream(filePath);
        Writer outputStreamWriter = new OutputStreamWriter(outputStream);
        outputStreamWriter.write(data);
        outputStreamWriter.close();
    } catch (IOException e) {
        Log.e("Exception", "File write failed: " + e.toString());
        Instrumentation.reportError(e, ErrorSeverityLevel.CRITICAL);
    }
}
JAVA

Disable the Agent to Stop Sending User Data to the Collector

You can disable the agent to stop sending all data to the collector while the agent is initialized and running. For example, you can disable the agent if your app has an option for users to opt-out of monitoring for privacy reasons.

shutdownAgent

The shutdownAgent call stops outgoing data to the collector, and does not persist data on the device.

Instrumentation.shutdownAgent();
JAVA
  • The call only stops the traffic out of the agent.
  • Once the agent has been initialized, the call cannot be removed, and a license will have been consumed.
  • If you want to make this state permanent for a device, add code in UserDefaults to save the state and use that flag to conditionally initialize the agent in your code.

restartAgent

To re-enable the agent and reverse shutdownAgent, use restartAgent.

Instrumentation.restartAgent();
JAVA
  • The call will respect the server side calls that can remotely shutdown the agent in a similar way. 
  • The call is only in effect while the app is running.
  • The call will be ignored if the agent has been remotely disabled.
  • If the call is removed from memory and the app restarts, or the device is rebooted, the agent will be initialized as normal. 

Configure Hybrid Support

By default, the Android Agent instruments Android WebViews by injecting the JavaScript Agent into WebViews. See Hybrid Application Support for an overview and an explanation of how it works. 

Runtime Configuration for Hybrid Support

The code example below disables the injection of the JavaScript Agent. If the client receives a false for this flag, then the JavaScript Agent will be disabled. Thus, WebViews will not be instrumented, and Ajax requests will not be monitored.

Instrumentation.start(
    AgentConfiguration.builder()
        .withAppKey(getString(R.string.app_key))
        .withContext(applicationContext)
        .withJSAgentInjectionEnabled(false)
        .build())
JAVA

The injection occurs during the creation of a new WKWebView. So, if a WKWebView is created when this flag is set to false, that particular WKWebView won't be instrumented even if the flag is subsequently set to true.

The collection and reporting of Ajax calls are disabled by default. To enable the injection and the collection and reporting of Ajax calls, pass true to the method jsAgentEnabled in the instrumentation configuration as shown below.

Instrumentation.start(
    AgentConfiguration.builder()
    .withAppKey(getString(R.string.app_key))
    .withContext(applicationContext)
    .withJSAgentAjaxEnabled(true)
    .build())
JAVA

Programmatically Control Sessions

By default, a mobile session ends after a period of user inactivity. For example, when a user opens your application, the session begins and only ends after the user stops using the application for a set period of time. When the user begins to use the application again, a new session begins. 

Instead of having a period of inactivity to define the duration of a session, however, you can use the following API to programmatically control when sessions begin and end:

void startNextSession()
JAVA

When you call the method startNextSession from the Instrumentation class, the current session ends and a new session begins. The API enables you to define and frame your sessions so that they align more closely with business goals and expected user flows. For example, you could use the API to define a session that tracks a purchase of a product or registers a new user. 

Excessive use of this API will cause sessions to be throttled (excessive use is >10 calls per minute per Android Agent, but is subject to change). When not using the API, sessions will fall back to the default of ending after a period of user inactivity.

Example of a Programmatically Controlled Session

In the code example below, the current session ends and a new one begins when the check out is made.

public void checkoutCart(){
    if (currentCartItems!=null && currentCartItems.size()>0){
        CheckoutTask checkoutReq = new CheckoutTask();
        checkoutReq.execute(getEndpoint() + "cart/co");
        currentCartItemsMap.clear();
        convertItemsMaptoList();
        Instrumentation.startNextSession();
    } else {
        displayToast("There are no items in the cart");
    }
}
JAVA

Start and End Session Frames

You can use the SessionFrame API to create session frames that will appear in the session activity. Session frames provide context for what the user is doing during a session. With the API, you can improve the names of user screens and chronicle user flows within a business context. 

Use Cases

The following are common use cases for using the SessionFrame API:

  • One Activity performs multiple functions and you want more granular tracking of the individual functions.
  • A user flow spans multiple activities or user interactions. For example, you could use the API to create the session frames "Login", "Product Selection", and "Purchase" to chronicle the user flow for purchases.
  • You want to capture dynamic information based on user interactions to name session frames, such as an order ID.

SessionFrame API

The table below lists the three methods you can use with session frames.

Class

Method

Description

Instrumentation
static SessionFrame startSessionFrame(String sessionFrameName)
JAVA

Use this to start and name your session frame.

Naming session frames enable you to easily identify and track the frames in the Sessions Dialog.

SessionFrame
static void updateName(String updatedSessionFrameName)
JAVA

Use this to start and name your session frame.

Naming session frames enable you to easily identify and track the frames in the Sessions Dialog.

SessionFrame
static void end()
JAVA
End the session frame. You call this method from the SessionFrame object returned from startSessionFrame.

Session Frame Example

In the following example, the ShoppingCartActivity class uses the SessionFrame API to track user activity during the checkout process. 

public class ShoppingCartActivity extends Activity {
 
  SessionFrame checkoutSessionFrame;
 
  public void onCheckoutCartButtonClicked() {
    // The user starts the checkout by clicking the checkout button.
    // This may be after they have updated the quantities of items in the cart, etc.
    checkoutSessionFrame = Instrumentation.startSessionFrame("Checkout");
  }
 
  public void onConfirmOrderButtonClicked() {
    // Once they have confirmed payment info and shipping information, and they
    // are clicking the "Confirm" button to start the backend process of checking out,
    // we may know more information about the order itself, such as an order ID.
    checkoutSessionFrame.updateName("Checkout: Order ID " + orderId);
  }
 
  public void onProcessOrderCompleted() {
    // Once the order is processed, the user is done "checking out", so we end the session frame.
    checkoutSessionFrame.end();
    checkoutSessionFrame = null;
  }
 
  public void onCheckoutCanceled() {
    // If the user cancels or returns to the cart, you'll want to end the session frame also, or else it will be
    // left open and appear to have never ended.
    checkoutSessionFrame.end();
    checkoutSessionFrame = null;
  }
}
JAVA

Use a Custom HTTP Library

The Android Agent automatically detects network requests when the underlying implementation is handled by any one of the supported network libraries. To have the Android Agent detect requests from a custom library, add request tracking code to your application manually, using the HttpRequestTracker interface.  

Supported Network Libraries

The libraries below cover the great majority of Android network requests. In some cases, however, mobile applications use custom HTTP libraries.  

  • HttpURLConnection
  • HttpsURLConnection
  • HttpClient classes
  • OkHttp
  • OkHttp3
  • ch.boye.httpclientandroidlib

To set headers to allow correlation with server-side processing, use the ServerCorrelationHeaders class.  

Add Request Tracking

To add request tracking manually, you use an HttpRequestTracker object to tell the agent when the request begins and when it ends and to report fields of the response to the agent.

Start Tracking a Request

To begin tracking an HTTP request, use an instance of the following interface.

You must initialize the agent using the Instrumentation.start method before using this interface.

public interface HttpRequestTracker {
    public Exception getException();
    public HttpRequestTracker withException(Exception e);
  
    public String getError();
    public HttpRequestTracker withError(String error);
  
    public int getResponseCode();
    public HttpRequestTracker withResponseCode(int responseCode);
  
    public Map<String, List<String>> getResponseHeaderFields();
    public HttpRequestTracker withResponseHeaderFields(Map<String, List<String>> responseHeaderFields);
  
   /**
    * Stops tracking an HTTP request.
    *
    * Immediately after receiving a response or an error, set the appropriate fields and call this method to
    * report the outcome of the HTTP request. You should not continue to use this object after calling this
    * method -- if you need to track another request, obtain a new instance.
    */
    public void reportDone();
}
JAVA

Example

Given a request snippet like this:

public byte[] sendRequest(URL url) throws HttpException {
    try {
        // implementation omitted
        return responseBody;
    } catch (UnderlyingException e) {
        throw new HttpException(e);
    }
}
JAVA


Adding the tracker could look like this:

public byte[] sendRequest(URL url) throws HttpException {
    HttpRequestTracker tracker = Instrumentation.beginHttpRequest(url);
    try {
        // implementation omitted
        tracker.withResponseCode(theResponseCode)
               .withResponseHeaderFields(theResponseHeaderFields)
               .reportDone();
        return responseBody;
    } catch (UnderlyingException e) {
        tracker.withException(e)
               .reportDone();
        throw new HttpException(e);
    }
}
JAVA

Attach a Custom User Data Property to a Network Request

You can attach a custom user data property to a specific network request by adding user data to HttpRequestTracker.

public HttpRequestTracker withUserData(String key, String value)
public HttpRequestTracker withUserLong(String key, Long value)
public HttpRequestTracker withUserBoolean(String key, Boolean value)
public HttpRequestTracker withUserDouble(String key, Double value)
public HttpRequestTracker withUserDate(String key, Date value)
JAVA

Example

public byte[] sendRequest(URL url) throws HttpException {
    HttpRequestTracker tracker = Instrumentation.beginHttpRequest(url);
    try {
        // implementation omitted
        tracker.withResponseCode(theResponseCode)
               .withResponseHeaderFields(theResponseHeaderFields)
               .withUserData("key", "value")
               .withUserLong("number", 100)
               .withUserBoolean("boolean", true)
               .withUserDouble("double", 1.1234)
               .withUserDate("date", 1609488000)
               .reportDone();
        return responseBody;
    } catch (UnderlyingException e) {
        tracker.withException(e)
               .reportDone();
        throw new HttpException(e);
    }
}
JAVA

Enable Server-Side Correlation

To enable correlation between your request and server-side processing, add specific headers to outgoing requests that the server-side agent can detect.

This is done automatically for standard HTTP libraries.

public class ServerCorrelationHeaders {
    public static Map<String, List<String>> generate();
}
JAVA


You must:

  1. Call the generate method and set the generated headers before sending a request to the backend.
  2. Report back the response headers, using data from the withResponseHeaderFields field.

Override the Request/Response Content-Length

  • You can generally obtain the content lengths of the network request and response by passing the headers with HttpRequestTracker.withRequestHeaderFields() and HttpRequestTracker.withResponseHeaderFields().
  • If for some reason this does not work for your custom HTTP tracking (i.e. the network library doesn't populate those fields until its being transmitted), then you can still report the request and response content lengths using HttpRequestTracker.withRequestContentLength(Long length) and HttpRequestTracker.withResponseContentLength(Long length).

  • For example, you want to track a request that has a byte array of content. You can report the request content length by passing the size of the byte array:

byte[] requestContent;
HttpRequestTracker tracker;
tracker.withRequestContentLength(requestContent.size());
JAVA

Customize the Agent Configuration

To customize the behavior of the agent itself, you pass the AgentConfiguration object to the Instrumentation.start method. The AgentConfiguration object allows you to:

  • Point to an on-premises EUM Server
  • Enable logging
  • Custom-set the application name, which is useful if you deploy the same application binary with different package names to different geographic areas. This ensures that all the data ends up being processed under the same name.
  • Ignore HTTP requests internal to your application that are not used for network requests
  • Configure the agent to use your custom HTTP library to send its beacons

Syntax

See the latest JavaDocs for more information.

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withCollectorURL(collectorURL*) // The URL of the EUM Server(on-prem)
      .withCompileTimeInstrumentationCheck(true) // Set to false if you are using features of the SDK only, like custom HTTP support, but not to instrument your app.      
      .withLoggingEnabled(true)//set default INFO logging. Tagged "AppDynamics".
      .withApplicationName(applicationName)//set a custom app name
      .withExcludedUrlPatterns(excludedUrlPatterns) // Set excluded url regex patterns for http tracking
      .withCollectorChannelFactory(collectorChannelFactory()) // The custom HTTP implementation to use
      .build());
JAVA

*For a list of URLs of EUM servers, see Cisco AppDynamics SaaS Domains and IP Ranges.

Configure the Agent to Use Custom HTTP Library

The Android Agent uses HTTP to deliver beacons. If you want the agent use your custom HTTP library to deliver beacons:

  1. Implement a class that extends the following abstract class:

    public abstract class CollectorChannel {
        private URL url;
        private int connectTimeout;
        private int readTimeout;
        private Map<String, List<String>> requestProperties = new HashMap<String, List<String>>();
        private String requestMethod;
      
        public void setURL(URL url) {
            this.url = url;
        }
      
        public URL getURL() {
            return url;
        }
      
        public void setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
        }
      
        public int getConnectTimeout() {
            return connectTimeout;
        }
      
        public void setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
        }
      
        public int getReadTimeout() {
            return readTimeout;
        }
      
        public void addRequestProperty(String property, String value) {
            if (!requestProperties.containsKey(property)) {
                requestProperties.put(property, new ArrayList<String>());
            }
            requestProperties.get(property).add(value);
        }
      
        public Map<String, List<String>> getRequestProperties() {
            return Collections.unmodifiableMap(requestProperties);
        }
      
        public void setRequestMethod(String requestMethod) {
            this.requestMethod = requestMethod;
        }
      
        public String getRequestMethod() {
            return requestMethod;
        }
      
        public abstract OutputStream getOutputStream() throws IOException;
      
        public abstract InputStream getInputStream() throws IOException;
      
        public abstract int getResponseCode() throws IOException;
      
        public abstract Map<String,List<String>> getHeaderFields() throws IOException;
    }
    JAVA


    This interface is loosely based on HttpURLConnection.

  2. Implement a version of the CollectorChannelFactory interface: 

    public interface CollectorChannelFactory {
     
        /**
         * Returns a new instance of CollectorChannel.
         *
         * If you want to supply a custom CollectorChannel, implement this interface, and return
         * an instance of your concrete implementation of CollectorChannel from this method.
         */
        public CollectorChannel newCollectorChannel();
    }
    JAVA

    The implementation of newCollectorChannel should return a new instance of your implementation of CollectorChannel.

  3. Pass the CollectorChannelFactory to the AgentConfiguration object.

Capture User Interactions

You can enable the agent to track certain user interactions. Once user interactions have been captured, you can sort sessions by UI event and view the UI event in the timeline of the session waterfall. 

You can capture when users do one or all of the following:

  • Press buttons
  • Select a text field
  • Click a list item

Security and Privacy Concerns

The interaction capture mode is disabled by default for security and privacy reasons as user interactions may contain sensitive information. This potential security and privacy issue may be compounded if you enable both the capturing of UI interactions and screenshots. 

Enable User Interaction Capture Mode

To enable user interaction capture mode, pass the capture mode to the method withInteractionCaptureMode() from an AgentConfiguration object. The instrumentation code example below configures the Android Agent to capture all the supported types of user interactions.

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withInteractionCaptureMode(InteractionCaptureMode.All)
      .build());
JAVA


You can also configure the Android Agent to only capture one type of user interaction:

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withInteractionCaptureMode(InteractionCaptureMode.ButtonPressed)
      .build());
JAVA

Programmatically Take Screenshots

By default, mobile screenshots are enabled on the agent-side but disabled on the Controller-side. To programmatically take manual screenshots, you must enable screenshots in the Controller UI and add the following API:

Instrumentation.takeScreenshot();
JAVA


For example, you can take a screenshot after loading a UI element to view how the element is displayed to your users:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_spinner_with_toast);
   spinner = (Spinner) findViewById(R.id.spnOptions);
   btnSpinnerVal = (Button) findViewById(R.id.btnSpinnerValue);
   loadSpinner();
   Instrumentation.takeScreenshot();
}
JAVA


Disable Screenshots

You can disable screenshots from the Controller UI or with the Android SDK. To disable screenshots with the Android SDK, use the method withScreenshotsEnabled(false) from the AgentConfiguration class:

Instrumentation.start(AgentConfiguration.builder()
     .withAppKey("<EUM_APP_KEY>")
     .withContext(getApplicationContext())
     .withScreenshotsEnabled(false)
     .build());
}
JAVA

Block/Unblock Screenshots

You can also use the Android SDK to block screenshots from being taken during the execution of a code block. This just temporarily blocks screenshots from being taken until you unblock screenshots. This enables you to stop taking screenshots in situations where users are entering personal data, such as on login and account screens.

You use the methods Instrumentation.blockScreenshots() and Instrumentation.unblockScreenshots() to block and unblock screenshots. If screenshots are disabled through AgentConfiguration.Builder.withScreenshotsEnabled(true) or through the Controller UI, these methods have no effect. You can call Instrumentation.screenshotsBlocked() to check if screenshots are being blocked.

The following example demonstrates how you could use the API to block and unblock screenshots when displaying user information.

public class UserAccountActivity extends Activity {
  ...
  public void displayCustomerAccount() {
    // Check to see if screenshots are blocked
    if (! Instrumentation.screenshotsBlocked()) {
        // If screenshots aren't blocked, block them before showing customer details
        Instrumentation.blockScreenshots();   
    }
    // Code to display customer details
    displayAccount(this.user);
    // After you're done, unblock screenshots
    Instrumentation.unblockScreenshots();
  }
  ...
}
JAVA


Transform URLs for Network Requests

When your application makes network requests, you may not want to report URLs containing sensitive information to the EUM Server. You can instead transform the network request URL before reporting it or ignore it altogether:

  1. Implement a network request callback that modifies or ignores specific URLs. 
  2. Register the network request callback in the initialization code.

Implement the Network Request Callback

The callback that modifies or ignores specific URLs is an implementation of the interface below. The method onNetworkRequest is synchronous, we recommend that you return from the function quickly.

public interface com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    boolean onNetworkRequest(HttpRequestTracker httpRequestTracker);
}
JAVA

Transforming URLs

The onNetworkRequest method, in general, should follow the steps below to transform URLs:

  1. Identify specific URLs using techniques such as regex or pattern matching.
  2. Modify the url property of the HttpRequestTracker object.
  3. Assign a valid URL to the url property. Modifying other properties of the HttpRequestTracker object will be ignored.
  4. Return true.

The first step is optional as you could choose to transform the URLs of all network requests. 

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        URL urlMask = new URL("http://networkrequest-mask.com/");
        httpRequestTracker.withURL(urlMask);
        return true;
    }
}
JAVA


In general, however, you would want to identify and transform URLs that contain sensitive information. For example:

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        String urlString = httpRequestTracker.getURL().toString();
        try {
            URL url = new URL("http://customer-account.com/");
            if (urlString.contains("accountInfo")) {
                // Change the URL for calls to Facebook
                httpRequestTracker.withURL(url);
                return true;
            }
        } catch (MalformedURLException e) {
            return false;
        }
        return true;
    }
}
JAVA


Ignoring URLs

If the onNetworkRequest method returns false, the beacon is dropped. The general process for ignoring beacons is:

  1. Identify specific URLs using techniques such as regex or pattern matching.

  2. Return false.

You could also ignore all network requests with the following implementation of onNetworkRequest:

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        return false;
    }
}
JAVA


In general, you would identify network requests that you didn't want to monitor and return false to ignore the network request. For example:

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        String urlString = httpRequestTracker.getURL().toString();
        try {
            URL url = new URL("http://socialnetworksite.com/");
            if (urlString.contains("avatar")) {
                // Ignore calls for avatars
                return false;
            }
        } catch (MalformedURLException e) {
            return false;
        }
        return true;
    }
}
JAVA


Register the Network Request Callback

After implementing the callback, you register it in the initialization code. When the Android Agent is ready to create a network request beacon, it will first call the callback with an HttpRequestTracker object.

To register the callback in the initialization code:

Instrumentation.start(AgentConfiguration.builder()
        .withContext(getApplicationContext())
        .withAppKey("<EUM_APP_KEY>")
        .withNetworkRequestCallback(new myNetworkRequestCallback())
        .build());
JAVA

Enable Logging and Set Logging Level

You use the method withLoggingLevel of the class AgentConfiguration to enable logging and set the logging level. You can set logging to one of the following levels:

  • LOGGING_LEVEL_NONE
  • LOGGING_LEVEL_INFO
  • LOGGING_LEVEL_VERBOSE

Use verbose logging only for troubleshooting. Make sure to disable for production.

Example:

AgentConfiguration config = AgentConfiguration.builder()
config.withAppKey(appKey)
config.withContext(context)
 .withLoggingLevel(Instrumentation.LOGGING_LEVEL_VERBOSE)
        .build();
    Instrumentation.start(config);
JAVA

Android SDK Documentation

For the complete SDK API documentation, see the latest JavaDocs or the previous versions listed below: