Skip to content

Conversation

@oschwald
Copy link
Member

No description provided.

@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

Add CI/CD infrastructure:
- ci.yml: Build SDK, run tests, lint (detekt/ktlint), build sample app
- codeql.yml: Security analysis for Java/Kotlin (weekly schedule)
- zizmor.yml: Workflow security scanning
- dependabot.yml: Daily dependency updates with 4-day cooldown

All workflows pass zizmor validation with:
- Actions pinned to SHA hashes
- Minimal permissions (permissions: {} at top level)
- persist-credentials: false on all checkouts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@oschwald oschwald force-pushed the greg/eng-3380 branch 3 times, most recently from b8c6bed to 47ca150 Compare December 10, 2025 20:23
oschwald and others added 14 commits December 10, 2025 12:40
Foundation commit establishing the Android device fingerprinting SDK:

- SDK architecture with singleton DeviceTracker, SdkConfig builder pattern
- Restructured DeviceData model with nested types for organized signal collection:
  - StoredIDs (mediaDrmID, androidID placeholders)
  - BuildInfo (comprehensive Build.* fields)
  - DisplayInfo (screen metrics with refresh rate)
  - HardwareInfo (CPU cores, memory, storage)
  - InstallationInfo (install timestamps, source)
  - LocaleInfo (language, country, timezone)
  - Placeholder types for future: GpuInfo, AudioInfo, SensorInfo, CameraInfo,
    CodecInfo, NetworkInfo, SystemSettings, BehaviorInfo
- DeviceDataCollector populating build, display, hardware, installation, locale
- Network layer with Ktor HTTP client
- Sample app demonstrating SDK usage
- Unit tests for SdkConfig builder validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement persistent device identifier collection:
- MediaDRM ID: Hardware-backed ID using Widevine, persists through factory reset
- Android ID (SSAID): App-scoped ID that persists across reinstalls

Both IDs gracefully handle unavailable scenarios (emulators, custom ROMs).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive tests for DeviceDataCollector covering:
- Build info (fingerprint, manufacturer, model, ABIs)
- Display info (dimensions, density, refresh rate)
- Hardware info (CPU cores, memory)
- Installation info (install times, version)
- Locale and timezone
- StoredIDs integration

Also fixes ktlint formatting in StoredIDsCollector.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect GPU fingerprint using EGL/OpenGL ES:
- Renderer name (e.g., "Adreno 640")
- Vendor (e.g., "Qualcomm")
- OpenGL ES version
- Supported extensions

Creates a temporary PBuffer surface to query GPU info without
requiring a visible window. Gracefully returns null when
OpenGL ES is unavailable (emulators, some devices).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect audio configuration via AudioManager:
- Native output sample rate (e.g., "48000")
- Output frames per buffer (e.g., "256")

These values indicate the device's native audio path configuration
and can be useful for device fingerprinting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Enumerate all device sensors via SensorManager:
- Sensor name and vendor
- Type, version, and specifications
- Power consumption and resolution

The complete sensor list provides a unique fingerprint of the device's
hardware capabilities.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Enumerate cameras via CameraManager (no permission required):
- Camera ID and facing direction (front/back/external)
- Sensor physical size
- Supported JPEG resolutions
- Available focal lengths

Provides device fingerprinting without requesting camera permission.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect media codec support via MediaCodecList:
- Audio codecs (name, supported types, encoder/decoder)
- Video codecs (name, supported types, encoder/decoder)

Collect system features via PackageManager:
- Hardware features (camera, bluetooth, wifi, etc.)
- Software features (live wallpaper, etc.)

Both provide device fingerprinting signals.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect network information via ConnectivityManager and WifiManager:
- Connection type (wifi, cellular, ethernet, bluetooth, vpn)
- Metered status
- Downstream bandwidth estimate
- WiFi-specific: frequency, link speed, signal strength (RSSI)

Adds ACCESS_WIFI_STATE permission (ACCESS_NETWORK_STATE was already present).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect system settings via Settings.System/Global:
- Screen timeout
- Development settings enabled
- ADB enabled
- Animator duration scale
- Boot count

Collect behavioral signals via Settings.Secure:
- Enabled input methods (keyboards)
- Enabled accessibility services

These signals help identify device configuration and usage patterns.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Collect the default WebView user agent string via WebSettings,
which provides browser version and system information useful
for device fingerprinting.

Gracefully handles devices where WebView is unavailable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add @Suppress annotations for LongMethod and ReturnCount in GpuCollector
- Add @Suppress annotation for ReturnCount in NetworkCollector
- Use error() instead of throw IllegalStateException in DeviceTracker
- Add @Suppress for TooGenericExceptionCaught in DeviceTracker and DeviceApiClient

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit implements server-generated stored IDs (similar to browser
cookies in the JS implementation) and renames the existing device-generated
identifiers for clarity.

Changes:
- Rename StoredIDs → DeviceIDs for device-generated hardware identifiers
  (MediaDRM ID, Android ID)
- Add StoredID model for server-generated IDs (format: "{uuid}:{hmac}")
- Add StoredIDStorage using SharedPreferences for persistence
- Add StoredIDCollector to read stored IDs during collection
- Update DeviceApiClient to parse server response and return stored ID
- Update DeviceTracker to save stored ID from server response
- Add @SerialName annotations for snake_case JSON serialization

JSON field names use snake_case for consistency with other MaxMind services:
- stored_id, device_ids, media_drm_id, android_id

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add two new device fingerprinting signals:

1. Telephony Context (TelephonyCollector):
   - Network operator name
   - SIM state
   - Phone type (GSM/CDMA/etc)
   - ICC card presence

2. Font Profile (FontCollector):
   - Tests for presence of standard Android fonts
   - Tests for manufacturer-specific fonts (Samsung, HTC, Sony, LG, Xiaomi, OnePlus)
   - Helps identify device manufacturers and custom ROMs

These signals were identified as missing from the original implementation plan
and provide additional device fingerprinting capabilities.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive HTTP tests using Ktor MockEngine:
- Test successful responses with stored_id
- Test null stored_id handling
- Test server error (500) handling
- Test client error (400) handling
- Test request body contains account_id and device data
- Test correct endpoint URL construction
- Test content type header
- Test network exception handling

Changes:
- Add ktor-client-mock test dependency
- Modify DeviceApiClient to accept optional HttpClient for testing
- Fix NestedClassesVisibility warning in ApiException
- Catch specific exceptions (SecurityException, IllegalArgumentException,
  IllegalStateException) instead of generic Exception in collectors
- Update CodecCollectorTest and TelephonyCollectorTest for specific exceptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the initial MaxMind Device SDK for Android, a comprehensive library for collecting device fingerprinting data and sending it to MaxMind servers. The implementation follows Android best practices with Kotlin-first design, Java compatibility, and coroutine-based async operations.

Key changes:

  • Complete SDK implementation with singleton pattern and initialization guard
  • Comprehensive device data collection (GPU, sensors, cameras, audio, display, etc.)
  • Network layer using Ktor with dual IPv6/IPv4 request flow
  • Sample app demonstrating SDK usage with Material Design UI
  • Extensive unit test coverage using JUnit 5, MockK, and Robolectric

Reviewed changes

Copilot reviewed 81 out of 81 changed files in this pull request and generated no comments.

Show a summary per file
File Description
sample/src/main/res/values/strings.xml Added UI strings for sample app demonstrating SDK functionality
sample/src/main/res/layout/activity_main.xml Material Design UI layout for SDK demo with collapsible device info sections
sample/src/main/java/.../MainActivity.kt Sample app demonstrating SDK initialization, data collection, and transmission
device-sdk/src/test/.../StoredIDStorageTest.kt Unit tests for SharedPreferences-based stored ID persistence
device-sdk/src/test/.../DeviceApiClientTest.kt Comprehensive tests for HTTP client with mock engine
device-sdk/src/test/.../SdkConfigTest.kt Builder pattern validation tests for SDK configuration
device-sdk/src/test/.../collector/helper/*.kt Tests for device info collection helpers (locale, installation, hardware, display)
device-sdk/src/test/.../collector/*.kt Tests for specialized collectors (WebView, telephony, sensors, etc.)
device-sdk/src/test/.../DeviceTrackerTest.kt Singleton lifecycle tests with reflection-based reset
device-sdk/src/main/.../storage/StoredIDStorage.kt SharedPreferences-based persistence for server-generated IDs
device-sdk/src/main/.../network/DeviceApiClient.kt Ktor HTTP client with dual IPv6/IPv4 request support
device-sdk/src/main/.../model/*.kt Serializable data models for device information
device-sdk/src/main/.../config/SdkConfig.kt Immutable configuration with builder pattern
device-sdk/src/main/.../collector/helper/*.kt Helper classes for collecting device information
device-sdk/src/main/.../collector/*.kt Specialized collectors for various device subsystems
device-sdk/src/main/.../DeviceTracker.kt Main SDK singleton entry point with lifecycle management
device-sdk/src/androidTest/.../collector/*.kt Instrumented tests for GPU, fonts, and cameras
device-sdk/proguard-rules.pro R8/ProGuard rules for SDK minification
device-sdk/consumer-rules.pro Consumer ProGuard rules for apps using the SDK
SETUP.md Comprehensive project setup guide
README.md SDK documentation with usage examples and API reference
CLAUDE.md Development guidelines for AI assistants
CHANGELOG.md Version history starting with 0.1.0

The code is well-structured, thoroughly tested, and follows Android/Kotlin best practices. No critical issues were identified during the review.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

oschwald and others added 8 commits December 11, 2025 09:17
Add dual-request logic to capture both IPv6 and IPv4 addresses for devices:
- First sends to IPv6 endpoint (d-ipv6.mmapiws.com)
- If response has ip_version=6, also sends to IPv4 endpoint (d-ipv4.mmapiws.com)
- Custom server URL bypasses dual-request and sends to single endpoint

Changes:
- SdkConfig: Replace serverUrl with customServerUrl, add useDefaultServers,
  add DEFAULT_IPV6_HOST, DEFAULT_IPV4_HOST, ENDPOINT_PATH constants
- ServerResponse: Add ipVersion field for IP version detection
- DeviceApiClient: Refactor to accept SdkConfig, implement sendWithDualRequest()
- DeviceTracker: Update to use SdkConfig-based DeviceApiClient
- Update tests for new behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Display device data in expandable/collapsible sections instead of raw JSON.
Uses reflection to dynamically iterate over DeviceData properties, so new
fields are automatically included without code changes.

Changes:
- Add collapsible section UI with tap to expand/collapse
- Show summary at top, detailed JSON in collapsible sections below
- Add kotlin-reflect dependency for dynamic property iteration
- All sections collapsed by default

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Display stored ID, MediaDRM ID, and Android ID in the summary section
for quick reference when debugging. Shows "(none)" for null values.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add a collectSafe() helper function that wraps inline collection methods
with try-catch error handling. This ensures partial device data is still
collected even if individual subsystems fail (e.g., WindowManager or
PackageManager throws).

Changes:
- Add collectSafe<T>(fallback, block) inline function
- Define fallback constants for BuildInfo, DisplayInfo, HardwareInfo,
  InstallationInfo, and LocaleInfo
- Wrap all inline collection calls in collect() with collectSafe
- Add enableLogging constructor parameter for optional failure logging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Extract inline collection methods into injectable helper classes to
enable unit testing without requiring Robolectric or instrumented tests.

New helper classes:
- BuildInfoHelper: Collects device build information from Build.*
- DisplayInfoHelper: Collects display metrics and HDR capabilities
- HardwareInfoHelper: Collects CPU, memory, and storage info
- InstallationInfoHelper: Collects app installation metadata
- LocaleInfoHelper: Collects locale and timezone info

DeviceDataCollector now accepts these helpers via constructor with
defaults, allowing tests to inject mocks for isolated testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add instrumented tests that run on real Android devices/emulators to verify:
- GpuCollector: EGL context creation, renderer/vendor info, resource cleanup
- CameraCollector: Camera2 API access, facing values, repeated calls
- FontCollector: Typeface detection, standard fonts, consistency

These tests require actual Android APIs and cannot be mocked in unit tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add unit tests for DeviceTracker singleton pattern using MockK to mock
the companion object state. Tests cover:
- getInstance throws when not initialized
- isInitialized returns expected values
- initialize throws when already initialized
- getInstance returns tracker when initialized
- collectDeviceData and shutdown can be called
- SdkConfig.Builder creates valid configuration

Uses mockkObject for companion object mocking since reflection-based
singleton reset is unreliable with Kotlin @volatile fields.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fix documentation issues:
- Change SdkConfig.Builder to use Int accountID instead of String API key
- Fix deviceData property access to use nested structure (deviceData.build.*)
- Update configuration options table with correct parameter names/types

Expand "Collected Data" section to document all signals:
- Device identifiers (storedID, deviceIDs)
- Device info (build, display, hardware)
- Subsystems (GPU, audio, sensors, cameras, codecs)
- System state (features, network, installation, settings, behavior)
- Additional signals (telephony, fonts, locale/timezone, WebView UA)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@oschwald oschwald force-pushed the main branch 5 times, most recently from 9ab8d81 to eb121fd Compare December 11, 2025 18:23
oschwald and others added 4 commits December 11, 2025 12:15
Add ability to configure the sample app to connect to a local development
server instead of production MaxMind servers. This is useful for testing
the SDK against a local backend.

Configuration via local.properties (gitignored):
- debug.server.url: Custom server URL (e.g., https://localhost:8443)
- debug.ca.cert: Path to CA certificate for self-signed HTTPS

When debug.ca.cert is configured, the build system:
- Copies the certificate to res/raw/debug_ca.crt (gitignored)
- Generates a network_security_config.xml that trusts the bundled cert
- Sets BuildConfig.DEBUG_SERVER_URL for the app to use

Without configuration, the sample app connects to production servers.

Also documents ADB reverse port forwarding for physical device testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Document the pre-commit formatting workflow using precious tidy -g
to fix formatting issues before committing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Move account ID from hardcoded value to local.properties configuration.
The sample app now requires maxmind.account.id to be set, showing an
error message if not configured.

Configuration in local.properties:
  maxmind.account.id=123456

This ensures developers use their own account ID rather than a demo value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Measure HTTP round-trip time of the first (IPv6) request and send it
with the second (IPv4) request. This matches the browser device.js
behavior and helps with proxy detection.

Changes:
- Add requestDuration field to DeviceData (Float?, milliseconds)
- Measure time in sendWithDualRequest() around IPv6 request
- Pass duration to IPv4 request via DeviceData.copy()
- Add tests for dual-request behavior and request_duration field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@horgh
Copy link

horgh commented Dec 15, 2025

I can't comment inline due to the size.

  • I'm curious about SETUP.md. Is that a standard thing to include? It seems to repeat some things from the README, though it has more of course. Would it make sense to link it from the README or something?
  • In some of the collectors, such as the camera one, we swallow exceptions. I wonder if adding logging would be useful there for debugging.
  • I noticed in the codec collector we are more granular about exceptions we catch rather than catching all. Would we want to catch all?
  • I wonder if we'd want to sort lists in more cases. I noticed in the system features collector we sort but not in some of the others. However order may already be stable in some/all cases?
  • I mostly skipped looking at tests and the sample app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants