ForgeRock Developer Experience

Set up device profiling in Android apps

This page shows how to detect the DeviceProfileCallback, how to collect the device profile, and how to send the profile to AM.

AM includes collected device information in its audit logs by default.

To configure AM to filter out this information and ensure no personally identifiable information (PII) is written to the audit logs, refer to Prevent auditing of device data.

Handle a device profile callback

If an authentication journey uses the device profile node, the SDK returns DeviceProfileCallback to collect device attributes.

You use various SDK methods to handle the callback.

Use the default device profile callback

Call the DeviceProfileCallback.execute() method to collect the device profile:

callback.execute(context, new FRListener<Void>() {
@Override
public void onSuccess(Void result) {
// call next

}

@Override
public void onException(Exception e) {
}
});

Customize the device profile callback

  1. Extend the callback that you want to override, providing two constructors that match the parent constructors.

  2. Annotate the constructor with the @Keep annotation.

  3. Override the default implementation:

    public class MyCustomDeviceProfileCallback extends DeviceProfileCallback {
    
     public MyCustomDeviceProfileCallback() {
     }
    
     @Keep
     public MyCustomDeviceProfileCallback(JSONObject jsonObject, int index) {
         super(jsonObject, index);
     }
    
     @Override
     public void execute(Context context, FRListener<Void> listener) {
         super.execute(context, listener);
     }
    }
  4. Register the callback:

    CallbackFactory.getInstance().register(MyCustomDeviceProfileCallback.class);

Manually collect device profile information

Instead of responding to a device callback, your app can get the device profile using default collectors.

You can also modify the default collectors. A set of collectors are predefined.

The FRDevice uses the default predefined collector to collect device profile:

The following code collects the device profile using the default collectors:

FRDeviceCollector.DEFAULT.collect(context, listener);

Default collectors

Collector name Description

FRDeviceCollector

Main collector that includes other collectors and provides a collector version.

BluetoothCollector

Collect BLE support information of the device.

BrowserCollector

Collect browser information of the device; specifically, the User-Agent.

CameraCollector

Collect camera information of the device.

DisplayCollector

Collect display information of the device.

HardwareCollector

Collect hardware-related information, such as the number of CPUs, number of active CPUs, and so on.

LocationCollector

Collect location Information of the device.

NetworkCollector

Collect network information of the device.

PlatformCollector

Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model.

TelephonyCollector

Collect telephony information of the device.

Sample device profile

{
  "identifier": "d50cdb5ce8d055a3-86bd35e1b975a14d76b40940112c2380264c8efd",
  "metadata": {
    "platform": {
      "platform": "Android",
      "version": 31,
      "device": "emulator64_x86_64_arm64",
      "deviceName": "sdk_gphone64_x86_64",
      "model": "sdk_gphone64_x86_64",
      "brand": "google",
      "locale": "en_US",
      "timeZone": "America/Vancouver",
      "jailBreakScore": 1
    },
    "hardware": {
      "hardware": "ranchu",
      "manufacturer": "Google",
      "storage": 5951,
      "memory": 1968,
      "cpu": 4,
      "display": {
        "width": 1080,
        "height": 2148,
        "orientation": 1
      },
      "camera": {
        "numberOfCameras": 2
      }
    },
    "browser": {
      "userAgent": "Mozilla/5.0 (Linux; Android 12; sdk_gphone64_x86_64 Build/SPB5.210812.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36"
    },
    "bluetooth": {
      "supported": true
    },
    "network": {
      "connected": true
    },
    "telephony": {
      "networkCountryIso": "us",
      "carrierName": "T-Mobile"
    }
  },
  "location": {
    "latitude": 37.4219711,
    "longitude": -122.0849955
  },
  "lastSelectedDate": 1634068456582,
  "alias": "sdk_gphone64_x86_64"
}

Create a custom collector

  1. Create a custom "DeviceCollector" class that implements the DeviceCollector interface:

    public class MyCustomMetadataCollector implements DeviceCollector {
    
     private static final List<DeviceCollector> COLLECTORS = new ArrayList<>();
    
     static {
         //Pick from existing Collector or implement your own collector
         COLLECTORS.add(new PlatformCollector());
         COLLECTORS.add(new NetworkCollector());
         COLLECTORS.add(new TelephonyCollector());
     }
    
     @Override
     public String getName() {
         return "metadata";
     }
    
     @Override
     public void collect(Context context, FRListener<JSONObject> listener) {
         collect(context, listener, new JSONObject(), COLLECTORS);
     }
    }
  2. Use FRDeviceCollectorBuilder to add your custom Collector:

    public class MyCustomDeviceProfileCallback extends DeviceProfileCallback {
    
     public MyCustomDeviceProfileCallback() {
     }
    
     @Keep
     public MyCustomDeviceProfileCallback(JSONObject jsonObject, int index) {
         super(jsonObject, index);
     }
    
     @Override
     public void execute(Context context, FRListener<Void> listener) {
         FRDeviceCollector.FRDeviceCollectorBuilder builder = FRDeviceCollector.builder();
         if (isMetadata()) {
             builder.collector(new MyCustomMetadataCollector());
         }
         if (isLocation()) {
             builder.collector(new LocationCollector());
         }
    
         builder.build().collect(context, new FRListener<JSONObject>() {
             @Override
             public void onSuccess(JSONObject result) {
                 setValue(result.toString());
                 Listener.onSuccess(listener, null);
             }
    
             @Override
             public void onException(Exception e) {
                 Listener.onException(listener, null);
             }
         });
     }
    }

Device profile attributes

By default, the ForgeRock SDK collects the following device attributes:

Attribute Value

identifier

A unique ID for the device.

To learn more about the device identifier, refer to Uniquely identifying devices.

location

The location of a device (longitude and latitude values).

This is configured in the node and requires user permissions.

metadata

Metadata for the device, including:

platform

The device OS, such as Android or iOS.

deviceName

The name of the device.

locale

The locale of the device, such as en.

timeZone

The time zone of the device, such as Africa/Johannesburg.

brand

The brand of the device, such as Apple.

jailBreakScore

A value between 0 and 1 that denotes the tampering level for a device.

Obtain user permission for the device location

Your app requires the user’s authorization to access the device location.

For information about how to request the authorization, refer to Request location permissions.

Implement default jailbreak/rooted device detection

The FRRootDetector class is responsible for analyzing whether the device is tampered.

The class analyzes the device by using multiple device tamper detectors, and returns the highest score in the range between 0.0 to 1.0 from all the detectors.

You can customize the metadata.platform.jailBreakScore with Root Detector.

Sample using default tamper detection:

// The DEFAULT Detector uses all available detectors in the SDK to determine if the device is rooted.
RootDetector rootDetector = FRRootDetector.DEFAULT;

// Check if device is rooted
double rootedScore = rootDetector.isRooted(context)

// Evaluate the result
if rootedScore == 0.0 {
    // Detectors score result with 0.0, likely device is not rooted
}
else if rootedScore <= 0.5 {
    // Some of the detectors returned a possible positive result that indicates the device might be rooted
}
else {
    // Most of the detectors returned a possible positive result that indicates the device is likely rooted
}

Customize jailbreak/rooted detection

The SDKs provide a set of industry-standard detectors that allow you to customize the detectors to use.

Sample custom tamper detection code:

// Using Builder to choose two detectors
RootDetector rootDetector = FRRootDetector.builder()
        .detector(new SuCommandDetector())
        .detector(new RootAppDetector())
        .build();

// Get result
double rootedScore = rootDetector.isRooted(context)

Implement custom detectors

You can provide your own detectors by implementing the RootDetector interface on Android. The interface represents the definition of an individual analyzer for detecting when the device is rooted or jailbroken.

Each detector determines whether the device is rooted or jailbroken. Each collector returns a result score as a Double, within the range of 0.0 to 1.0.

Sample custom detector code:

// Add custom detector to RootDetector
RootDetector rootDetector = FRRootDetector.builder()
        .detectors(FRRootDetector.DEFAULT_DETECTORS)
        .detector(new RootDetector() {
            @Override
            public double isRooted(Context context) {
                return 0;
            }
        })
        .build();

// Get result
double rootedScore = rootDetector.isRooted(context);

More information